/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* * Dispatch function for SMB2_CREATE * [MS-SMB2] 2.2.13 */ #include #include /* * Some flags used locally to keep track of which Create Context * names have been provided and/or requested. */ #define CCTX_EA_BUFFER 1 #define CCTX_SD_BUFFER 2 #define CCTX_DH_REQUEST 4 #define CCTX_DH_RECONNECT 8 #define CCTX_ALLOCATION_SIZE 0x10 #define CCTX_QUERY_MAX_ACCESS 0x20 #define CCTX_TIMEWARP_TOKEN 0x40 #define CCTX_QUERY_ON_DISK_ID 0x80 #define CCTX_REQUEST_LEASE 0x100 #define CCTX_AAPL_EXT 0x200 typedef struct smb2_create_ctx_elem { uint32_t cce_len; mbuf_chain_t cce_mbc; } smb2_create_ctx_elem_t; typedef struct smb2_create_ctx { mbuf_chain_t cc_in_mbc; uint_t cc_in_flags; /* CCTX_... */ uint_t cc_out_flags; /* CCTX_... */ /* Elements we may see in the request. */ smb2_create_ctx_elem_t cc_in_ext_attr; smb2_create_ctx_elem_t cc_in_sec_desc; smb2_create_ctx_elem_t cc_in_dh_request; smb2_create_ctx_elem_t cc_in_dh_reconnect; smb2_create_ctx_elem_t cc_in_alloc_size; smb2_create_ctx_elem_t cc_in_time_warp; smb2_create_ctx_elem_t cc_in_req_lease; smb2_create_ctx_elem_t cc_in_aapl; /* Elements we my place in the response */ smb2_create_ctx_elem_t cc_out_max_access; smb2_create_ctx_elem_t cc_out_file_id; smb2_create_ctx_elem_t cc_out_aapl; } smb2_create_ctx_t; static uint32_t smb2_decode_create_ctx( smb_request_t *, smb2_create_ctx_t *); static uint32_t smb2_encode_create_ctx( smb_request_t *, smb2_create_ctx_t *); static int smb2_encode_create_ctx_elem( mbuf_chain_t *, smb2_create_ctx_elem_t *, uint32_t); static void smb2_free_create_ctx(smb2_create_ctx_t *); smb_sdrc_t smb2_create(smb_request_t *sr) { smb_attr_t *attr; smb2_create_ctx_elem_t *cce; smb2_create_ctx_t cctx; smb_arg_open_t *op = &sr->arg.open; smb_ofile_t *of = NULL; uint16_t StructSize; uint8_t SecurityFlags; uint8_t OplockLevel; uint32_t ImpersonationLevel; uint64_t SmbCreateFlags; uint64_t Reserved4; uint16_t NameOffset; uint16_t NameLength; uint32_t CreateCtxOffset; uint32_t CreateCtxLength; smb2fid_t smb2fid; uint32_t status; int skip; int rc = 0; bzero(&cctx, sizeof (cctx)); op->create_ctx = &cctx; /* for debugging */ /* * Paranoia. This will set sr->fid_ofile, so * if we already have one, release it now. */ if (sr->fid_ofile != NULL) { smb_ofile_request_complete(sr->fid_ofile); smb_ofile_release(sr->fid_ofile); sr->fid_ofile = NULL; } /* * Decode the SMB2 Create request * * Most decode errors return SDRC_ERROR, but * for some we give a more specific error. * * In the "decode section" (starts here) any * errors should either return SDRC_ERROR, or * if any cleanup is needed, goto errout. */ rc = smb_mbc_decodef( &sr->smb_data, "wbblqqlllllwwll", &StructSize, /* w */ &SecurityFlags, /* b */ &op->op_oplock_level, /* b */ &ImpersonationLevel, /* l */ &SmbCreateFlags, /* q */ &Reserved4, /* q */ &op->desired_access, /* l */ &op->dattr, /* l */ &op->share_access, /* l */ &op->create_disposition, /* l */ &op->create_options, /* l */ &NameOffset, /* w */ &NameLength, /* w */ &CreateCtxOffset, /* l */ &CreateCtxLength); /* l */ if (rc != 0 || StructSize != 57) return (SDRC_ERROR); /* * We're normally positioned at the path name now, * but there could be some padding before it. */ skip = (NameOffset + sr->smb2_cmd_hdr) - sr->smb_data.chain_offset; if (skip < 0) return (SDRC_ERROR); if (skip > 0) (void) smb_mbc_decodef(&sr->smb_data, "#.", skip); /* * Get the path name * * Name too long is not technically a decode error, * but it's very rare, so we'll just skip the * dtrace probes for this error case. */ if (NameLength >= SMB_MAXPATHLEN) { status = NT_STATUS_OBJECT_PATH_INVALID; goto errout; } if (NameLength == 0) { op->fqi.fq_path.pn_path = "\\"; } else { rc = smb_mbc_decodef(&sr->smb_data, "%#U", sr, NameLength, &op->fqi.fq_path.pn_path); if (rc) { status = NT_STATUS_OBJECT_PATH_INVALID; goto errout; } } op->fqi.fq_dnode = sr->tid_tree->t_snode; /* * If there is a "Create Context" payload, decode it. * This may carry things like a security descriptor, * extended attributes, etc. to be used in create. * * The create ctx buffer must start after the headers * and file name, and must be 8-byte aligned. */ if (CreateCtxLength != 0) { if ((CreateCtxOffset & 7) != 0 || (CreateCtxOffset + sr->smb2_cmd_hdr) < sr->smb_data.chain_offset) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } rc = MBC_SHADOW_CHAIN(&cctx.cc_in_mbc, &sr->smb_data, sr->smb2_cmd_hdr + CreateCtxOffset, CreateCtxLength); if (rc) { status = NT_STATUS_INVALID_PARAMETER; goto errout; } status = smb2_decode_create_ctx(sr, &cctx); if (status) goto errout; } /* * Everything is decoded into some internal form, so * in this probe one can look at sr->arg.open etc. * * This marks the end of the "decode" section and the * beginning of the "body" section. Any errors in * this section should use: goto cmd_done (which is * just before the dtrace "done" probe). */ DTRACE_SMB2_START(op__Create, smb_request_t *, sr); /* arg.open */ /* * Process the incoming create contexts (already decoded), * that need action before the open, starting with the * Durable Handle ones, which may override others. */ /* * Validate the requested oplock level. * Convert the SMB2 oplock level into SMB1 form. */ switch (op->op_oplock_level) { case SMB2_OPLOCK_LEVEL_NONE: op->op_oplock_level = SMB_OPLOCK_NONE; break; case SMB2_OPLOCK_LEVEL_II: op->op_oplock_level = SMB_OPLOCK_LEVEL_II; break; case SMB2_OPLOCK_LEVEL_EXCLUSIVE: op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE; break; case SMB2_OPLOCK_LEVEL_BATCH: op->op_oplock_level = SMB_OPLOCK_BATCH; break; case SMB2_OPLOCK_LEVEL_LEASE: /* not yet */ default: /* Unknown SMB2 oplock level. */ status = NT_STATUS_INVALID_PARAMETER; goto cmd_done; } op->op_oplock_levelII = B_TRUE; /* * Only disk trees get oplocks or leases. */ if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) { op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE; } if (cctx.cc_in_flags & CCTX_EA_BUFFER) { status = NT_STATUS_EAS_NOT_SUPPORTED; goto cmd_done; } /* * ImpersonationLevel (spec. says validate + ignore) * SmbCreateFlags (spec. says ignore) */ if ((op->create_options & FILE_DELETE_ON_CLOSE) && !(op->desired_access & DELETE)) { status = NT_STATUS_INVALID_PARAMETER; goto cmd_done; } if (op->dattr & FILE_FLAG_WRITE_THROUGH) op->create_options |= FILE_WRITE_THROUGH; if (op->dattr & FILE_FLAG_DELETE_ON_CLOSE) op->create_options |= FILE_DELETE_ON_CLOSE; if (op->dattr & FILE_FLAG_BACKUP_SEMANTICS) op->create_options |= FILE_OPEN_FOR_BACKUP_INTENT; if (op->create_options & FILE_OPEN_FOR_BACKUP_INTENT) sr->user_cr = smb_user_getprivcred(sr->uid_user); if (op->create_disposition > FILE_MAXIMUM_DISPOSITION) { status = NT_STATUS_INVALID_PARAMETER; goto cmd_done; } /* * The real open call. Note: this gets attributes into * op->fqi.fq_fattr (SMB_AT_ALL). We need those below. * When of != NULL, goto errout closes it. */ status = smb_common_open(sr); if (status != NT_STATUS_SUCCESS) goto cmd_done; of = sr->fid_ofile; /* * NB: after the above smb_common_open() success, * we have a handle allocated (sr->fid_ofile). * If we don't return success, we must close it. * * Using sr->smb_fid as the file handle for now, * though it could later be something larger, * (16 bytes) similar to an NFSv4 open handle. */ smb2fid.persistent = 0; smb2fid.temporal = sr->smb_fid; switch (sr->tid_tree->t_res_type & STYPE_MASK) { case STYPE_DISKTREE: case STYPE_PRINTQ: if (op->create_options & FILE_DELETE_ON_CLOSE) smb_ofile_set_delete_on_close(of); break; } /* * Process any outgoing create contexts that need work * after the open succeeds. Encode happens later. */ if (cctx.cc_in_flags & CCTX_QUERY_MAX_ACCESS) { op->maximum_access = 0; if (of->f_node != NULL) { smb_fsop_eaccess(sr, of->f_cr, of->f_node, &op->maximum_access); } op->maximum_access |= of->f_granted_access; cctx.cc_out_flags |= CCTX_QUERY_MAX_ACCESS; } if ((cctx.cc_in_flags & CCTX_QUERY_ON_DISK_ID) != 0 && of->f_node != NULL) { op->op_fsid = SMB_NODE_FSID(of->f_node); cctx.cc_out_flags |= CCTX_QUERY_ON_DISK_ID; } if ((cctx.cc_in_flags & CCTX_AAPL_EXT) != 0) { cce = &cctx.cc_out_aapl; /* * smb2_aapl_crctx has a variable response depending on * what the incoming context looks like, so it does all * the work of building cc_out_aapl, including setting * cce_len, cce_mbc.max_bytes, and smb_mbc_encode. * If we see errors getting this, simply omit it from * the collection of returned create contexts. */ status = smb2_aapl_crctx(sr, &cctx.cc_in_aapl.cce_mbc, &cce->cce_mbc); if (status == 0) { cce->cce_len = cce->cce_mbc.chain_offset; cctx.cc_out_flags |= CCTX_AAPL_EXT; } status = 0; } /* * This marks the end of the "body" section and the * beginning of the "encode" section. Any errors * encoding the response should use: goto errout */ cmd_done: /* Want status visible in the done probe. */ sr->smb2_status = status; DTRACE_SMB2_DONE(op__Create, smb_request_t *, sr); if (status != NT_STATUS_SUCCESS) goto errout; /* * Encode all the create contexts to return. */ if (cctx.cc_out_flags) { sr->raw_data.max_bytes = smb2_max_trans; status = smb2_encode_create_ctx(sr, &cctx); if (status) goto errout; } /* * Convert the negotiated Oplock level back into * SMB2 encoding form. */ switch (op->op_oplock_level) { default: case SMB_OPLOCK_NONE: OplockLevel = SMB2_OPLOCK_LEVEL_NONE; break; case SMB_OPLOCK_LEVEL_II: OplockLevel = SMB2_OPLOCK_LEVEL_II; break; case SMB_OPLOCK_EXCLUSIVE: OplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE; break; case SMB_OPLOCK_BATCH: OplockLevel = SMB2_OPLOCK_LEVEL_BATCH; break; } /* * Encode the SMB2 Create reply */ attr = &op->fqi.fq_fattr; rc = smb_mbc_encodef( &sr->reply, "wb.lTTTTqqllqqll", 89, /* StructSize */ /* w */ OplockLevel, /* b */ op->action_taken, /* l */ &attr->sa_crtime, /* T */ &attr->sa_vattr.va_atime, /* T */ &attr->sa_vattr.va_mtime, /* T */ &attr->sa_vattr.va_ctime, /* T */ attr->sa_allocsz, /* q */ attr->sa_vattr.va_size, /* q */ attr->sa_dosattr, /* l */ 0, /* reserved2 */ /* l */ smb2fid.persistent, /* q */ smb2fid.temporal, /* q */ 0, /* CreateCtxOffset l */ 0); /* CreateCtxLength l */ if (rc != 0) { status = NT_STATUS_UNSUCCESSFUL; goto errout; } CreateCtxOffset = sr->reply.chain_offset - sr->smb2_reply_hdr; CreateCtxLength = MBC_LENGTH(&sr->raw_data); if (CreateCtxLength != 0) { /* * Overwrite CreateCtxOffset, CreateCtxLength, pad */ sr->reply.chain_offset -= 8; rc = smb_mbc_encodef( &sr->reply, "ll#C", CreateCtxOffset, /* l */ CreateCtxLength, /* l */ CreateCtxLength, /* # */ &sr->raw_data); /* C */ if (rc != 0) { status = NT_STATUS_UNSUCCESSFUL; goto errout; } } else { (void) smb_mbc_encodef(&sr->reply, "."); } if (status != 0) { errout: if (of != NULL) smb_ofile_close(of, 0); smb2sr_put_error(sr, status); } if (op->sd != NULL) { smb_sd_term(op->sd); kmem_free(op->sd, sizeof (*op->sd)); } if (cctx.cc_out_flags) smb2_free_create_ctx(&cctx); return (SDRC_SUCCESS); } /* * Decode an SMB2 Create Context buffer into our internal form. * Avoid policy decisions about what's supported here, just decode. */ static uint32_t smb2_decode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc) { smb_arg_open_t *op = &sr->arg.open; smb2_create_ctx_elem_t *cce; mbuf_chain_t *in_mbc = &cc->cc_in_mbc; mbuf_chain_t name_mbc; union { uint32_t i; char ch[4]; } cc_name; uint32_t status; int32_t next_off; uint32_t data_len; uint16_t data_off; uint16_t name_off; uint16_t name_len; int top_offset; int rc; /* * Any break from the loop below before we've decoded * the entire create context means it was malformatted, * so we should return INVALID_PARAMETER. */ status = NT_STATUS_INVALID_PARAMETER; for (;;) { cce = NULL; top_offset = in_mbc->chain_offset; rc = smb_mbc_decodef( in_mbc, "lww..wl", &next_off, /* l */ &name_off, /* w */ &name_len, /* w */ /* reserved .. */ &data_off, /* w */ &data_len); /* l */ if (rc) break; /* * The Create Context "name", per [MS-SMB] 2.2.13.2 * They're defined as network-order integers for our * switch below. We don't have routines to decode * native order, so read as char[4] then ntohl. * NB: in SMB3, some of these are 8 bytes. */ if ((top_offset + name_off) < in_mbc->chain_offset) break; rc = MBC_SHADOW_CHAIN(&name_mbc, in_mbc, top_offset + name_off, name_len); if (rc) break; rc = smb_mbc_decodef(&name_mbc, "4c", &cc_name); if (rc) break; cc_name.i = ntohl(cc_name.i); switch (cc_name.i) { case SMB2_CREATE_EA_BUFFER: /* ("ExtA") */ cc->cc_in_flags |= CCTX_EA_BUFFER; cce = &cc->cc_in_ext_attr; break; case SMB2_CREATE_SD_BUFFER: /* ("SecD") */ cc->cc_in_flags |= CCTX_SD_BUFFER; cce = &cc->cc_in_sec_desc; break; case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */ cc->cc_in_flags |= CCTX_DH_REQUEST; cce = &cc->cc_in_dh_request; break; case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */ cc->cc_in_flags |= CCTX_DH_RECONNECT; cce = &cc->cc_in_dh_reconnect; break; case SMB2_CREATE_ALLOCATION_SIZE: /* ("AISi") */ cc->cc_in_flags |= CCTX_ALLOCATION_SIZE; cce = &cc->cc_in_alloc_size; break; case SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ: /* ("MxAc") */ cc->cc_in_flags |= CCTX_QUERY_MAX_ACCESS; /* no input data for this */ break; case SMB2_CREATE_TIMEWARP_TOKEN: /* ("TWrp") */ cc->cc_in_flags |= CCTX_TIMEWARP_TOKEN; cce = &cc->cc_in_time_warp; break; case SMB2_CREATE_QUERY_ON_DISK_ID: /* ("QFid") */ cc->cc_in_flags |= CCTX_QUERY_ON_DISK_ID; /* no input data for this */ break; case SMB2_CREATE_REQUEST_LEASE: /* ("RqLs") */ cc->cc_in_flags |= CCTX_REQUEST_LEASE; cce = &cc->cc_in_req_lease; break; case SMB2_CREATE_CTX_AAPL: /* ("AAPL") */ cc->cc_in_flags |= CCTX_AAPL_EXT; cce = &cc->cc_in_aapl; break; default: /* * Unknown create context values are normal, and * should be ignored. However, in debug mode, * let's log them so we know which ones we're * not handling (and may want to add). */ #ifdef DEBUG cmn_err(CE_NOTE, "unknown create context ID 0x%x", cc_name.i); #endif cce = NULL; break; } if (cce == NULL || data_len == 0) goto next_cc; if ((data_off & 7) != 0) break; if ((top_offset + data_off) < in_mbc->chain_offset) break; rc = MBC_SHADOW_CHAIN(&cce->cce_mbc, in_mbc, top_offset + data_off, data_len); if (rc) break; cce->cce_len = data_len; /* * Additonal decoding for some create contexts. */ switch (cc_name.i) { uint64_t nttime; case SMB2_CREATE_SD_BUFFER: /* ("SecD") */ op->sd = kmem_alloc(sizeof (smb_sd_t), KM_SLEEP); if (smb_decode_sd(&cce->cce_mbc, op->sd) != 0) goto errout; break; case SMB2_CREATE_ALLOCATION_SIZE: /* ("AISi") */ rc = smb_mbc_decodef(&cce->cce_mbc, "q", &op->dsize); if (rc != 0) goto errout; break; case SMB2_CREATE_TIMEWARP_TOKEN: /* ("TWrp") */ /* * Support for opening "Previous Versions". * [MS-SMB2] 2.2.13.2.7 Data is an NT time. */ rc = smb_mbc_decodef(&cce->cce_mbc, "q", &nttime); if (rc != 0) goto errout; smb_time_nt_to_unix(nttime, &op->timewarp); op->create_timewarp = B_TRUE; break; } next_cc: if (next_off == 0) { /* Normal loop termination */ status = 0; break; } if ((next_off & 7) != 0) break; if ((top_offset + next_off) < in_mbc->chain_offset) break; if ((top_offset + next_off) > in_mbc->max_bytes) break; in_mbc->chain_offset = top_offset + next_off; } errout: return (status); } /* * Encode an SMB2 Create Context buffer from our internal form. * * Build the Create Context to return; first the * per-element parts, then the aggregated buffer. * * No response for these: * CCTX_EA_BUFFER * CCTX_SD_BUFFER * CCTX_ALLOCATION_SIZE * CCTX_TIMEWARP_TOKEN * * Remember to add code sections to smb2_free_create_ctx() * for each section here that encodes a context element. */ static uint32_t smb2_encode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc) { smb_arg_open_t *op = &sr->arg.open; smb2_create_ctx_elem_t *cce; mbuf_chain_t *mbc = &sr->raw_data; int last_top = -1; int rc; if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) { cce = &cc->cc_out_max_access; cce->cce_mbc.max_bytes = cce->cce_len = 8; (void) smb_mbc_encodef(&cce->cce_mbc, "ll", 0, op->maximum_access); last_top = mbc->chain_offset; rc = smb2_encode_create_ctx_elem(mbc, cce, SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ); if (rc) return (NT_STATUS_INTERNAL_ERROR); (void) smb_mbc_poke(mbc, last_top, "l", mbc->chain_offset - last_top); } if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) { cce = &cc->cc_out_file_id; cce->cce_mbc.max_bytes = cce->cce_len = 32; (void) smb_mbc_encodef( &cce->cce_mbc, "qll.15.", op->fileid, /* q */ op->op_fsid.val[0], /* l */ op->op_fsid.val[1]); /* l */ /* reserved (16 bytes) .15. */ last_top = mbc->chain_offset; rc = smb2_encode_create_ctx_elem(mbc, cce, SMB2_CREATE_QUERY_ON_DISK_ID); if (rc) return (NT_STATUS_INTERNAL_ERROR); (void) smb_mbc_poke(mbc, last_top, "l", mbc->chain_offset - last_top); } if (cc->cc_out_flags & CCTX_AAPL_EXT) { cce = &cc->cc_out_aapl; /* cc_out_aapl already encoded */ last_top = mbc->chain_offset; rc = smb2_encode_create_ctx_elem(mbc, cce, SMB2_CREATE_CTX_AAPL); if (rc) return (NT_STATUS_INTERNAL_ERROR); (void) smb_mbc_poke(mbc, last_top, "l", mbc->chain_offset - last_top); } if (last_top >= 0) (void) smb_mbc_poke(mbc, last_top, "l", 0); return (0); } static int smb2_encode_create_ctx_elem(mbuf_chain_t *out_mbc, smb2_create_ctx_elem_t *cce, uint32_t id) { union { uint32_t i; char ch[4]; } cc_name; int rc; /* as above */ cc_name.i = htonl(id); /* * This is the header, per [MS-SMB2] 2.2.13.2 * Sorry about the fixed offsets. We know we'll * layout the data part as [name, payload] and * name is a fixed length, so this easy. * The final layout looks like this: * a: this header (16 bytes) * b: the name (4 bytes, 4 pad) * c: the payload (variable) * * Note that "Next elem." is filled in later. */ rc = smb_mbc_encodef( out_mbc, "lwwwwl", 0, /* Next offset l */ 16, /* NameOffset w */ 4, /* NameLength w */ 0, /* Reserved w */ 24, /* DataOffset w */ cce->cce_len); /* DataLen l */ if (rc) return (rc); /* * Now the "name" and payload. */ rc = smb_mbc_encodef( out_mbc, "4c4.#C", cc_name.ch, /* 4c4. */ cce->cce_len, /* # */ &cce->cce_mbc); /* C */ return (rc); } static void smb2_free_create_ctx(smb2_create_ctx_t *cc) { smb2_create_ctx_elem_t *cce; if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) { cce = &cc->cc_out_max_access; MBC_FLUSH(&cce->cce_mbc); } if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) { cce = &cc->cc_out_file_id; MBC_FLUSH(&cce->cce_mbc); } if (cc->cc_out_flags & CCTX_AAPL_EXT) { cce = &cc->cc_out_aapl; MBC_FLUSH(&cce->cce_mbc); } }