/* * smb_handle_write_raw * * Called from smb_session_daemon() when the SMB command is SMB_COM_WRITE_RAW. * Dispatches the command to the worker thread and waits until the worker * has completed processing the command. * * Returns 0 for success, non-zero for failure */ int smb_handle_write_raw(smb_session_t *session, smb_request_t *sr) { int drop_reason = 0; /* * Set flag to indicate that we are waiting for raw data. The * worker thread will actually retrieve the raw data directly * from the socket. This should be the only case when a worker * thread reads from the session socket. When the data is read * the worker will clear the flag. */ smb_rwx_rwenter(&session->s_lock, RW_WRITER); switch (session->s_state) { case SMB_SESSION_STATE_NEGOTIATED: case SMB_SESSION_STATE_OPLOCK_BREAKING: session->s_state = SMB_SESSION_STATE_WRITE_RAW_ACTIVE; smb_rwx_rwexit(&session->s_lock); smb_srqueue_waitq_enter(session->s_srqueue); sr->sr_state = SMB_REQ_STATE_SUBMITTED; (void) taskq_dispatch(session->s_server->sv_worker_pool, smb_session_worker, sr, TQ_SLEEP); smb_rwx_rwenter(&session->s_lock, RW_READER); while (session->s_state == SMB_SESSION_STATE_WRITE_RAW_ACTIVE) { (void) smb_rwx_rwwait(&session->s_lock, -1); } drop_reason = session->s_write_raw_status; break; default: drop_reason = 21; break; } smb_rwx_rwexit(&session->s_lock); return (drop_reason); }
smb_sdrc_t smb_pre_write_raw(smb_request_t *sr) { smb_rw_param_t *param; uint32_t off_low; uint32_t timeout; uint32_t off_high; uint16_t datalen; uint16_t total; int rc; param = smb_srm_zalloc(sr, sizeof (smb_rw_param_t)); sr->arg.rw = param; param->rw_magic = SMB_RW_MAGIC; if (sr->smb_wct == 12) { rc = smbsr_decode_vwv(sr, "ww2.llw4.ww", &sr->smb_fid, &total, &off_low, &timeout, ¶m->rw_mode, &datalen, ¶m->rw_dsoff); param->rw_offset = (uint64_t)off_low; param->rw_dsoff -= 59; } else { rc = smbsr_decode_vwv(sr, "ww2.llw4.wwl", &sr->smb_fid, &total, &off_low, &timeout, ¶m->rw_mode, &datalen, ¶m->rw_dsoff, &off_high); param->rw_offset = ((uint64_t)off_high << 32) | off_low; param->rw_dsoff -= 63; } param->rw_count = (uint32_t)datalen; param->rw_total = (uint32_t)total; param->rw_vdb.vdb_uio.uio_loffset = (offset_t)param->rw_offset; DTRACE_SMB_2(op__WriteRaw__start, smb_request_t *, sr, smb_rw_param_t *, sr->arg.rw); smb_rwx_rwenter(&sr->session->s_lock, RW_WRITER); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); }
/* * smb2_sign_begin * Handles both SMB2 & SMB3 * * Get the mechanism info. * Intializes MAC key based on the user session key and store it in * the signing structure. This begins signing on this session. */ int smb2_sign_begin(smb_request_t *sr, smb_token_t *token) { smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; struct smb_key *sign_key = &u->u_sign_key; int (*get_mech)(smb_sign_mech_t *); int (*sign_calc)(smb_request_t *, struct mbuf_chain *, uint8_t *); smb_sign_mech_t *mech; int rc; /* * We should normally have a session key here because * our caller filters out Anonymous and Guest logons. * However, buggy clients could get us here without a * session key, in which case we'll fail later when a * request that requires signing can't be checked. */ if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0) return (0); if (s->dialect >= SMB_VERS_3_0) { get_mech = smb3_cmac_getmech; sign_calc = smb3_sign_calc; } else { get_mech = smb2_hmac_getmech; sign_calc = smb2_sign_calc; } /* * Session-level initialization (once per session) * Get mech handle, sign_calc function */ smb_rwx_rwenter(&s->s_lock, RW_WRITER); if (s->sign_mech == NULL) { mech = kmem_zalloc(sizeof (*mech), KM_SLEEP); rc = get_mech(mech); if (rc != 0) { kmem_free(mech, sizeof (*mech)); smb_rwx_rwexit(&s->s_lock); return (rc); } s->sign_mech = mech; s->sign_calc = sign_calc; s->sign_fini = smb2_sign_fini; } smb_rwx_rwexit(&s->s_lock); /* * Compute and store the signing key, which lives in * the user structure. */ sign_key->len = SMB2_SIG_SIZE; if (s->dialect >= SMB_VERS_3_0) { /* * For SMB3, the signing key is a "KDF" hash of the * sesion key. */ if (smb3_do_kdf(sign_key->key, &token->tkn_ssnkey) != 0) return (-1); } else { /* * For SMB2, the signing key is just the first 16 bytes * of the session key (truncated or padded with zeros). * [MS-SMB2] 3.2.5.3.1 */ bcopy(token->tkn_ssnkey.val, sign_key->key, MIN(token->tkn_ssnkey.len, sign_key->len)); } mutex_enter(&u->u_mutex); if (s->secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) u->u_sign_flags |= SMB_SIGNING_ENABLED; if (s->secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) u->u_sign_flags |= SMB_SIGNING_ENABLED | SMB_SIGNING_CHECK; mutex_exit(&u->u_mutex); /* * If we just turned on signing, the current request * (an SMB2 session setup) will have come in without * SMB2_FLAGS_SIGNED (and not signed) but the response * is is supposed to be signed. [MS-SMB2] 3.3.5.5 */ if (u->u_sign_flags & SMB_SIGNING_ENABLED) sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED; return (0); }
/* * smb2sr_work * * This function processes each SMB command in the current request * (which may be a compound request) building a reply containing * SMB reply messages, one-to-one with the SMB commands. Some SMB * commands (change notify, blocking pipe read) may require both an * "interim response" and a later "async response" at completion. * In such cases, we'll encode the interim response in the reply * compound we're building, and put the (now async) command on a * list of commands that need further processing. After we've * finished processing the commands in this compound and building * the compound reply, we'll send the compound reply, and finally * process the list of async commands. * * As we work our way through the compound request and reply, * we need to keep track of the bounds of the current request * and reply. For the request, this uses an MBC_SHADOW_CHAIN * that begins at smb2_cmd_hdr. The reply is appended to the * sr->reply chain starting at smb2_reply_hdr. * * This function must always free the smb request. */ void smb2sr_work(struct smb_request *sr) { smb_session_t *session; uint32_t msg_len; int rc; boolean_t disconnect = B_FALSE; session = sr->session; ASSERT(sr->tid_tree == 0); ASSERT(sr->uid_user == 0); ASSERT(sr->fid_ofile == 0); sr->smb_fid = (uint16_t)-1; /* temporary until we identify a user */ sr->user_cr = zone_kcred(); mutex_enter(&sr->sr_mutex); switch (sr->sr_state) { case SMB_REQ_STATE_SUBMITTED: case SMB_REQ_STATE_CLEANED_UP: sr->sr_state = SMB_REQ_STATE_ACTIVE; break; default: ASSERT(0); /* FALLTHROUGH */ case SMB_REQ_STATE_CANCELED: goto complete_unlock_free; } mutex_exit(&sr->sr_mutex); cmd_start: /* * Reserve space for the reply header, and save the offset. * The reply header will be overwritten later. */ sr->smb2_reply_hdr = sr->reply.chain_offset; (void) smb_mbc_encodef(&sr->reply, "#.", SMB2_HDR_SIZE); /* * Decode the request header * * Most problems with decoding will result in the error * STATUS_INVALID_PARAMETER. If the decoding problem * prevents continuing, we'll close the connection. * [MS-SMB2] 3.3.5.2.6 Handling Incorrectly Formatted... */ sr->smb2_status = 0; sr->smb2_cmd_hdr = sr->command.chain_offset; if ((rc = smb2_decode_header(sr)) != 0) { cmn_err(CE_WARN, "clnt %s bad SMB2 header", session->ip_addr_str); disconnect = B_TRUE; goto cleanup; } /* * Figure out the length of data following the SMB2 header. * It ends at either the next SMB2 header if there is one * (smb2_next_command != 0) or at the end of the message. */ if (sr->smb2_next_command != 0) { /* [MS-SMB2] says this is 8-byte aligned */ msg_len = sr->smb2_next_command; if ((msg_len & 7) != 0 || (msg_len < SMB2_HDR_SIZE) || ((sr->smb2_cmd_hdr + msg_len) > sr->command.max_bytes)) { cmn_err(CE_WARN, "clnt %s bad SMB2 next cmd", session->ip_addr_str); disconnect = B_TRUE; goto cleanup; } } else { msg_len = sr->command.max_bytes - sr->smb2_cmd_hdr; } /* * Setup a shadow chain for this SMB2 command, starting * with the header and ending at either the next command * or the end of the message. Note that we've already * decoded the header, so chain_offset is now positioned * at the end of the header. The signing check needs the * entire SMB2 command, so we'll shadow starting at the * smb2_cmd_hdr offset. After the signing check, we'll * move chain_offset up to the end of the header. */ (void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command, sr->smb2_cmd_hdr, msg_len); /* * Verify SMB signature if signing is enabled and active now. * [MS-SMB2] 3.3.5.2.4 Verifying the Signature */ if ((sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) != 0) { rc = smb2_sign_check_request(sr); if (rc != 0) { DTRACE_PROBE1(smb2__sign__check, smb_request_t, sr); if (session->signing.flags & SMB_SIGNING_CHECK) { smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED); goto cmd_finish; } } } /* * Now that the signing check is done with smb_data, * advance past the SMB2 header we decoded above. * This leaves sr->smb_data correctly positioned * for command-specific decoding in the dispatch * function called next. */ sr->smb_data.chain_offset = sr->smb2_cmd_hdr + SMB2_HDR_SIZE; /* * Default credit response. Command handler may modify. */ sr->smb2_credit_response = sr->smb2_credit_request; /* * Common dispatch (for sync & async) */ rc = smb2sr_dispatch(sr, NULL); switch (rc) { case SDRC_SUCCESS: break; default: /* * SMB2 does not use the other dispatch return codes. * If we see something else, log an event so we'll * know something is returning bogus status codes. * If you see these in the log, use dtrace to find * the code returning something else. */ #ifdef DEBUG cmn_err(CE_NOTE, "smb2sr_dispatch -> 0x%x", rc); #endif /* FALLTHROUGH */ case SDRC_ERROR: if (sr->smb2_status == 0) sr->smb2_status = NT_STATUS_INTERNAL_ERROR; break; case SDRC_DROP_VC: disconnect = B_TRUE; goto cleanup; } /* * If there's a next command, figure out where it starts, * and fill in the next command offset for the reply. * Note: We sanity checked smb2_next_command above * (the offset to the next command). Similarly set * smb2_next_reply as the offset to the next reply. */ cmd_finish: if (sr->smb2_next_command != 0) { sr->command.chain_offset = sr->smb2_cmd_hdr + sr->smb2_next_command; sr->smb2_next_reply = sr->reply.chain_offset - sr->smb2_reply_hdr; } else { sr->smb2_next_reply = 0; } /* * Overwrite the SMB2 header for the response of * this command (possibly part of a compound). */ sr->smb2_hdr_flags |= SMB2_FLAGS_SERVER_TO_REDIR; (void) smb2_encode_header(sr, B_TRUE); if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) smb2_sign_reply(sr); if (sr->smb2_next_command != 0) goto cmd_start; /* * We've done all the commands in this compound. * Send it out. */ smb2_send_reply(sr); /* * If any of the requests "went async", process those now. */ if (sr->sr_async_req != NULL) { smb2sr_do_async(sr); } cleanup: if (disconnect) { smb_rwx_rwenter(&session->s_lock, RW_WRITER); switch (session->s_state) { case SMB_SESSION_STATE_DISCONNECTED: case SMB_SESSION_STATE_TERMINATED: break; default: smb_soshutdown(session->sock); session->s_state = SMB_SESSION_STATE_DISCONNECTED; break; } smb_rwx_rwexit(&session->s_lock); } mutex_enter(&sr->sr_mutex); complete_unlock_free: sr->sr_state = SMB_REQ_STATE_COMPLETED; mutex_exit(&sr->sr_mutex); smb_request_free(sr); }