s3:smb2_notify: fix use after free on long living notify requests
[samba.git] / source3 / smbd / smb2_notify.c
1 /*
2    Unix SMB/CIFS implementation.
3    Core SMB2 server
4
5    Copyright (C) Stefan Metzmacher 2009
6    Copyright (C) Jeremy Allison 2010
7
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.
12
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.
17
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/>.
20 */
21
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"
27
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;
35 };
36
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);
47
48 static void smbd_smb2_request_notify_done(struct tevent_req *subreq);
49 NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req)
50 {
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;
60
61         status = smbd_smb2_request_verify_sizes(req, 0x20);
62         if (!NT_STATUS_IS_OK(status)) {
63                 return smbd_smb2_request_error(req, status);
64         }
65         inbody = SMBD_SMB2_IN_BODY_PTR(req);
66
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);
72
73         /*
74          * 0x00010000 is what Windows 7 uses,
75          * Windows 2008 uses 0x00080000
76          */
77         if (in_output_buffer_length > req->sconn->smb2.max_trans) {
78                 return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
79         }
80
81         status = smbd_smb2_request_verify_creditcharge(req,
82                                                 in_output_buffer_length);
83
84         if (!NT_STATUS_IS_OK(status)) {
85                 return smbd_smb2_request_error(req, status);
86         }
87
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);
91         }
92
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);
100         }
101         tevent_req_set_callback(subreq, smbd_smb2_request_notify_done, req);
102
103         return smbd_smb2_request_pending_queue(req, subreq, 500);
104 }
105
106 static void smbd_smb2_request_notify_done(struct tevent_req *subreq)
107 {
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 */
116
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;
127                 }
128                 return;
129         }
130
131         out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
132
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;
140                 }
141                 return;
142         }
143
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 */
149
150         outdyn = out_output_buffer;
151
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;
157         }
158 }
159
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);
164
165 static int smbd_smb2_notify_state_destructor(struct smbd_smb2_notify_state *state)
166 {
167         if (!state->has_request) {
168                 return 0;
169         }
170
171         state->skip_reply = true;
172         smbd_notify_cancel_by_smbreq(state->smbreq);
173         return 0;
174 }
175
176 static int smbd_smb2_notify_smbreq_destructor(struct smb_request *smbreq)
177 {
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);
182
183         /*
184          * Our temporary parent from change_notify_add_request()
185          * goes away.
186          */
187         state->has_request = false;
188
189         /*
190          * move it back to its original parent,
191          * which means we no longer need the destructor
192          * to protect it.
193          */
194         talloc_steal(smbreq->smb2req, smbreq);
195         talloc_set_destructor(smbreq, NULL);
196
197         /*
198          * We want to keep smbreq!
199          */
200         return -1;
201 }
202
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)
210 {
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;
217
218         req = tevent_req_create(mem_ctx, &state,
219                                 struct smbd_smb2_notify_state);
220         if (req == NULL) {
221                 return NULL;
222         }
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);
227
228         DEBUG(10,("smbd_smb2_notify_send: %s - %s\n",
229                   fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
230
231         smbreq = smbd_smb2_fake_smb_request(smb2req);
232         if (tevent_req_nomem(smbreq, req)) {
233                 return tevent_req_post(req, ev);
234         }
235
236         state->smbreq = smbreq;
237         smbreq->async_priv = (void *)req;
238
239         if (DEBUGLEVEL >= 3) {
240                 char *filter_string;
241
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);
245                 }
246
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));
250
251                 TALLOC_FREE(filter_string);
252         }
253
254         if ((!fsp->is_directory) || (conn != fsp->conn)) {
255                 tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
256                 return tevent_req_post(req, ev);
257         }
258
259         if (fsp->notify == NULL) {
260
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);
269                 }
270         }
271
272         if (change_notify_fsp_has_changes(fsp)) {
273
274                 /*
275                  * We've got changes pending, respond immediately
276                  */
277
278                 /*
279                  * TODO: write a torture test to check the filtering behaviour
280                  * here.
281                  */
282
283                 change_notify_reply(smbreq,
284                                     NT_STATUS_OK,
285                                     in_output_buffer_length,
286                                     fsp->notify,
287                                     smbd_smb2_notify_reply);
288
289                 /*
290                  * change_notify_reply() above has independently
291                  * called tevent_req_done().
292                  */
293                 return tevent_req_post(req, ev);
294         }
295
296         /*
297          * No changes pending, queue the request
298          */
299
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);
308         }
309
310         /*
311          * This is a HACK!
312          *
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.
316          */
317         state->has_request = true;
318         talloc_set_destructor(smbreq, smbd_smb2_notify_smbreq_destructor);
319
320         /* allow this request to be canceled */
321         tevent_req_set_cancel_fn(req, smbd_smb2_notify_cancel);
322
323         return req;
324 }
325
326 static void smbd_smb2_notify_reply(struct smb_request *smbreq,
327                                    NTSTATUS error_code,
328                                    uint8_t *buf, size_t len)
329 {
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);
334
335         if (state->skip_reply) {
336                 return;
337         }
338
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;
348                 }
349         }
350
351         tevent_req_defer_callback(req, state->smb2req->sconn->ev_ctx);
352
353         if (!NT_STATUS_IS_OK(state->status)) {
354                 tevent_req_nterror(req, state->status);
355                 return;
356         }
357
358         tevent_req_done(req);
359 }
360
361 static bool smbd_smb2_notify_cancel(struct tevent_req *req)
362 {
363         struct smbd_smb2_notify_state *state = tevent_req_data(req,
364                                                struct smbd_smb2_notify_state);
365
366         smbd_notify_cancel_by_smbreq(state->smbreq);
367
368         return true;
369 }
370
371 static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
372                                       TALLOC_CTX *mem_ctx,
373                                       DATA_BLOB *out_output_buffer)
374 {
375         NTSTATUS status;
376         struct smbd_smb2_notify_state *state = tevent_req_data(req,
377                                                struct smbd_smb2_notify_state);
378
379         if (tevent_req_is_nterror(req, &status)) {
380                 tevent_req_received(req);
381                 return status;
382         }
383
384         *out_output_buffer = state->out_output_buffer;
385         talloc_steal(mem_ctx, out_output_buffer->data);
386
387         tevent_req_received(req);
388         return NT_STATUS_OK;
389 }