Coverage Report

Created: 2026-07-25 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/smbd/smb2_write.c
Line
Count
Source
1
/*
2
   Unix SMB/CIFS implementation.
3
   Core SMB2 server
4
5
   Copyright (C) Stefan Metzmacher 2009
6
7
   This program is free software; you can redistribute it and/or modify
8
   it under the terms of the GNU General Public License as published by
9
   the Free Software Foundation; either version 3 of the License, or
10
   (at your option) any later version.
11
12
   This program is distributed in the hope that it will be useful,
13
   but WITHOUT ANY WARRANTY; without even the implied warranty of
14
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
   GNU General Public License for more details.
16
17
   You should have received a copy of the GNU General Public License
18
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
*/
20
21
#include "includes.h"
22
#include "smbd/smbd.h"
23
#include "smbd/globals.h"
24
#include "../libcli/smb/smb_common.h"
25
#include "../lib/util/tevent_ntstatus.h"
26
#include "rpc_server/srv_pipe_hnd.h"
27
#include "libcli/security/security.h"
28
29
#undef DBGC_CLASS
30
0
#define DBGC_CLASS DBGC_SMB2
31
32
static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
33
                 struct tevent_context *ev,
34
                 struct smbd_smb2_request *smb2req,
35
                 struct files_struct *in_fsp,
36
                 DATA_BLOB in_data,
37
                 off_t in_offset,
38
                 uint32_t in_flags);
39
static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
40
             uint32_t *out_count);
41
42
static void smbd_smb2_request_write_done(struct tevent_req *subreq);
43
NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req)
44
0
{
45
0
  struct smbXsrv_connection *xconn = req->xconn;
46
0
  NTSTATUS status;
47
0
  const uint8_t *inbody;
48
0
  uint16_t in_data_offset;
49
0
  uint32_t in_data_length;
50
0
  DATA_BLOB in_data_buffer;
51
0
  off_t in_offset;
52
0
  uint64_t in_file_id_persistent;
53
0
  uint64_t in_file_id_volatile;
54
0
  struct files_struct *in_fsp;
55
0
  uint32_t in_flags;
56
0
  size_t in_dyn_len = 0;
57
0
  uint8_t *in_dyn_ptr = NULL;
58
0
  struct tevent_req *subreq;
59
60
0
  status = smbd_smb2_request_verify_sizes(req, 0x31);
61
0
  if (!NT_STATUS_IS_OK(status)) {
62
0
    return smbd_smb2_request_error(req, status);
63
0
  }
64
0
  inbody = SMBD_SMB2_IN_BODY_PTR(req);
65
66
0
  in_data_offset    = SVAL(inbody, 0x02);
67
0
  in_data_length    = IVAL(inbody, 0x04);
68
0
  in_offset   = PULL_LE_I64(inbody, 0x08);
69
0
  in_file_id_persistent = BVAL(inbody, 0x10);
70
0
  in_file_id_volatile = BVAL(inbody, 0x18);
71
0
  in_flags    = IVAL(inbody, 0x2C);
72
73
0
  if (in_data_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
74
0
    return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
75
0
  }
76
77
0
  if (req->smb1req != NULL && req->smb1req->unread_bytes > 0) {
78
0
    in_dyn_ptr = NULL;
79
0
    in_dyn_len = req->smb1req->unread_bytes;
80
0
  } else {
81
0
    in_dyn_ptr = SMBD_SMB2_IN_DYN_PTR(req);
82
0
    in_dyn_len = SMBD_SMB2_IN_DYN_LEN(req);
83
0
  }
84
85
0
  if (in_data_length > in_dyn_len) {
86
0
    return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
87
0
  }
88
89
  /* check the max write size */
90
0
  if (in_data_length > xconn->smb2.server.max_write) {
91
0
    DEBUG(2,("smbd_smb2_request_process_write : "
92
0
      "client ignored max write :%s: 0x%08X: 0x%08X\n",
93
0
      __location__, in_data_length, xconn->smb2.server.max_write));
94
0
    return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
95
0
  }
96
97
  /*
98
   * Note: that in_dyn_ptr is NULL for the recvfile case.
99
   */
100
0
  in_data_buffer.data = in_dyn_ptr;
101
0
  in_data_buffer.length = in_data_length;
102
103
0
  status = smbd_smb2_request_verify_creditcharge(req, in_data_length);
104
0
  if (!NT_STATUS_IS_OK(status)) {
105
0
    return smbd_smb2_request_error(req, status);
106
0
  }
107
108
0
  in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
109
0
  if (in_fsp == NULL) {
110
0
    return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
111
0
  }
112
113
0
  subreq = smbd_smb2_write_send(req, req->sconn->ev_ctx,
114
0
              req, in_fsp,
115
0
              in_data_buffer,
116
0
              in_offset,
117
0
              in_flags);
118
0
  if (subreq == NULL) {
119
0
    return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
120
0
  }
121
0
  tevent_req_set_callback(subreq, smbd_smb2_request_write_done, req);
122
123
0
  return smbd_smb2_request_pending_queue(req, subreq, 500);
124
0
}
125
126
static void smbd_smb2_request_write_done(struct tevent_req *subreq)
127
0
{
128
0
  struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
129
0
          struct smbd_smb2_request);
