summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
blob: a09d5f80de4366ccd4c28ec1ff5a35ff90a62ea5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2020 Tintri by DDN, Inc.  All rights reserved.
 * Copyright 2022 RackTop Systems, Inc.
 */

/*
 * SMB: locking_andx
 *
 * SMB_COM_LOCKING_ANDX allows both locking and/or unlocking of file range(s).
 *
 *  Client Request                     Description
 *  ================================== =================================
 *
 *  UCHAR WordCount;                   Count of parameter words = 8
 *  UCHAR AndXCommand;                 Secondary (X) command;  0xFF = none
 *  UCHAR AndXReserved;                Reserved (must be 0)
 *  USHORT AndXOffset;                 Offset to next command WordCount
 *  USHORT Fid;                        File handle
 *  UCHAR LockType;                    See LockType table below
 *  UCHAR OplockLevel;                 The new oplock level
 *  ULONG Timeout;                     Milliseconds to wait for unlock
 *  USHORT NumberOfUnlocks;            Num. unlock range structs following
 *  USHORT NumberOfLocks;              Num. lock range structs following
 *  USHORT ByteCount;                  Count of data bytes
 *  LOCKING_ANDX_RANGE Unlocks[];      Unlock ranges
 *  LOCKING_ANDX_RANGE Locks[];        Lock ranges
 *
 *  LockType Flag Name            Value Description
 *  ============================  ===== ================================
 *
 *  LOCKING_ANDX_SHARED_LOCK      0x01  Read-only lock
 *  LOCKING_ANDX_OPLOCK_RELEASE   0x02  Oplock break notification
 *  LOCKING_ANDX_CHANGE_LOCKTYPE  0x04  Change lock type
 *  LOCKING_ANDX_CANCEL_LOCK      0x08  Cancel outstanding request
 *  LOCKING_ANDX_LARGE_FILES      0x10  Large file locking format
 *
 *  LOCKING_ANDX_RANGE Format
 *  =====================================================================
 *
 *  USHORT Pid;                        PID of process "owning" lock
 *  ULONG Offset;                      Offset to bytes to [un]lock
 *  ULONG Length;                      Number of bytes to [un]lock
 *
 *  Large File LOCKING_ANDX_RANGE Format
 *  =====================================================================
 *
 *  USHORT Pid;                        PID of process "owning" lock
 *  USHORT Pad;                        Pad to DWORD align (mbz)
 *  ULONG OffsetHigh;                  Offset to bytes to [un]lock
 *                                      (high)
 *  ULONG OffsetLow;                   Offset to bytes to [un]lock (low)
 *  ULONG LengthHigh;                  Number of bytes to [un]lock
 *                                      (high)
 *  ULONG LengthLow;                   Number of bytes to [un]lock (low)
 *
 *  Server Response                    Description
 *  ================================== =================================
 *
 *  UCHAR WordCount;                   Count of parameter words = 2
 *  UCHAR AndXCommand;                 Secondary (X) command;  0xFF =
 *                                      none
 *  UCHAR AndXReserved;                Reserved (must be 0)
 *  USHORT AndXOffset;                 Offset to next command WordCount
 *  USHORT ByteCount;                  Count of data bytes = 0
 *
 * Locking is a simple mechanism for excluding other processes read/write
 * access to regions of a file.  The locked regions can be anywhere in the
 * logical file.  Locking beyond end-of-file is permitted.  Any process
 * using the Fid specified in this request's Fid has access to the locked
 * bytes, other processes will be denied the locking of the same bytes.
 *
 * The proper method for using locks is not to rely on being denied read or
 * write access on any of the read/write protocols but rather to attempt
 * the locking protocol and proceed with the read/write only if the locks
 * succeeded.
 *
 * Locking a range of bytes will fail if any subranges or overlapping
 * ranges are locked.  In other words, if any of the specified bytes are
 * already locked, the lock will fail.
 *
 * If NumberOfUnlocks is non-zero, the Unlocks vector contains
 * NumberOfUnlocks elements.  Each element requests that a lock at Offset
 * of Length be released.  If NumberOfLocks is nonzero, the Locks vector
 * contains NumberOfLocks elements.  Each element requests the acquisition
 * of a lock at Offset of Length.
 *
 * Timeout is the maximum amount of time to wait for the byte range(s)
 * specified to become unlocked.  A timeout value of 0 indicates that the
 * server should fail immediately if any lock range specified is locked.  A
 *
 * timeout value of -1 indicates that the server should wait as long as it
 * takes for each byte range specified to become unlocked so that it may be
 * again locked by this protocol.  Any other value of smb_timeout specifies
 * the maximum number of milliseconds to wait for all lock range(s)
 * specified to become available.
 *
 * If any of the lock ranges timeout because of the area to be locked is
 * already locked (or the lock fails), the other ranges in the protocol
 * request which were successfully locked as a result of this protocol will
 * be unlocked (either all requested ranges will be locked when this
 * protocol returns to the client or none).
 *
 * If LockType has the LOCKING_ANDX_SHARED_LOCK flag set, the lock is
 * specified as a shared lock.  Locks for both read and write (where
 * LOCKING_ANDX_SHARED_LOCK is clear) should be prohibited, but other
 * shared locks should be permitted.  If shared locks can not be supported
 * by a server, the server should map the lock to a lock for both read and
 * write.  Closing a file with locks still in force causes the locks to be
 * released in no defined order.
 *
 * If LockType has the LOCKING_ANDX_LARGE_FILES flag set and if the
 * negotiated protocol is NT LM 0.12 or later, then the Locks and Unlocks
 * vectors are in the Large File LOCKING_ANDX_RANGE format.  This allows
 * specification of 64 bit offsets for very large files.
 *
 * If the one and only member of the Locks vector has the
 * LOCKING_ANDX_CANCEL_LOCK flag set in the LockType field, the client is
 * requesting the server to cancel a previously requested, but not yet
 * responded to, lock.
 *
 * If LockType has the LOCKING_ANDX_CHANGE_LOCKTYPE flag set, the client is
 * requesting that the server atomically change the lock type from a shared
 * lock to an exclusive lock or vice versa.  If the server can not do this
 * in an atomic fashion, the server must reject this request.  NT and W95
 * servers do not support this capability.
 *
 * Oplocks are described in the "Opportunistic Locks" section elsewhere in
 * this document.  A client requests an oplock by setting the appropriate
 * bit in the SMB_COM_OPEN_ANDX request when the file is being opened in a
 * mode which is not exclusive.  The server responds by setting the
 * appropriate bit in the response SMB indicating whether or not the oplock
 * was granted.  By granting the oplock, the server tells the client the
 * file is currently only being used by this one client process at the
 * current time.  The client can therefore safely do read ahead and write
 * behind as well as local caching of file locks knowing that the file will
 * not be accessed/changed in any way by another process while the oplock
 * is in effect.  The client will be notified when any other process
 * attempts to open or modify the oplocked file.
 *
 * When another user attempts to open or otherwise modify the file which a
 * client has oplocked, the server delays the second attempt and notifies
 * the client via an SMB_LOCKING_ANDX SMB asynchronously sent from the
 * server to the client.  This message has the LOCKING_ANDX_OPLOCK_RELEASE
 * flag set indicating to the client that the oplock is being broken.
 *
 * OplockLevel indicates the type of oplock the client now owns. If
 * OplockLevel is 0, the client possesses no oplocks on the file at all, if
 * OplockLevel is 1 the client possesses a Level II oplock.  The client is
 * expected to flush any dirty buffers to the server, submit any file locks
 * and respond to the server with either an SMB_LOCKING_ANDX SMB having the
 * LOCKING_ANDX_OPLOCK_RELEASE flag set, or with a file close if the file
 * is no longer in use by the client.  If the client sends an
 * SMB_LOCKING_ANDX SMB with the LOCKING_ANDX_OPLOCK_RELEASE flag set and
 * NumberOfLocks is zero, the server does not send a response.  Since a
 * close being sent to the server and break oplock notification from the
 * server could cross on the wire, if the client gets an oplock
 * notification on a file which it does not have open, that notification
 * should be ignored.
 *
 * Due to timing, the client could get an "oplock broken" notification in a
 * user's data buffer as a result of this notification crossing on the wire
 * with a SMB_COM_READ_RAW request.  The client must detect this (use
 * length of msg, "FFSMB", MID of -1 and Command of SMB_COM_LOCKING_ANDX)
 * and honor the "oplock broken" notification as usual.  The server must
 * also note on receipt of an SMB_COM_READ_RAW request that there is an
 * outstanding (unanswered) "oplock broken" notification to the client and
 * return a zero length response denoting failure of the read raw request.
 * The client should (after responding to the "oplock broken"
 * notification), use a standard read protocol to redo the read request.
 * This allows a file to actually contain data matching an "oplock broken"
 * notification and still be read correctly.
 *
 * The entire message sent and received including the optional second
 * protocol must fit in the negotiated maximum transfer size.  The
 * following are the only valid SMB commands for AndXCommand for
 * SMB_COM_LOCKING_ANDX:
 *
 *     SMB_COM_READ       SMB_COM_READ_ANDX
 *     SMB_COM_WRITE      SMB_COM_WRITE_ANDX
 *     SMB_COM_FLUSH
 *
 * 4.2.6.1   Errors
 *
 * ERRDOS/ERRbadfile
 * ERRDOS/ERRbadfid
 * ERRDOS/ERRlock
 * ERRDOS/ERRinvdevice
 * ERRSRV/ERRinvid
 * ERRSRV/ERRbaduid
 */

