s3:smb2_notify: fix use after free on long living notify requests
[Samba.git] / source3 / smbd / smb2_notify.c
blobc35acc50da74b6fed05778a6d472f6c278c7e76d
1 /*
2 Unix SMB/CIFS implementation.
3 Core SMB2 server
5 Copyright (C) Stefan Metzmacher 2009
6 Copyright (C) Jeremy Allison 2010
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "includes.h"
23 #include "smbd/smbd.h"
24 #include "smbd/globals.h"
25 #include "../libcli/smb/smb_common.h"
26 #include "../lib/util/tevent_ntstatus.h"
28 struct smbd_smb2_notify_state {
29 struct smbd_smb2_request *smb2req;
30 struct smb_request *smbreq;
31 bool has_request;
32 bool skip_reply;
33 NTSTATUS status;
34 DATA_BLOB out_output_buffer;
37 static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
38 struct tevent_context *ev,
39 struct smbd_smb2_request *smb2req,
40 struct files_struct *in_fsp,
41 uint16_t in_flags,
42 uint32_t in_output_buffer_length,
43 uint64_t in_completion_filter);
44 static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
45 TALLOC_CTX *mem_ctx,
46 DATA_BLOB *out_output_buffer);
48 static void smbd_smb2_request_notify_done(struct tevent_req *subreq);
49 NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req)
51 NTSTATUS status;
52 const uint8_t *inbody;
53 uint16_t in_flags;
54 uint32_t in_output_buffer_length;
55 uint64_t in_file_id_persistent;
56 uint64_t in_file_id_volatile;
57 struct files_struct *in_fsp;
58 uint64_t in_completion_filter;
59 struct tevent_req *subreq;
61 status = smbd_smb2_request_verify_sizes(req, 0x20);
62 if (!NT_STATUS_IS_OK(status)) {
63 return smbd_smb2_request_error(req, status);
65 inbody = SMBD_SMB2_IN_BODY_PTR(req);
67 in_flags = SVAL(inbody, 0x02);
68 in_output_buffer_length = IVAL(inbody, 0x04);
69 in_file_id_persistent = BVAL(inbody, 0x08);
70 in_file_id_volatile = BVAL(inbody, 0x10);
71 in_completion_filter = IVAL(inbody, 0x18);
74 * 0x00010000 is what Windows 7 uses,
75 * Windows 2008 uses 0x00080000
77 if (in_output_buffer_length > req->sconn->smb2.max_trans) {
78 return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
81 status = smbd_smb2_request_verify_creditcharge(req,
82 in_output_buffer_length);
84 if (!NT_STATUS_IS_OK(status)) {
85 return smbd_smb2_request_error(req, status);
88 in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
89 if (in_fsp == NULL) {
90 return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
93 subreq = smbd_smb2_notify_send(req, req->sconn->ev_ctx,
94 req, in_fsp,
95 in_flags,
96 in_output_buffer_length,
97 in_completion_filter);
98 if (subreq == NULL) {
99 return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
101 tevent_req_set_callback(subreq, smbd_smb2_request_notify_done, req);
103 return smbd_smb2_request_pending_queue(req, subreq, 500);
106 static void smbd_smb2_request_notify_done(struct tevent_req *subreq)
108 struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
109 struct smbd_smb2_request);
110 DATA_BLOB outbody;
111 DATA_BLOB outdyn;
112 uint16_t out_output_buffer_offset;
113 DATA_BLOB out_output_buffer = data_blob_null;
114 NTSTATUS status;
115 NTSTATUS error; /* transport error */
117 status = smbd_smb2_notify_recv(subreq,
118 req,
119 &out_output_buffer);
120 TALLOC_FREE(subreq);
121 if (!NT_STATUS_IS_OK(status)) {
122 error = smbd_smb2_request_error(req, status);
123 if (!NT_STATUS_IS_OK(error)) {
124 smbd_server_connection_terminate(req->sconn,
125 nt_errstr(error));
126 return;
128 return;
131 out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
133 outbody = data_blob_talloc(req->out.vector, NULL, 0x08);
134 if (outbody.data == NULL) {
135 error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
136 if (!NT_STATUS_IS_OK(error)) {
137 smbd_server_connection_terminate(req->sconn,
138 nt_errstr(error));
139 return;
141 return;
144 SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
145 SSVAL(outbody.data, 0x02,
146 out_output_buffer_offset); /* output buffer offset */
147 SIVAL(outbody.data, 0x04,
148 out_output_buffer.length); /* output buffer length */
150 outdyn = out_output_buffer;
152 error = smbd_smb2_request_done(req, outbody, &outdyn);
153 if (!NT_STATUS_IS_OK(error)) {
154 smbd_server_connection_terminate(req->sconn,
155 nt_errstr(error));
156 return;
160 static void smbd_smb2_notify_reply(struct smb_request *smbreq,
161 NTSTATUS error_code,
162 uint8_t *buf, size_t len);
163 static bool smbd_smb2_notify_cancel(struct tevent_req *req);
165 static int smbd_smb2_notify_state_destructor(struct smbd_smb2_notify_state *state)
167 if (!state->has_request) {
168 return 0;
171 state->skip_reply = true;
172 smbd_notify_cancel_by_smbreq(state->smbreq);
173 return 0;
176 static int smbd_smb2_notify_smbreq_destructor(struct smb_request *smbreq)
178 struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
179 struct tevent_req);
180 struct smbd_smb2_notify_state *state = tevent_req_data(req,
181 struct smbd_smb2_notify_state);
184 * Our temporary parent from change_notify_add_request()
185 * goes away.
187 state->has_request = false;
190 * move it back to its original parent,
191 * which means we no longer need the destructor
192 * to protect it.
194 talloc_steal(smbreq->smb2req, smbreq);
195 talloc_set_destructor(smbreq, NULL);
198 * We want to keep smbreq!
200 return -1;
203 static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
204 struct tevent_context *ev,
205 struct smbd_smb2_request *smb2req,
206 struct files_struct *fsp,
207 uint16_t in_flags,
208 uint32_t in_output_buffer_length,
209 uint64_t in_completion_filter)
211 struct tevent_req *req;
212 struct smbd_smb2_notify_state *state;
213 struct smb_request *smbreq;
214 connection_struct *conn = smb2req->tcon->compat;
215 bool recursive = (in_flags & SMB2_WATCH_TREE) ? true : false;
216 NTSTATUS status;
218 req = tevent_req_create(mem_ctx, &state,
219 struct smbd_smb2_notify_state);
220 if (req == NULL) {
221 return NULL;
223 state->smb2req = smb2req;
224 state->status = NT_STATUS_INTERNAL_ERROR;
225 state->out_output_buffer = data_blob_null;
226 talloc_set_destructor(state, smbd_smb2_notify_state_destructor);
228 DEBUG(10,("smbd_smb2_notify_send: %s - %s\n",
229 fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
231 smbreq = smbd_smb2_fake_smb_request(smb2req);
232 if (tevent_req_nomem(smbreq, req)) {
233 return tevent_req_post(req, ev);
236 state->smbreq = smbreq;
237 smbreq->async_priv = (void *)req;
240 char *filter_string;
242 filter_string = notify_filter_string(NULL, in_completion_filter);
243 if (tevent_req_nomem(filter_string, req)) {
244 return tevent_req_post(req, ev);
247 DEBUG(3,("smbd_smb2_notify_send: notify change "
248 "called on %s, filter = %s, recursive = %d\n",
249 fsp_str_dbg(fsp), filter_string, recursive));
251 TALLOC_FREE(filter_string);
254 if ((!fsp->is_directory) || (conn != fsp->conn)) {
255 tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
256 return tevent_req_post(req, ev);
259 if (fsp->notify == NULL) {
261 status = change_notify_create(fsp,
262 in_completion_filter,
263 recursive);
264 if (!NT_STATUS_IS_OK(status)) {
265 DEBUG(10, ("change_notify_create returned %s\n",
266 nt_errstr(status)));
267 tevent_req_nterror(req, status);
268 return tevent_req_post(req, ev);
272 if (change_notify_fsp_has_changes(fsp)) {
275 * We've got changes pending, respond immediately
279 * TODO: write a torture test to check the filtering behaviour
280 * here.
283 change_notify_reply(smbreq,
284 NT_STATUS_OK,
285 in_output_buffer_length,
286 fsp->notify,
287 smbd_smb2_notify_reply);
290 * change_notify_reply() above has independently
291 * called tevent_req_done().
293 return tevent_req_post(req, ev);
297 * No changes pending, queue the request
300 status = change_notify_add_request(smbreq,
301 in_output_buffer_length,
302 in_completion_filter,
303 recursive, fsp,
304 smbd_smb2_notify_reply);
305 if (!NT_STATUS_IS_OK(status)) {
306 tevent_req_nterror(req, status);
307 return tevent_req_post(req, ev);
311 * This is a HACK!
313 * change_notify_add_request() talloc_moves()
314 * smbreq away from us, so we need a destructor
315 * which moves it back at the end.
317 state->has_request = true;
318 talloc_set_destructor(smbreq, smbd_smb2_notify_smbreq_destructor);
320 /* allow this request to be canceled */
321 tevent_req_set_cancel_fn(req, smbd_smb2_notify_cancel);
323 return req;
326 static void smbd_smb2_notify_reply(struct smb_request *smbreq,
327 NTSTATUS error_code,
328 uint8_t *buf, size_t len)
330 struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
331 struct tevent_req);
332 struct smbd_smb2_notify_state *state = tevent_req_data(req,
333 struct smbd_smb2_notify_state);
335 if (state->skip_reply) {
336 return;
339 state->status = error_code;
340 if (!NT_STATUS_IS_OK(error_code)) {
341 /* nothing */
342 } else if (len == 0) {
343 state->status = STATUS_NOTIFY_ENUM_DIR;
344 } else {
345 state->out_output_buffer = data_blob_talloc(state, buf, len);
346 if (state->out_output_buffer.data == NULL) {
347 state->status = NT_STATUS_NO_MEMORY;
351 tevent_req_defer_callback(req, state->smb2req->sconn->ev_ctx);
353 if (!NT_STATUS_IS_OK(state->status)) {
354 tevent_req_nterror(req, state->status);
355 return;
358 tevent_req_done(req);
361 static bool smbd_smb2_notify_cancel(struct tevent_req *req)
363 struct smbd_smb2_notify_state *state = tevent_req_data(req,
364 struct smbd_smb2_notify_state);
366 smbd_notify_cancel_by_smbreq(state->smbreq);
368 return true;
371 static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
372 TALLOC_CTX *mem_ctx,
373 DATA_BLOB *out_output_buffer)
375 NTSTATUS status;
376 struct smbd_smb2_notify_state *state = tevent_req_data(req,
377 struct smbd_smb2_notify_state);
379 if (tevent_req_is_nterror(req, &status)) {
380 tevent_req_received(req);
381 return status;
384 *out_output_buffer = state->out_output_buffer;
385 talloc_steal(mem_ctx, out_output_buffer->data);
387 tevent_req_received(req);
388 return NT_STATUS_OK;