130
0
  DATA_BLOB outbody;
131
0
  DATA_BLOB outdyn;
132
0
  uint32_t out_count = 0;
133
0
  NTSTATUS status;
134
0
  NTSTATUS error; /* transport error */
135
136
0
  status = smbd_smb2_write_recv(subreq, &out_count);
137
0
  TALLOC_FREE(subreq);
138
0
  if (!NT_STATUS_IS_OK(status)) {
139
0
    error = smbd_smb2_request_error(req, status);
140
0
    if (!NT_STATUS_IS_OK(error)) {
141
0
      smbd_server_connection_terminate(req->xconn,
142
0
               nt_errstr(error));
143
0
      return;
144
0
    }
145
0
    return;
146
0
  }
147
148
0
  outbody = smbd_smb2_generate_outbody(req, 0x10);
149
0
  if (outbody.data == NULL) {
150
0
    error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
151
0
    if (!NT_STATUS_IS_OK(error)) {
152
0
      smbd_server_connection_terminate(req->xconn,
153
0
               nt_errstr(error));
154
0
      return;
155
0
    }
156
0
    return;
157
0
  }
158
159
0
  SSVAL(outbody.data, 0x00, 0x10 + 1); /* struct size */
160
0
  SSVAL(outbody.data, 0x02, 0);    /* reserved */
161
0
  SIVAL(outbody.data, 0x04, out_count);  /* count */
162
0
  SIVAL(outbody.data, 0x08, 0);    /* remaining */
163
0
  SSVAL(outbody.data, 0x0C, 0);    /* write channel info offset */
164
0
  SSVAL(outbody.data, 0x0E, 0);    /* write channel info length */
165
166
0
  outdyn = data_blob_const(NULL, 0);
167
168
0
  error = smbd_smb2_request_done(req, outbody, &outdyn);
169
0
  if (!NT_STATUS_IS_OK(error)) {
170
0
    smbd_server_connection_terminate(req->xconn, nt_errstr(error));
171
0
    return;
172
0
  }
173
0
}
174
175
struct smbd_smb2_write_state {
176
  struct smbd_smb2_request *smb2req;
177
  struct smb_request *smbreq;
178
  files_struct *fsp;
179
  bool write_through;
180
  uint32_t in_length;
181
  off_t in_offset;
182
  uint32_t out_count;
183
};
184
185
static void smbd_smb2_write_pipe_done(struct tevent_req *subreq);
186
187
static NTSTATUS smb2_write_complete_internal(struct tevent_req *req,
188
               ssize_t nwritten, int err,
189
               bool do_sync)