#include <smbsrv/smb_kproto.h>

/*
 * This is a somewhat arbitrary sanity limit on the length of the
 * SMB2_LOCK_ELEMENT array.  It usually has length one or two.
 */
int smb_lock_max_elem = 1024;

smb_sdrc_t
smb_pre_locking_andx(smb_request_t *sr)
{
	DTRACE_SMB_START(op__LockingX, smb_request_t *, sr);
	return (SDRC_SUCCESS);
}

void
smb_post_locking_andx(smb_request_t *sr)
{
	DTRACE_SMB_DONE(op__LockingX, smb_request_t *, sr);
}

struct lreq {
	uint64_t off;
	uint64_t len;
	uint32_t pid;
	uint32_t reserved;
};

smb_sdrc_t
smb_com_locking_andx(smb_request_t *sr)
{
	unsigned short	i;
	unsigned char	lock_type;	/* See lock_type table above */
	unsigned char	oplock_level;	/* The new oplock level */
	uint32_t	timeout;	/* Milliseconds to wait for lock */
	unsigned short	unlock_num;	/* # unlock range structs */
	unsigned short	lock_num;	/* # lock range structs */
	DWORD		result;
	int		rc;
	uint32_t	ltype;
	smb_ofile_t	*ofile;
	uint16_t	tmp_pid;	/* locking uses 16-bit pids */
	uint32_t	lrv_tot;
	struct lreq	*lrv_ul;
	struct lreq	*lrv_lk;
	struct lreq	*lr;

	rc = smbsr_decode_vwv(sr, "4.wbblww", &sr->smb_fid, &lock_type,
	    &oplock_level, &timeout, &unlock_num, &lock_num);
	if (rc != 0)
		return (SDRC_ERROR);

	smbsr_lookup_file(sr);
	if (sr->fid_ofile == NULL) {
		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
		return (SDRC_ERROR);
	}
	ofile = sr->fid_ofile;
	if (ofile->f_node == NULL) {
		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
		    ERRDOS, ERROR_INVALID_PARAMETER);
		return (SDRC_ERROR);
	}

	if (unlock_num > smb_lock_max_elem ||
	    lock_num > smb_lock_max_elem) {
		smbsr_error(sr, NT_STATUS_INSUFFICIENT_RESOURCES,
		    ERRDOS, ERROR_NO_SYSTEM_RESOURCES);
		return (SDRC_ERROR);
	}

	if (lock_type & LOCKING_ANDX_SHARED_LOCK)
		ltype = SMB_LOCK_TYPE_READONLY;
	else
		ltype = SMB_LOCK_TYPE_READWRITE;

	if (lock_type & LOCKING_ANDX_OPLOCK_RELEASE) {
		smb1_oplock_ack_break(sr, oplock_level);
		if (unlock_num == 0 && lock_num == 0)
			return (SDRC_NO_REPLY);
		/*
		 * Don't allow combining other lock/unlock actions
		 * with an oplock ACK (normally don't get here).
		 */
		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
		    ERRDOS, ERROR_INVALID_PARAMETER);
		return (SDRC_ERROR);
	}

	/*
	 * No support for changing locktype (although we could probably
	 * implement this)
	 */
	if (lock_type & LOCKING_ANDX_CHANGE_LOCK_TYPE) {
		smbsr_error(sr, 0, ERRDOS,
		    ERROR_ATOMIC_LOCKS_NOT_SUPPORTED);
		return (SDRC_ERROR);
	}

	if (lock_type & LOCKING_ANDX_LARGE_FILES) {
		/*
		 * negotiated protocol should be NT LM 0.12 or later
		 */
		if (sr->session->dialect < NT_LM_0_12) {
			smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
			    ERRDOS, ERROR_INVALID_PARAMETER);
			return (SDRC_ERROR);
		}
	}

	/*
	 * Parse the unlock, lock vectors.  Will parse all the
	 * unlock + lock records into one array, and then use
	 * pointers to the unlock and lock parts.
	 */
	lrv_tot = unlock_num + lock_num;
	lrv_ul = smb_srm_zalloc(sr, lrv_tot * sizeof (*lrv_ul));
	lrv_lk = &lrv_ul[unlock_num];

	for (i = 0; i < lrv_tot; i++) {
		lr = &lrv_ul[i];
		if (lock_type & LOCKING_ANDX_LARGE_FILES) {
			rc = smb_mbc_decodef(&sr->smb_data, "w2.QQ",
			    &tmp_pid, &lr->off, &lr->len);
		} else {
			uint32_t	offset32, length32;
			rc = smb_mbc_decodef(&sr->smb_data, "wll",
			    &tmp_pid, &offset32, &length32);
			lr->off = offset32;
			lr->len = length32;
		}
		lr->pid = tmp_pid;	/* 16-bit PID */
		if (rc) {
			/*
			 * This is the error returned by Windows 2000
			 * even when STATUS32 has been negotiated.
			 */
			smbsr_error(sr, 0, ERRSRV, ERRerror);
			return (SDRC_ERROR);
		}
	}

	/*
	 * Cancel waiting locks.  MS-CIFS says one place that
	 * this cancels all waiting locks for this FID+PID,
	 * but smbtorture insists this cancels just one.
	 * Tests with Windows 7 confirms that.
	 */
	if ((lock_type & LOCKING_ANDX_CANCEL_LOCK) != 0) {
		lr = lrv_lk;

		result = smb_lock_range_cancel(sr, lr->off, lr->len, lr->pid);

		if (result != NT_STATUS_SUCCESS) {
			smbsr_error(sr, 0, ERRDOS,
			    ERROR_CANCEL_VIOLATION);
			return (SDRC_ERROR);
		}
		goto out;
	}

	/*
	 * Normal unlock and lock list
	 */
	for (i = 0; i < unlock_num; i++) {
		lr = &lrv_ul[i];

		result = smb_unlock_range(sr, lr->off, lr->len, lr->pid);
		if (result != NT_STATUS_SUCCESS) {
			smbsr_error(sr, NT_STATUS_RANGE_NOT_LOCKED,
			    ERRDOS, ERROR_NOT_LOCKED);
			return (SDRC_ERROR);
		}
	}
	for (i = 0; i < lock_num; i++) {
		lr = &lrv_lk[i];

		result = smb_lock_range(sr, lr->off, lr->len, lr->pid,
		    ltype, timeout);
		if (result != NT_STATUS_SUCCESS) {
			/*
			 * Oh... we have to rollback.
			 */
			while (i > 0) {
				--i;
				lr = &lrv_lk[i];
				(void) smb_unlock_range(sr,
				    lr->off, lr->len, lr->pid);
			}
			smb_lock_range_error(sr, result);
			return (SDRC_ERROR);
		}
	}

out:
	if (smbsr_encode_result(sr, 2, 0, "bb.ww",
	    2, sr->andx_com, 0x27, 0) != 0)
		return (SDRC_ERROR);
	return (SDRC_SUCCESS);
}