smb2_durable.c revision 811599a462e8920d70cf548f4002182d3c222d13
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
50uint32_t smb2_dh_def_timeout = 60 * MILLISEC;	/* mSec. */
51uint32_t smb2_dh_max_timeout = 300 * MILLISEC;	/* mSec. */
52
53uint32_t smb2_res_def_timeout = 120 * MILLISEC;	/* mSec. */
54uint32_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 */
84boolean_t
85smb_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	if (of->dh_vers == SMB2_RESILIENT)
97		return (B_TRUE);
98
99	if (!SMB_OFILE_OPLOCK_GRANTED(of))
100		return (B_FALSE);
101
102	if (of->f_oplock_grant.og_level == SMB_OPLOCK_BATCH)
103		return (B_TRUE);
104
105	return (B_FALSE);
106}
107
108/*
109 * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7):
110 * - security descriptor must match provided descriptor
111 *
112 * If file is leased:
113 * - lease must be requested
114 * - client guid must match session guid
115 * - file name must match given name
116 * - lease key must match provided lease key
117 * If file is not leased:
118 * - Lease must not be requested
119 *
120 * dh_v2 only:
121 * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true
122 * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false
123 * - desired access, share access, and create_options must be ignored
124 * - createguid must match
125 */
126static uint32_t
127smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of)
128{
129	smb_arg_open_t	*op = &sr->sr_open;
130
131	if (op->dh_vers == SMB2_DURABLE_V2) {
132		boolean_t op_persist =
133		    ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0);
134		if (of->dh_persist != op_persist)
135			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
136		if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN))
137			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
138	}
139
140	if (!smb_is_same_user(sr->user_cr, of->f_cr))
141		return (NT_STATUS_ACCESS_DENIED);
142
143	return (NT_STATUS_SUCCESS);
144}
145
146/*
147 * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2)
148 *
149 * Looks up an ofile on the server's sv_dh_list by the persistid.
150 * If found, it validates the request.
151 * (see smb2_dh_reconnect_checks() for details)
152 * If the checks are passed, add it onto the new tree's list.
153 *
154 * Note that the oplock break code path can get to an ofile via the node
155 * ofile list.  It starts with a ref taken in smb_ofile_hold_olbrk, which
156 * waits if the ofile is found in state RECONNECT.  That wait happens with
157 * the node ofile list lock held as reader, and the oplock mutex held.
158 * Implications of that are: While we're in state RECONNECT, we shoud NOT
159 * block (at least, not for long) and must not try to enter any of the
160 * node ofile list lock or oplock mutex.  Thankfully, we don't need to
161 * enter those while reclaiming an orphaned ofile.
162 */
163uint32_t
164smb2_dh_reconnect(smb_request_t *sr)
165{
166	smb_arg_open_t	*op = &sr->sr_open;
167	smb_tree_t *tree = sr->tid_tree;
168	smb_ofile_t *of;
169	cred_t *old_cr;
170	uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
171	uint16_t fid = 0;
172
173	if (smb_idpool_alloc(&tree->t_fid_pool, &fid))
174		return (NT_STATUS_TOO_MANY_OPENED_FILES);
175
176	/* Find orphaned handle. */
177	of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent);
178	if (of == NULL)
179		goto errout;
180
181	mutex_enter(&of->f_mutex);
182	if (of->f_state != SMB_OFILE_STATE_ORPHANED) {
183		mutex_exit(&of->f_mutex);
184		goto errout;
185	}
186
187	status = smb2_dh_reconnect_checks(sr, of);
188	if (status != NT_STATUS_SUCCESS) {
189		mutex_exit(&of->f_mutex);
190		goto errout;
191	}
192
193	/*
194	 * Note: cv_broadcast(&of->f_cv) when we're
195	 * done messing around in this state.
196	 * See: smb_ofile_hold_olbrk()
197	 */
198	of->f_state = SMB_OFILE_STATE_RECONNECT;
199	mutex_exit(&of->f_mutex);
200
201	/*
202	 * At this point, we should be the only thread with a ref on the
203	 * ofile, and the RECONNECT state should prevent new refs from
204	 * being granted, or other durable threads from observing or
205	 * reclaiming it. Put this ofile in the new tree, similar to
206	 * the last part of smb_ofile_open.
207	 */
208
209	old_cr = of->f_cr;
210	of->f_cr = sr->user_cr;
211	crhold(of->f_cr);
212	crfree(old_cr);
213
214	of->f_session = sr->session; /* hold is via user and tree */
215	smb_user_hold_internal(sr->uid_user);
216	of->f_user = sr->uid_user;
217	smb_tree_hold_internal(tree);
218	of->f_tree = tree;
219	of->f_fid = fid;
220
221	op->op_oplock_level = of->f_oplock_grant.og_level;
222
223	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
224	smb_llist_insert_tail(&tree->t_ofile_list, of);
225	smb_llist_exit(&tree->t_ofile_list);
226	atomic_inc_32(&tree->t_open_files);
227	atomic_inc_32(&sr->session->s_file_cnt);
228
229	/*
230	 * The ofile is now in the caller's session & tree.
231	 *
232	 * In case smb_ofile_hold or smb_oplock_send_brk() are
233	 * waiting for state RECONNECT to complete, wakeup.
234	 */
235	mutex_enter(&of->f_mutex);
236	of->dh_expire_time = 0;
237	of->f_state = SMB_OFILE_STATE_OPEN;
238	cv_broadcast(&of->f_cv);
239	mutex_exit(&of->f_mutex);
240
241	/*
242	 * The ofile is now visible in the new session.
243	 * From here, this is similar to the last part of
244	 * smb_common_open().
245	 */
246	op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
247	(void) smb_node_getattr(sr, of->f_node, zone_kcred(), of,
248	    &op->fqi.fq_fattr);
249
250	/*
251	 * Set up the fileid and dosattr in open_param for response
252	 */
253	op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid;
254	op->dattr = op->fqi.fq_fattr.sa_dosattr;
255
256	/*
257	 * Set up the file type in open_param for the response
258	 * The ref. from ofile lookup is "given" to fid_ofile.
259	 */
260	op->ftype = SMB_FTYPE_DISK;
261	sr->smb_fid = of->f_fid;
262	sr->fid_ofile = of;
263
264	if (smb_node_is_file(of->f_node)) {
265		op->dsize = op->fqi.fq_fattr.sa_vattr.va_size;
266	} else {
267		/* directory or symlink */
268		op->dsize = 0;
269	}
270
271	op->create_options = 0; /* no more modifications wanted */
272	op->action_taken = SMB_OACT_OPENED;
273	return (NT_STATUS_SUCCESS);
274
275errout:
276	if (of != NULL)
277		smb_ofile_release(of);
278	if (fid != 0)
279		smb_idpool_free(&tree->t_fid_pool, fid);
280
281	return (status);
282}
283
284/*
285 * Durable handle expiration
286 * ofile state is _EXPIRED
287 */
288static void
289smb2_dh_expire(void *arg)
290{
291	smb_ofile_t *of = (smb_ofile_t *)arg;
292
293	smb_ofile_close(of, 0);
294	smb_ofile_release(of);
295}
296
297void
298smb2_durable_timers(smb_server_t *sv)
299{
300	smb_hash_t *hash;
301	smb_llist_t *bucket;
302	smb_ofile_t *of;
303	hrtime_t now;
304	int i;
305
306	hash = sv->sv_persistid_ht;
307	now = gethrtime();
308
309	for (i = 0; i < hash->num_buckets; i++) {
310		bucket = &hash->buckets[i].b_list;
311		smb_llist_enter(bucket, RW_READER);
312		for (of = smb_llist_head(bucket);
313		    of != NULL;
314		    of = smb_llist_next(bucket, of)) {
315			SMB_OFILE_VALID(of);
316
317			/*
318			 * Check outside the mutex first to avoid some
319			 * mutex_enter work in this loop.  If the state
320			 * changes under foot, the worst that happens
321			 * is we either enter the mutex when we might
322			 * not have needed to, or we miss some DH in
323			 * this pass and get it on the next.
324			 */
325			if (of->f_state != SMB_OFILE_STATE_ORPHANED)
326				continue;
327
328			mutex_enter(&of->f_mutex);
329			/* STATE_ORPHANED implies dh_expire_time != 0 */
330			if (of->f_state == SMB_OFILE_STATE_ORPHANED &&
331			    of->dh_expire_time <= now) {
332				of->f_state = SMB_OFILE_STATE_EXPIRED;
333				/* inline smb_ofile_hold_internal() */
334				of->f_refcnt++;
335				smb_llist_post(bucket, of, smb2_dh_expire);
336			}
337			mutex_exit(&of->f_mutex);
338		}
339		smb_llist_exit(bucket);
340	}
341}
342
343/*
344 * Clean out durable handles during shutdown.
345 * Like, smb2_durable_timers but expire all,
346 * and make sure the hash buckets are empty.
347 */
348void
349smb2_dh_shutdown(smb_server_t *sv)
350{
351	smb_hash_t *hash;
352	smb_llist_t *bucket;
353	smb_ofile_t *of;
354	int i;
355
356	hash = sv->sv_persistid_ht;
357
358	for (i = 0; i < hash->num_buckets; i++) {
359		bucket = &hash->buckets[i].b_list;
360		smb_llist_enter(bucket, RW_READER);
361		of = smb_llist_head(bucket);
362		while (of != NULL) {
363			SMB_OFILE_VALID(of);
364			mutex_enter(&of->f_mutex);
365
366			switch (of->f_state) {
367			case SMB_OFILE_STATE_ORPHANED:
368				of->f_state = SMB_OFILE_STATE_EXPIRED;
369				/* inline smb_ofile_hold_internal() */
370				of->f_refcnt++;
371				smb_llist_post(bucket, of, smb2_dh_expire);
372				break;
373			default:
374				break;
375			}
376			mutex_exit(&of->f_mutex);
377			of = smb_llist_next(bucket, of);
378		}
379		smb_llist_exit(bucket);
380	}
381
382#ifdef	DEBUG
383	for (i = 0; i < hash->num_buckets; i++) {
384		bucket = &hash->buckets[i].b_list;
385		smb_llist_enter(bucket, RW_READER);
386		of = smb_llist_head(bucket);
387		while (of != NULL) {
388			SMB_OFILE_VALID(of);
389			cmn_err(CE_NOTE, "dh_shutdown leaked of=%p",
390			    (void *)of);
391			of = smb_llist_next(bucket, of);
392		}
393		smb_llist_exit(bucket);
394	}
395#endif	// DEBUG
396}
397
398uint32_t
399smb2_fsctl_resiliency(smb_request_t *sr, smb_fsctl_t *fsctl)
400{
401	uint32_t timeout;
402	smb_ofile_t *of = sr->fid_ofile;
403
404	/*
405	 * Note: The spec does not explicitly prohibit resilient directories
406	 * the same way it prohibits durable directories. We prohibit them
407	 * anyway as a simplifying assumption, as there doesn't seem to be
408	 * much use for it. (HYPER-V only seems to use it on files anyway)
409	 */
410	if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node))
411		return (NT_STATUS_INVALID_PARAMETER);
412
413	(void) smb_mbc_decodef(fsctl->in_mbc, "l4.",
414	    &timeout); /* milliseconds */
415
416	if (smb2_enable_dh == 0)
417		return (NT_STATUS_NOT_SUPPORTED);
418
419	/*
420	 * The spec wants us to return INVALID_PARAMETER if the timeout
421	 * is too large, but we have no way of informing the client
422	 * what an appropriate timeout is, so just set the timeout to
423	 * our max and return SUCCESS.
424	 */
425	if (timeout == 0)
426		timeout = smb2_res_def_timeout;
427	if (timeout > smb2_res_max_timeout)
428		timeout = smb2_res_max_timeout;
429
430	mutex_enter(&of->f_mutex);
431	of->dh_vers = SMB2_RESILIENT;
432	of->dh_timeout_offset = MSEC2NSEC(timeout);
433	mutex_exit(&of->f_mutex);
434
435	return (NT_STATUS_SUCCESS);
436}
437