190
0
{
191
0
  NTSTATUS status;
192
0
  struct smbd_smb2_write_state *state = tevent_req_data(req,
193
0
          struct smbd_smb2_write_state);
194
0
  files_struct *fsp = state->fsp;
195
196
0
  if (nwritten == -1) {
197
0
    if (err == EOVERFLOW && fsp_is_alternate_stream(fsp)) {
198
0
      status = NT_STATUS_FILE_SYSTEM_LIMITATION;
199
0
    } else {
200
0
      status = map_nt_error_from_unix(err);
201
0
    }
202
203
0
    DEBUG(2, ("smb2_write failed: %s, file %s, "
204
0
        "length=%lu offset=%lu nwritten=-1: %s\n",
205
0
        fsp_fnum_dbg(fsp),
206
0
        fsp_str_dbg(fsp),
207
0
        (unsigned long)state->in_length,
208
0
        (unsigned long)state->in_offset,
209
0
        nt_errstr(status)));
210
211
0
    return status;
212
0
  }
213
214
0
  DEBUG(3,("smb2: %s, file %s, "
215
0
    "length=%lu offset=%lu wrote=%lu\n",
216
0
    fsp_fnum_dbg(fsp),
217
0
    fsp_str_dbg(fsp),
218
0
    (unsigned long)state->in_length,
219
0
    (unsigned long)state->in_offset,
220
0
    (unsigned long)nwritten));
221
222
0
  if ((nwritten == 0) && (state->in_length != 0)) {
223
0
    DEBUG(5,("smb2: write [%s] disk full\n",
224
0
      fsp_str_dbg(fsp)));
225
0
    return NT_STATUS_DISK_FULL;
226
0
  }
227
228
0
  if (do_sync) {
229
0
    status = sync_file(fsp->conn, fsp, state->write_through);
230
0
    if (!NT_STATUS_IS_OK(status)) {
231
0
      DEBUG(5,("smb2: sync_file for %s returned %s\n",
232
0
         fsp_str_dbg(fsp),
233
0
         nt_errstr(status)));
234
0
      return status;
235
0
    }
236
0
  }
237
238
0
  state->out_count = nwritten;
239
240
0
  return NT_STATUS_OK;
241
0
}
242
243
NTSTATUS smb2_write_complete(struct tevent_req *req, ssize_t nwritten, int err)
244
0
{
245
0
  return smb2_write_complete_internal(req, nwritten, err, true);
246
0
}
247
248
NTSTATUS smb2_write_complete_nosync(struct tevent_req *req, ssize_t nwritten,
249
            int err)
250
0
{
251
0
  return smb2_write_complete_internal(req, nwritten, err, false);
252
0
}
253
254
255
static bool smbd_smb2_write_cancel(struct tevent_req *req)
256
0
{
257
0
  struct smbd_smb2_write_state *state =
258
0
    tevent_req_data(req,
259
0
    struct smbd_smb2_write_state);
260
261
0
  return cancel_smb2_aio(state->smbreq);
262
0
}
263
264
static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
265
                 struct tevent_context *ev,
266
                 struct smbd_smb2_request *smb2req,
267
                 struct files_struct *fsp,
268
                 DATA_BLOB in_data,
269
                 off_t in_offset,
270
                 uint32_t in_flags)
271
0
{
272
0
  NTSTATUS status;
273
0
  struct tevent_req *req = NULL;
274
0
  struct smbd_smb2_write_state *state = NULL;
275
0
  struct smb_request *smbreq = NULL;
276
0
  connection_struct *conn = smb2req->tcon->compat;
277
0
  ssize_t nwritten;
278
0
  struct lock_struct lock;
279
280
0
  req = tevent_req_create(mem_ctx, &state,
281
0
        struct smbd_smb2_write_state);
282
0
  if (req == NULL) {
283
0
    return NULL;
284
0
  }
285
0
  state->smb2req = smb2req;
286
0
  if (smb2req->xconn->protocol >= PROTOCOL_SMB3_02) {
287
0
    if (in_flags & SMB2_WRITEFLAG_WRITE_UNBUFFERED) {
288
0
      state->write_through = true;
289
0
    }
290
0
  }
291
0
  if (in_flags & SMB2_WRITEFLAG_WRITE_THROUGH) {
292
0
    state->write_through = true;
293
0
  }
294
0
  state->in_length = in_data.length;
295
0
  state->in_offset = in_offset;
296
0
  state->out_count = 0;
297
298
0
  DEBUG(10,("smbd_smb2_write: %s - %s\n",
299
0
      fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
300
301
0
  smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
302
0
  if (tevent_req_nomem(smbreq, req)) {
303
0
    return tevent_req_post(req, ev);
304
0
  }
305
0
  state->smbreq = smbreq;
306
307
0
  state->fsp = fsp;
308
309
0
  if (IS_IPC(smbreq->conn)) {
310
0
    struct tevent_req *subreq = NULL;
311
0
                bool ok;
312
313
0
    if (!fsp_is_np(fsp)) {
314
0
      tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
315
0
      return tevent_req_post(req, ev);
316
0
    }
317
318
0
    subreq = np_write_send(state, ev,
319
0
               fsp->fake_file_handle,
320
0
               in_data.data,
321
0
               in_data.length);
322
0
    if (tevent_req_nomem(subreq, req)) {
323
0
      return tevent_req_post(req, ev);
324
0
    }
325
0
    tevent_req_set_callback(subreq,
326
0
          smbd_smb2_write_pipe_done,
327
0
          req);
328
329
    /*
330
     * Make sure we mark the fsp as having outstanding async
331
     * activity so we don't crash on shutdown close.
332
     */
333
334
0
    ok = aio_add_req_to_fsp(fsp, req);
335
0
    if (!ok) {
336
0
      tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
337
0
      return tevent_req_post(req, ev);
338
0
    }
339
340
0
    return req;
341
0
  }
342
343
0
  status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
344
0
  if (!NT_STATUS_IS_OK(status)) {
345
0
    tevent_req_nterror(req, status);
346
0
    return tevent_req_post(req, ev);
347
0
  }
348
349
0
  if (state->in_offset == VFS_PWRITE_APPEND_OFFSET &&
350
0
      !fsp->fsp_flags.posix_append)
351
0
  {
352
0
    tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
353
0
    return tevent_req_post(req, ev);
354
0
  } else if (fsp->fsp_flags.posix_append &&
355
0
       state->in_offset != VFS_PWRITE_APPEND_OFFSET)
356
0
  {
357
0
    tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
358
0
    return tevent_req_post(req, ev);
359
0
  }
360
361
  /* Try and do an asynchronous write. */
362
0
  status = schedule_aio_smb2_write(conn,
363
0
          smbreq,
364
0
          fsp,
365
0
          in_offset,
366
0
          in_data,
367
0
          state->write_through);
368
369
0
  if (NT_STATUS_IS_OK(status)) {
370
    /*
371
     * Doing an async write, allow this
372
     * request to be canceled
373
     */
374
0
    tevent_req_set_cancel_fn(req, smbd_smb2_write_cancel);
375
0
    return req;
376
0
  }
377
378
0
  if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
379
    /* Real error in setting up aio. Fail. */
380
0
    tevent_req_nterror(req, status);
381
0
    return tevent_req_post(req, ev);
382
0
  }
383
384
  /* Fallback to synchronous. */
385
0
  init_strict_lock_struct(fsp,
386
0
        fsp->op->global->open_persistent_id,
387
0
        in_offset,
388
0
        in_data.length,
389
0
        WRITE_LOCK,
390
0
        &lock);
391
392
0
  if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
393
0
    tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
394
0
    return tevent_req_post(req, ev);
395
0
  }
396
397
  /*
398
   * Note: in_data.data is NULL for the recvfile case.
399
   */
400
0
  nwritten = write_file(smbreq, fsp,
401
0
            (const char *)in_data.data,
402
0
            in_offset,
403
0
            in_data.length);
404
405
0
  status = smb2_write_complete(req, nwritten, errno);
406
407
0
  DEBUG(10,("smb2: write on "
408
0
    "file %s, offset %.0f, requested %u, written = %u\n",
409
0
    fsp_str_dbg(fsp),
410
0
    (double)in_offset,
411
0
    (unsigned int)in_data.length,
412
0
    (unsigned int)nwritten ));
413
414
0
  if (!NT_STATUS_IS_OK(status)) {
415
0
    tevent_req_nterror(req, status);
416
0
  } else {
417
    /* Success. */
418
0
    tevent_req_done(req);
419
0
  }
420
421
0
  return tevent_req_post(req, ev);
422
0
}
423
424
static void smbd_smb2_write_pipe_done(struct tevent_req *subreq)
425
0
{
426
0
  struct tevent_req *req = tevent_req_callback_data(subreq,
427
0
         struct tevent_req);
428
0
  struct smbd_smb2_write_state *state = tevent_req_data(req,
429
0
                struct smbd_smb2_write_state);
430
0
  NTSTATUS status;
431
0
  ssize_t nwritten = -1;
432
433
0
  status = np_write_recv(subreq, &nwritten);
434
0
  TALLOC_FREE(subreq);
435
0
  if (!NT_STATUS_IS_OK(status)) {
436
0
    NTSTATUS old = status;
437
0
    status = nt_status_np_pipe(old);
438
0
    tevent_req_nterror(req, status);
439
0
    return;
440
0
  }
441
442
0
  if ((nwritten == 0 && state->in_length != 0) || (nwritten < 0)) {
443
0
    tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
444
0
    return;
445
0
  }
446
447
0
  state->out_count = nwritten;
448
449
0
  tevent_req_done(req);
450
0
}
451
452
static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
453
             uint32_t *out_count)
454
0
{
455
0
  NTSTATUS status;
456
0
  struct smbd_smb2_write_state *state = tevent_req_data(req,
457
0
                struct smbd_smb2_write_state);
458
459
0
  if (tevent_req_is_nterror(req, &status)) {
460
0
    tevent_req_received(req);
461
0
    return status;
462
0
  }
463
464
0
  *out_count = state->out_count;
465
466
0
  tevent_req_received(req);
467
0
  return NT_STATUS_OK;
468
0
}