From 8716bb5e03cc4f10e2d4edc704d8defe7e8045f1 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 16 Jul 2015 22:46:05 +0200 Subject: [PATCH 01/40] CVE-2015-5370: dcerpc.idl: add DCERPC_{NCACN_PAYLOAD,FRAG}_MAX_SIZE defines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG: https://bugzilla.samba.org/show_bug.cgi?id=11344 Signed-off-by: Stefan Metzmacher Reviewed-by: Günther Deschner --- librpc/idl/dcerpc.idl | 2 ++ 1 file changed, 2 insertions(+) --- a/librpc/idl/dcerpc.idl +++ b/librpc/idl/dcerpc.idl @@ -475,9 +475,11 @@ interface dcerpc const uint8 DCERPC_PFC_OFFSET = 3; const uint8 DCERPC_DREP_OFFSET = 4; const uint8 DCERPC_FRAG_LEN_OFFSET = 8; + const uint32 DCERPC_FRAG_MAX_SIZE = 5840; const uint8 DCERPC_AUTH_LEN_OFFSET = 10; const uint8 DCERPC_CALL_ID_OFFSET = 12; const uint8 DCERPC_NCACN_PAYLOAD_OFFSET = 16; + const uint32 DCERPC_NCACN_PAYLOAD_MAX_SIZE = 0x400000; /* 4 MByte */ /* little-endian flag */ const uint8 DCERPC_DREP_LE = 0x10; --- a/librpc/rpc/dcerpc_util.c +++ b/librpc/rpc/dcerpc_util.c @@ -92,31 +92,49 @@ uint8_t dcerpc_get_endian_flag(DATA_BLOB * * @return - A NTSTATUS error code. */ -NTSTATUS dcerpc_pull_auth_trailer(struct ncacn_packet *pkt, +NTSTATUS dcerpc_pull_auth_trailer(const struct ncacn_packet *pkt, TALLOC_CTX *mem_ctx, - DATA_BLOB *pkt_trailer, + const DATA_BLOB *pkt_trailer, struct dcerpc_auth *auth, - uint32_t *auth_length, + uint32_t *_auth_length, bool auth_data_only) { struct ndr_pull *ndr; enum ndr_err_code ndr_err; - uint32_t data_and_pad; + uint16_t data_and_pad; + uint16_t auth_length; + uint32_t tmp_length; - data_and_pad = pkt_trailer->length - - (DCERPC_AUTH_TRAILER_LENGTH + pkt->auth_length); + ZERO_STRUCTP(auth); + if (_auth_length != NULL) { + *_auth_length = 0; + } + + /* Paranoia checks for auth_length. The caller should check this... */ + if (pkt->auth_length == 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* Paranoia checks for auth_length. The caller should check this... */ + if (pkt->auth_length > pkt->frag_length) { + return NT_STATUS_INTERNAL_ERROR; + } + tmp_length = DCERPC_NCACN_PAYLOAD_OFFSET; + tmp_length += DCERPC_AUTH_TRAILER_LENGTH; + tmp_length += pkt->auth_length; + if (tmp_length > pkt->frag_length) { + return NT_STATUS_INTERNAL_ERROR; + } + if (pkt_trailer->length > UINT16_MAX) { + return NT_STATUS_INTERNAL_ERROR; + } - /* paranoia check for pad size. This would be caught anyway by - the ndr_pull_advance() a few lines down, but it scared - Jeremy enough for him to call me, so we might as well check - it now, just to prevent someone posting a bogus YouTube - video in the future. - */ - if (data_and_pad > pkt_trailer->length) { - return NT_STATUS_INFO_LENGTH_MISMATCH; + auth_length = DCERPC_AUTH_TRAILER_LENGTH + pkt->auth_length; + if (pkt_trailer->length < auth_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; } - *auth_length = pkt_trailer->length - data_and_pad; + data_and_pad = pkt_trailer->length - auth_length; ndr = ndr_pull_init_blob(pkt_trailer, mem_ctx); if (!ndr) { @@ -136,14 +154,28 @@ NTSTATUS dcerpc_pull_auth_trailer(struct ndr_err = ndr_pull_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS, auth); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(ndr); + ZERO_STRUCTP(auth); return ndr_map_error2ntstatus(ndr_err); } + if (data_and_pad < auth->auth_pad_length) { + DEBUG(1, (__location__ ": ERROR: pad length mismatch. " + "Calculated %u got %u\n", + (unsigned)data_and_pad, + (unsigned)auth->auth_pad_length)); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (auth_data_only && data_and_pad != auth->auth_pad_length) { - DEBUG(1, (__location__ ": WARNING: pad length mismatch. " + DEBUG(1, (__location__ ": ERROR: pad length mismatch. " "Calculated %u got %u\n", (unsigned)data_and_pad, (unsigned)auth->auth_pad_length)); + talloc_free(ndr); + ZERO_STRUCTP(auth); + return NT_STATUS_RPC_PROTOCOL_ERROR; } DEBUG(6,(__location__ ": auth_pad_length %u\n", @@ -152,6 +184,83 @@ NTSTATUS dcerpc_pull_auth_trailer(struct talloc_steal(mem_ctx, auth->credentials.data); talloc_free(ndr); + if (_auth_length != NULL) { + *_auth_length = auth_length; + } + + return NT_STATUS_OK; +} + +/** +* @brief Verify the fields in ncacn_packet header. +* +* @param pkt - The ncacn_packet strcuture +* @param ptype - The expected PDU type +* @param max_auth_info - The maximum size of a possible auth trailer +* @param required_flags - The required flags for the pdu. +* @param optional_flags - The possible optional flags for the pdu. +* +* @return - A NTSTATUS error code. +*/ +NTSTATUS dcerpc_verify_ncacn_packet_header(const struct ncacn_packet *pkt, + enum dcerpc_pkt_type ptype, + size_t max_auth_info, + uint8_t required_flags, + uint8_t optional_flags) +{ + if (pkt->rpc_vers != 5) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->rpc_vers_minor != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->auth_length > pkt->frag_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->ptype != ptype) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (max_auth_info > UINT16_MAX) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (pkt->auth_length > 0) { + size_t max_auth_length; + + if (max_auth_info <= DCERPC_AUTH_TRAILER_LENGTH) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + max_auth_length = max_auth_info - DCERPC_AUTH_TRAILER_LENGTH; + + if (pkt->auth_length > max_auth_length) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + } + + if ((pkt->pfc_flags & required_flags) != required_flags) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->pfc_flags & ~(optional_flags|required_flags)) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + + if (pkt->drep[0] & ~DCERPC_DREP_LE) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[1] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[2] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + if (pkt->drep[3] != 0) { + return NT_STATUS_RPC_PROTOCOL_ERROR; + } + return NT_STATUS_OK; } --- a/librpc/rpc/rpc_common.h +++ b/librpc/rpc/rpc_common.h @@ -158,12 +158,17 @@ uint8_t dcerpc_get_endian_flag(DATA_BLOB * * @return - A NTSTATUS error code. */ -NTSTATUS dcerpc_pull_auth_trailer(struct ncacn_packet *pkt, +NTSTATUS dcerpc_pull_auth_trailer(const struct ncacn_packet *pkt, TALLOC_CTX *mem_ctx, - DATA_BLOB *pkt_trailer, + const DATA_BLOB *pkt_trailer, struct dcerpc_auth *auth, uint32_t *auth_length, bool auth_data_only); +NTSTATUS dcerpc_verify_ncacn_packet_header(const struct ncacn_packet *pkt, + enum dcerpc_pkt_type ptype, + size_t max_auth_info, + uint8_t required_flags, + uint8_t optional_flags); struct tevent_req *dcerpc_read_ncacn_packet_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream); --- a/source3/librpc/rpc/dcerpc_helpers.c +++ b/source3/librpc/rpc/dcerpc_helpers.c @@ -210,47 +210,6 @@ NTSTATUS dcerpc_push_dcerpc_auth(TALLOC_ } /** -* @brief Decodes a dcerpc_auth blob -* -* @param mem_ctx The memory context on which to allocate the packet -* elements -* @param blob The blob of data to decode -* @param r An empty dcerpc_auth structure, must not be NULL -* -* @return a NTSTATUS error code -*/ -NTSTATUS dcerpc_pull_dcerpc_auth(TALLOC_CTX *mem_ctx, - const DATA_BLOB *blob, - struct dcerpc_auth *r, - bool bigendian) -{ - enum ndr_err_code ndr_err; - struct ndr_pull *ndr; - - ndr = ndr_pull_init_blob(blob, mem_ctx); - if (!ndr) { - return NT_STATUS_NO_MEMORY; - } - if (bigendian) { - ndr->flags |= LIBNDR_FLAG_BIGENDIAN; - } - - ndr_err = ndr_pull_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS, r); - - if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { - talloc_free(ndr); - return ndr_map_error2ntstatus(ndr_err); - } - talloc_free(ndr); - - if (DEBUGLEVEL >= 10) { - NDR_PRINT_DEBUG(dcerpc_auth, r); - } - - return NT_STATUS_OK; -} - -/** * @brief Calculate how much data we can in a packet, including calculating * auth token and pad lengths. * @@ -782,7 +741,7 @@ NTSTATUS dcerpc_add_auth_footer(struct p auth->auth_type, auth->auth_level, pad_len, - 1 /* context id. */, + auth->auth_context_id, &auth_blob, &auth_info); if (!NT_STATUS_IS_OK(status)) { @@ -844,19 +803,18 @@ NTSTATUS dcerpc_add_auth_footer(struct p * * @param auth The auth data for the connection * @param pkt The actual ncacn_packet -* @param pkt_trailer The stub_and_verifier part of the packet +* @param pkt_trailer [in][out] The stub_and_verifier part of the packet, +* the auth_trailer and padding will be removed. * @param header_size The header size * @param raw_pkt The whole raw packet data blob -* @param pad_len [out] The padding length used in the packet * * @return A NTSTATUS error code */ NTSTATUS dcerpc_check_auth(struct pipe_auth_data *auth, struct ncacn_packet *pkt, DATA_BLOB *pkt_trailer, - size_t header_size, - DATA_BLOB *raw_pkt, - size_t *pad_len) + uint8_t header_size, + DATA_BLOB *raw_pkt) { struct schannel_state *schannel_auth; struct auth_ntlmssp_state *ntlmssp_ctx; @@ -868,6 +826,14 @@ NTSTATUS dcerpc_check_auth(struct pipe_a DATA_BLOB full_pkt; DATA_BLOB data; + /* + * These check should be done in the caller. + */ + SMB_ASSERT(raw_pkt->length == pkt->frag_length); + SMB_ASSERT(header_size <= pkt->frag_length); + SMB_ASSERT(pkt_trailer->length < pkt->frag_length); + SMB_ASSERT((pkt_trailer->length + header_size) <= pkt->frag_length); + switch (auth->auth_level) { case DCERPC_AUTH_LEVEL_PRIVACY: DEBUG(10, ("Requested Privacy.\n")); @@ -881,7 +847,6 @@ NTSTATUS dcerpc_check_auth(struct pipe_a if (pkt->auth_length != 0) { break; } - *pad_len = 0; return NT_STATUS_OK; case DCERPC_AUTH_LEVEL_NONE: @@ -890,7 +855,6 @@ NTSTATUS dcerpc_check_auth(struct pipe_a "authenticated connection!\n")); return NT_STATUS_INVALID_PARAMETER; } - *pad_len = 0; return NT_STATUS_OK; default: @@ -899,16 +863,8 @@ NTSTATUS dcerpc_check_auth(struct pipe_a return NT_STATUS_INVALID_PARAMETER; } - /* Paranioa checks for auth_length. */ - if (pkt->auth_length > pkt->frag_length) { - return NT_STATUS_INFO_LENGTH_MISMATCH; - } - if (((unsigned int)pkt->auth_length - + DCERPC_AUTH_TRAILER_LENGTH < (unsigned int)pkt->auth_length) || - ((unsigned int)pkt->auth_length - + DCERPC_AUTH_TRAILER_LENGTH < DCERPC_AUTH_TRAILER_LENGTH)) { - /* Integer wrap attempt. */ - return NT_STATUS_INFO_LENGTH_MISMATCH; + if (pkt->auth_length == 0) { + return NT_STATUS_INVALID_PARAMETER; } status = dcerpc_pull_auth_trailer(pkt, pkt, pkt_trailer, @@ -917,10 +873,23 @@ NTSTATUS dcerpc_check_auth(struct pipe_a return status; } + if (auth_info.auth_type != auth->auth_type) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_info.auth_level != auth->auth_level) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_info.auth_context_id != auth->auth_context_id) { + return NT_STATUS_INVALID_PARAMETER; + } + + pkt_trailer->length -= auth_length; data = data_blob_const(raw_pkt->data + header_size, - pkt_trailer->length - auth_length); - full_pkt = data_blob_const(raw_pkt->data, - raw_pkt->length - auth_info.credentials.length); + pkt_trailer->length); + full_pkt = data_blob_const(raw_pkt->data, raw_pkt->length); + full_pkt.length -= auth_info.credentials.length; switch (auth->auth_type) { case DCERPC_AUTH_TYPE_NONE: @@ -996,10 +965,13 @@ NTSTATUS dcerpc_check_auth(struct pipe_a * pkt_trailer actually has a copy of the raw data, and they * are still both used in later calls */ if (auth->auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + if (pkt_trailer->length != data.length) { + return NT_STATUS_INVALID_PARAMETER; + } memcpy(pkt_trailer->data, data.data, data.length); } - *pad_len = auth_info.auth_pad_length; + pkt_trailer->length -= auth_info.auth_pad_length; data_blob_free(&auth_info.credentials); return NT_STATUS_OK; } --- a/source3/rpc_client/cli_pipe.c +++ b/source3/rpc_client/cli_pipe.c @@ -404,9 +404,9 @@ static NTSTATUS cli_pipe_validate_curren DATA_BLOB *rdata, DATA_BLOB *reply_pdu) { - struct dcerpc_response *r; + const struct dcerpc_response *r = NULL; + DATA_BLOB tmp_stub = data_blob_null; NTSTATUS ret = NT_STATUS_OK; - size_t pad_len = 0; /* * Point the return values at the real data including the RPC @@ -414,50 +414,128 @@ static NTSTATUS cli_pipe_validate_curren */ *rdata = *pdu; + if ((pkt->ptype == DCERPC_PKT_BIND_ACK) && + !(pkt->pfc_flags & DCERPC_PFC_FLAG_LAST)) { + /* + * TODO: do we still need this hack which was introduced + * in commit a42afcdcc7ab9aa9ed193ae36d3dbb10843447f0. + * + * I don't even know what AS/U might be... + */ + DEBUG(5, (__location__ ": bug in server (AS/U?), setting " + "fragment first/last ON.\n")); + pkt->pfc_flags |= DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; + } + /* Ensure we have the correct type. */ switch (pkt->ptype) { - case DCERPC_PKT_ALTER_RESP: + case DCERPC_PKT_BIND_NAK: + DEBUG(1, (__location__ ": Bind NACK received from %s!\n", + rpccli_pipe_txt(talloc_tos(), cli))); + + ret = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_BIND_NAK, + 0, /* max_auth_info */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST, + 0); /* optional flags */ + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + return ret; + } + + /* Use this for now... */ + return NT_STATUS_NETWORK_ACCESS_DENIED; + case DCERPC_PKT_BIND_ACK: + ret = dcerpc_verify_ncacn_packet_header(pkt, + expected_pkt_type, + pkt->u.bind_ack.auth_info.length, + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST, + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + return ret; + } - /* Client code never receives this kind of packets */ break; + case DCERPC_PKT_ALTER_RESP: + ret = dcerpc_verify_ncacn_packet_header(pkt, + expected_pkt_type, + pkt->u.alter_resp.auth_info.length, + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST, + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + return ret; + } + + break; case DCERPC_PKT_RESPONSE: r = &pkt->u.response; + ret = dcerpc_verify_ncacn_packet_header(pkt, + expected_pkt_type, + r->stub_and_verifier.length, + 0, /* required_flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + return ret; + } + + tmp_stub.data = r->stub_and_verifier.data; + tmp_stub.length = r->stub_and_verifier.length; + /* Here's where we deal with incoming sign/seal. */ ret = dcerpc_check_auth(cli->auth, pkt, - &r->stub_and_verifier, + &tmp_stub, DCERPC_RESPONSE_LENGTH, - pdu, &pad_len); + pdu); if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); return ret; } - if (pkt->frag_length < DCERPC_RESPONSE_LENGTH + pad_len) { - return NT_STATUS_BUFFER_TOO_SMALL; - } - /* Point the return values at the NDR data. */ - rdata->data = r->stub_and_verifier.data; + *rdata = tmp_stub; - if (pkt->auth_length) { - /* We've already done integer wrap tests in - * dcerpc_check_auth(). */ - rdata->length = r->stub_and_verifier.length - - pad_len - - DCERPC_AUTH_TRAILER_LENGTH - - pkt->auth_length; - } else { - rdata->length = r->stub_and_verifier.length; - } - - DEBUG(10, ("Got pdu len %lu, data_len %lu, ss_len %u\n", + DEBUG(10, ("Got pdu len %lu, data_len %lu\n", (long unsigned int)pdu->length, - (long unsigned int)rdata->length, - (unsigned int)pad_len)); + (long unsigned int)rdata->length)); /* * If this is the first reply, and the allocation hint is @@ -478,14 +556,24 @@ static NTSTATUS cli_pipe_validate_curren break; - case DCERPC_PKT_BIND_NAK: - DEBUG(1, (__location__ ": Bind NACK received from %s!\n", - rpccli_pipe_txt(talloc_tos(), cli))); - /* Use this for now... */ - return NT_STATUS_NETWORK_ACCESS_DENIED; - case DCERPC_PKT_FAULT: + ret = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_FAULT, + 0, /* max_auth_info */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST, + DCERPC_PFC_FLAG_DID_NOT_EXECUTE); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, (__location__ ": Connection to %s got an unexpected " + "RPC packet type - %u, expected %u: %s\n", + rpccli_pipe_txt(talloc_tos(), cli), + pkt->ptype, expected_pkt_type, + nt_errstr(ret))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + return ret; + } + DEBUG(1, (__location__ ": RPC fault code %s received " "from %s!\n", dcerpc_errstr(talloc_tos(), @@ -502,13 +590,6 @@ static NTSTATUS cli_pipe_validate_curren return NT_STATUS_RPC_PROTOCOL_ERROR; } - if (pkt->ptype != expected_pkt_type) { - DEBUG(3, (__location__ ": Connection to %s got an unexpected " - "RPC packet type - %u, not %u\n", - rpccli_pipe_txt(talloc_tos(), cli), - pkt->ptype, expected_pkt_type)); - return NT_STATUS_RPC_PROTOCOL_ERROR; - } if (pkt->call_id != call_id) { DEBUG(3, (__location__ ": Connection to %s got an unexpected " @@ -518,17 +599,6 @@ static NTSTATUS cli_pipe_validate_curren return NT_STATUS_RPC_PROTOCOL_ERROR; } - /* Do this just before return - we don't want to modify any rpc header - data before now as we may have needed to do cryptographic actions on - it before. */ - - if ((pkt->ptype == DCERPC_PKT_BIND_ACK) && - !(pkt->pfc_flags & DCERPC_PFC_FLAG_LAST)) { - DEBUG(5, (__location__ ": bug in server (AS/U?), setting " - "fragment first/last ON.\n")); - pkt->pfc_flags |= DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST; - } - return NT_STATUS_OK; } @@ -883,6 +953,12 @@ static void rpc_api_pipe_got_pdu(struct state->pkt = talloc(state, struct ncacn_packet); if (!state->pkt) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); tevent_req_nterror(req, NT_STATUS_NO_MEMORY); return; } @@ -892,18 +968,16 @@ static void rpc_api_pipe_got_pdu(struct state->pkt, !state->endianess); if (!NT_STATUS_IS_OK(status)) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); tevent_req_nterror(req, status); return; } - if (state->incoming_frag.length != state->pkt->frag_length) { - DEBUG(5, ("Incorrect pdu length %u, expected %u\n", - (unsigned int)state->incoming_frag.length, - (unsigned int)state->pkt->frag_length)); - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); - return; - } - status = cli_pipe_validate_current_pdu(state, state->cli, state->pkt, &state->incoming_frag, @@ -917,6 +991,28 @@ static void rpc_api_pipe_got_pdu(struct (unsigned)state->reply_pdu_offset, nt_errstr(status))); + if (state->pkt->ptype != DCERPC_PKT_FAULT && !NT_STATUS_IS_OK(status)) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + } if (!NT_STATUS_IS_OK(status)) { tevent_req_nterror(req, status); return; @@ -941,7 +1037,24 @@ static void rpc_api_pipe_got_pdu(struct "%s\n", state->endianess?"little":"big", state->pkt->drep[0]?"little":"big")); - tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + if (state->reply_pdu_offset + rdata.length > MAX_RPC_DATA_SIZE) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); return; } @@ -949,6 +1062,12 @@ static void rpc_api_pipe_got_pdu(struct if (state->reply_pdu.length < state->reply_pdu_offset + rdata.length) { if (!data_blob_realloc(NULL, &state->reply_pdu, state->reply_pdu_offset + rdata.length)) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); tevent_req_nterror(req, NT_STATUS_NO_MEMORY); return; } @@ -978,6 +1097,14 @@ static void rpc_api_pipe_got_pdu(struct subreq = get_complete_frag_send(state, state->ev, state->cli, state->call_id, &state->incoming_frag); + if (subreq == NULL) { + /* + * TODO: do a real async disconnect ... + * + * For now do it sync... + */ + TALLOC_FREE(state->cli->transport); + } if (tevent_req_nomem(subreq, req)) { return; } @@ -1247,7 +1374,7 @@ static NTSTATUS create_rpc_bind_req(TALL auth->auth_type, auth->auth_level, 0, /* auth_pad_length */ - 1, /* auth_context_id */ + auth->auth_context_id, &auth_token, &auth_info); if (!NT_STATUS_IS_OK(ret)) { @@ -1749,9 +1876,8 @@ static bool check_bind_response(const st static NTSTATUS create_rpc_bind_auth3(TALLOC_CTX *mem_ctx, struct rpc_pipe_client *cli, - uint32 rpc_call_id, - enum dcerpc_AuthType auth_type, - enum dcerpc_AuthLevel auth_level, + struct pipe_auth_data *auth, + uint32_t rpc_call_id, DATA_BLOB *pauth_blob, DATA_BLOB *rpc_out) { @@ -1761,10 +1887,10 @@ static NTSTATUS create_rpc_bind_auth3(TA u.auth3._pad = 0; status = dcerpc_push_dcerpc_auth(mem_ctx, - auth_type, - auth_level, + auth->auth_type, + auth->auth_level, 0, /* auth_pad_length */ - 1, /* auth_context_id */ + auth->auth_context_id, pauth_blob, &u.auth3.auth_info); if (!NT_STATUS_IS_OK(status)) { @@ -1794,9 +1920,8 @@ static NTSTATUS create_rpc_bind_auth3(TA ********************************************************************/ static NTSTATUS create_rpc_alter_context(TALLOC_CTX *mem_ctx, - enum dcerpc_AuthType auth_type, - enum dcerpc_AuthLevel auth_level, - uint32 rpc_call_id, + struct pipe_auth_data *auth, + uint32_t rpc_call_id, const struct ndr_syntax_id *abstract, const struct ndr_syntax_id *transfer, const DATA_BLOB *pauth_blob, /* spnego auth blob already created. */ @@ -1806,10 +1931,10 @@ static NTSTATUS create_rpc_alter_context NTSTATUS status; status = dcerpc_push_dcerpc_auth(mem_ctx, - auth_type, - auth_level, + auth->auth_type, + auth->auth_level, 0, /* auth_pad_length */ - 1, /* auth_context_id */ + auth->auth_context_id, pauth_blob, &auth_info); if (!NT_STATUS_IS_OK(status)) { @@ -1957,30 +2082,45 @@ static void rpc_pipe_bind_step_one_done( rpc_pipe_bind_step_two_trigger(req); return; - case DCERPC_AUTH_TYPE_NTLMSSP: - case DCERPC_AUTH_TYPE_SPNEGO: - case DCERPC_AUTH_TYPE_KRB5: - /* Paranoid lenght checks */ - if (pkt->frag_length < DCERPC_AUTH_TRAILER_LENGTH - + pkt->auth_length) { - tevent_req_nterror(req, - NT_STATUS_INFO_LENGTH_MISMATCH); + default: + if (pkt->auth_length == 0) { + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); return; } /* get auth credentials */ - status = dcerpc_pull_dcerpc_auth(talloc_tos(), - &pkt->u.bind_ack.auth_info, - &auth, false); + status = dcerpc_pull_auth_trailer(pkt, talloc_tos(), + &pkt->u.bind_ack.auth_info, + &auth, NULL, true); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Failed to pull dcerpc auth: %s.\n", nt_errstr(status))); tevent_req_nterror(req, status); return; } - break; - default: - goto err_out; + if (auth.auth_type != pauth->auth_type) { + DEBUG(0, (__location__ " Auth type %u mismatch expected %u.\n", + auth.auth_type, pauth->auth_type)); + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + if (auth.auth_level != pauth->auth_level) { + DEBUG(0, (__location__ " Auth level %u mismatch expected %u.\n", + auth.auth_level, pauth->auth_level)); + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + if (auth.auth_context_id != pauth->auth_context_id) { + DEBUG(0, (__location__ " Auth context id %u mismatch expected %u.\n", + (unsigned)auth.auth_context_id, + (unsigned)pauth->auth_context_id)); + tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR); + return; + } + + break; } /* @@ -2226,9 +2366,7 @@ static NTSTATUS rpc_bind_next_send(struc /* Now prepare the alter context pdu. */ data_blob_free(&state->rpc_out); - status = create_rpc_alter_context(state, - auth->auth_type, - auth->auth_level, + status = create_rpc_alter_context(state, auth, state->rpc_call_id, &state->cli->abstract_syntax, &state->cli->transfer_syntax, @@ -2261,10 +2399,8 @@ static NTSTATUS rpc_bind_finish_send(str /* Now prepare the auth3 context pdu. */ data_blob_free(&state->rpc_out); - status = create_rpc_bind_auth3(state, state->cli, + status = create_rpc_bind_auth3(state, state->cli, auth, state->rpc_call_id, - auth->auth_type, - auth->auth_level, auth_token, &state->rpc_out); if (!NT_STATUS_IS_OK(status)) { @@ -2498,8 +2634,9 @@ static struct tevent_req *rpccli_bh_disc /* * TODO: do a real async disconnect ... * - * For now the caller needs to free rpc_cli + * For now we do it sync... */ + TALLOC_FREE(hs->rpc_cli->transport); hs->rpc_cli = NULL; tevent_req_done(req); @@ -2636,6 +2773,7 @@ NTSTATUS rpccli_ncalrpc_bind_data(TALLOC result->auth_type = DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM; result->auth_level = DCERPC_AUTH_LEVEL_CONNECT; + result->auth_context_id = 1; result->user_name = talloc_strdup(result, ""); result->domain = talloc_strdup(result, ""); @@ -2660,6 +2798,7 @@ NTSTATUS rpccli_anon_bind_data(TALLOC_CT result->auth_type = DCERPC_AUTH_TYPE_NONE; result->auth_level = DCERPC_AUTH_LEVEL_NONE; + result->auth_context_id = 0; result->user_name = talloc_strdup(result, ""); result->domain = talloc_strdup(result, ""); @@ -2697,6 +2836,7 @@ static NTSTATUS rpccli_ntlmssp_bind_data result->auth_type = auth_type; result->auth_level = auth_level; + result->auth_context_id = 1; result->user_name = talloc_strdup(result, username); result->domain = talloc_strdup(result, domain); @@ -2768,6 +2908,7 @@ NTSTATUS rpccli_schannel_bind_data(TALLO result->auth_type = DCERPC_AUTH_TYPE_SCHANNEL; result->auth_level = auth_level; + result->auth_context_id = 1; result->user_name = talloc_strdup(result, ""); result->domain = talloc_strdup(result, domain); @@ -3432,6 +3573,7 @@ NTSTATUS cli_rpc_pipe_open_krb5(struct c } auth->auth_type = DCERPC_AUTH_TYPE_KRB5; auth->auth_level = auth_level; + auth->auth_context_id = 1; if (!username) { username = ""; @@ -3502,6 +3644,7 @@ NTSTATUS cli_rpc_pipe_open_spnego_krb5(s } auth->auth_type = DCERPC_AUTH_TYPE_SPNEGO; auth->auth_level = auth_level; + auth->auth_context_id = 1; if (!username) { username = ""; @@ -3576,6 +3719,7 @@ NTSTATUS cli_rpc_pipe_open_spnego_ntlmss } auth->auth_type = DCERPC_AUTH_TYPE_SPNEGO; auth->auth_level = auth_level; + auth->auth_context_id = 1; if (!username) { username = ""; --- a/source4/rpc_server/dcesrv_auth.c +++ b/source4/rpc_server/dcesrv_auth.c @@ -46,7 +46,7 @@ bool dcesrv_auth_bind(struct dcesrv_call NTSTATUS status; uint32_t auth_length; - if (pkt->u.bind.auth_info.length == 0) { + if (pkt->auth_length == 0) { dce_conn->auth_state.auth_info = NULL; return true; } @@ -108,7 +108,7 @@ NTSTATUS dcesrv_auth_bind_ack(struct dce struct dcesrv_connection *dce_conn = call->conn; NTSTATUS status; - if (!call->conn->auth_state.gensec_security) { + if (call->pkt.auth_length == 0) { return NT_STATUS_OK; } @@ -155,10 +155,16 @@ bool dcesrv_auth_auth3(struct dcesrv_cal NTSTATUS status; uint32_t auth_length; - /* We can't work without an existing gensec state, and an new blob to feed it */ - if (!dce_conn->auth_state.auth_info || - !dce_conn->auth_state.gensec_security || - pkt->u.auth3.auth_info.length == 0) { + if (pkt->auth_length == 0) { + return false; + } + + if (!dce_conn->auth_state.auth_info) { + return false; + } + + /* We can't work without an existing gensec state */ + if (!dce_conn->auth_state.gensec_security) { return false; } @@ -203,7 +209,7 @@ bool dcesrv_auth_alter(struct dcesrv_cal uint32_t auth_length; /* on a pure interface change there is no auth blob */ - if (pkt->u.alter.auth_info.length == 0) { + if (pkt->auth_length == 0) { return true; } @@ -238,8 +244,7 @@ NTSTATUS dcesrv_auth_alter_ack(struct dc /* on a pure interface change there is no auth_info structure setup */ - if (!call->conn->auth_state.auth_info || - dce_conn->auth_state.auth_info->credentials.length == 0) { + if (call->pkt.auth_length == 0) { return NT_STATUS_OK; } @@ -315,6 +320,11 @@ bool dcesrv_auth_request(struct dcesrv_c return false; } + if (pkt->auth_length == 0) { + DEBUG(1,("dcesrv_auth_request: unexpected auth_length of 0\n")); + return false; + } + status = dcerpc_pull_auth_trailer(pkt, call, &pkt->u.request.stub_and_verifier, &auth, &auth_length, false); --- a/source4/librpc/rpc/dcerpc.c +++ b/source4/librpc/rpc/dcerpc.c @@ -701,6 +701,14 @@ static NTSTATUS ncacn_pull_request_auth( return NT_STATUS_INVALID_LEVEL; } + if (pkt->auth_length == 0) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (c->security_state.generic_state == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + status = dcerpc_pull_auth_trailer(pkt, mem_ctx, &pkt->u.response.stub_and_verifier, &auth, &auth_length, false); @@ -1074,7 +1082,7 @@ static void dcerpc_bind_recv_handler(str } /* the bind_ack might contain a reply set of credentials */ - if (conn->security_state.auth_info && pkt->u.bind_ack.auth_info.length) { + if (conn->security_state.auth_info && pkt->auth_length) { NTSTATUS status; uint32_t auth_length; status = dcerpc_pull_auth_trailer(pkt, conn, &pkt->u.bind_ack.auth_info, @@ -1847,8 +1855,7 @@ static void dcerpc_alter_recv_handler(st } /* the alter_resp might contain a reply set of credentials */ - if (recv_pipe->conn->security_state.auth_info && - pkt->u.alter_resp.auth_info.length) { + if (recv_pipe->conn->security_state.auth_info && pkt->auth_length) { struct dcecli_connection *conn = recv_pipe->conn; NTSTATUS status; uint32_t auth_length; --- a/source3/librpc/rpc/dcerpc.h +++ b/source3/librpc/rpc/dcerpc.h @@ -42,6 +42,7 @@ struct pipe_auth_data { bool verified_bitmask1; void *auth_ctx; + uint32_t auth_context_id; /* Only the client code uses these 3 for now */ char *domain; @@ -71,10 +72,6 @@ NTSTATUS dcerpc_push_dcerpc_auth(TALLOC_ uint32_t auth_context_id, const DATA_BLOB *credentials, DATA_BLOB *blob); -NTSTATUS dcerpc_pull_dcerpc_auth(TALLOC_CTX *mem_ctx, - const DATA_BLOB *blob, - struct dcerpc_auth *r, - bool bigendian); NTSTATUS dcerpc_guess_sizes(struct pipe_auth_data *auth, size_t header_len, size_t data_left, size_t max_xmit_frag, size_t pad_alignment, @@ -85,9 +82,8 @@ NTSTATUS dcerpc_add_auth_footer(struct p NTSTATUS dcerpc_check_auth(struct pipe_auth_data *auth, struct ncacn_packet *pkt, DATA_BLOB *pkt_trailer, - size_t header_size, - DATA_BLOB *raw_pkt, - size_t *pad_len); + uint8_t header_size, + DATA_BLOB *raw_pkt); /* The following definitions come from librpc/rpc/rpc_common.c */ --- a/source3/rpc_server/srv_pipe.c +++ b/source3/rpc_server/srv_pipe.c @@ -42,6 +42,7 @@ #include "auth.h" #include "ntdomain.h" #include "rpc_server/srv_pipe.h" +#include "../librpc/gen_ndr/ndr_dcerpc.h" #include "../librpc/ndr/ndr_dcerpc.h" #undef DBGC_CLASS @@ -270,10 +271,14 @@ static bool setup_bind_nak(struct pipes_ p->out_data.data_sent_length = 0; p->out_data.current_pdu_sent = 0; + set_incoming_fault(p); TALLOC_FREE(p->auth.auth_ctx); p->auth.auth_level = DCERPC_AUTH_LEVEL_NONE; p->auth.auth_type = DCERPC_AUTH_TYPE_NONE; p->pipe_bound = False; + p->allow_bind = false; + p->allow_alter = false; + p->allow_auth3 = false; return True; } @@ -339,16 +344,46 @@ static bool check_bind_req(struct pipes_ DEBUG(3,("check_bind_req for %s\n", get_pipe_name_from_syntax(talloc_tos(), abstract))); + ok = ndr_syntax_id_equal(transfer, &ndr_transfer_syntax); + if (!ok) { + DEBUG(1,("check_bind_req unknown transfer syntax for " + "%s context_id=%u\n", + get_pipe_name_from_syntax(talloc_tos(), abstract), + (unsigned)context_id)); + return false; + } + + for (context_fns = p->contexts; + context_fns != NULL; + context_fns = context_fns->next) + { + if (context_fns->context_id != context_id) { + continue; + } + + ok = ndr_syntax_id_equal(&context_fns->syntax, + abstract); + if (ok) { + return true; + } + + DEBUG(1,("check_bind_req: changing abstract syntax for " + "%s context_id=%u into %s not supported\n", + get_pipe_name_from_syntax(talloc_tos(), &context_fns->syntax), + (unsigned)context_id, + get_pipe_name_from_syntax(talloc_tos(), abstract))); + return false; + } + /* we have to check all now since win2k introduced a new UUID on the lsaprpc pipe */ - if (rpc_srv_pipe_exists_by_id(abstract) && - ndr_syntax_id_equal(transfer, &ndr_transfer_syntax)) { - DEBUG(3, ("check_bind_req: \\PIPE\\%s -> \\PIPE\\%s\n", - rpc_srv_get_pipe_cli_name(abstract), - rpc_srv_get_pipe_srv_name(abstract))); - } else { + if (!rpc_srv_pipe_exists_by_id(abstract)) { return false; } + DEBUG(3, ("check_bind_req: %s -> %s rpc service\n", + rpc_srv_get_pipe_cli_name(abstract), + rpc_srv_get_pipe_srv_name(abstract))); + context_fns = SMB_MALLOC_P(struct pipe_rpc_fns); if (context_fns == NULL) { DEBUG(0,("check_bind_req: malloc() failed!\n")); @@ -447,6 +482,7 @@ static bool pipe_spnego_auth_bind(struct p->auth.auth_ctx = spnego_ctx; p->auth.auth_type = DCERPC_AUTH_TYPE_SPNEGO; + p->auth.auth_context_id = auth_info->auth_context_id; DEBUG(10, ("SPNEGO auth started\n")); @@ -557,6 +593,7 @@ static bool pipe_schannel_auth_bind(stru /* We're finished with this bind - no more packets. */ p->auth.auth_ctx = schannel_auth; p->auth.auth_type = DCERPC_AUTH_TYPE_SCHANNEL; + p->auth.auth_context_id = auth_info->auth_context_id; p->pipe_bound = True; @@ -601,6 +638,7 @@ static bool pipe_ntlmssp_auth_bind(struc p->auth.auth_ctx = ntlmssp_state; p->auth.auth_type = DCERPC_AUTH_TYPE_NTLMSSP; + p->auth.auth_context_id = auth_info->auth_context_id; DEBUG(10, (__location__ ": NTLMSSP auth started\n")); @@ -776,6 +814,11 @@ static NTSTATUS pipe_auth_verify_final(s void *mech_ctx; NTSTATUS status; + if (p->auth.auth_type == DCERPC_AUTH_TYPE_NONE) { + p->pipe_bound = true; + return NT_STATUS_OK; + } + switch (p->auth.auth_type) { case DCERPC_AUTH_TYPE_NTLMSSP: ntlmssp_ctx = talloc_get_type_abort(p->auth.auth_ctx, @@ -867,16 +910,38 @@ static bool api_pipe_bind_req(struct pip DATA_BLOB auth_resp = data_blob_null; DATA_BLOB auth_blob = data_blob_null; - /* No rebinds on a bound pipe - use alter context. */ - if (p->pipe_bound) { - DEBUG(2,("api_pipe_bind_req: rejecting bind request on bound " - "pipe %s.\n", - get_pipe_name_from_syntax(talloc_tos(), &p->syntax))); + if (!p->allow_bind) { + DEBUG(2,("Pipe not in allow bind state\n")); return setup_bind_nak(p, pkt); } + p->allow_bind = false; + + status = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_BIND, + pkt->u.bind.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("api_pipe_bind_req: invalid pdu: %s\n", + nt_errstr(status))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + goto err_exit; + } if (pkt->u.bind.num_contexts == 0) { - DEBUG(0, ("api_pipe_bind_req: no rpc contexts around\n")); + DEBUG(1, ("api_pipe_bind_req: no rpc contexts around\n")); + goto err_exit; + } + + if (pkt->u.bind.ctx_list[0].num_transfer_syntaxes == 0) { + DEBUG(1, ("api_pipe_bind_req: no transfer syntaxes around\n")); goto err_exit; } @@ -960,25 +1025,12 @@ static bool api_pipe_bind_req(struct pip * Check if this is an authenticated bind request. */ if (pkt->auth_length) { - /* Quick length check. Won't catch a bad auth footer, - * prevents overrun. */ - - if (pkt->frag_length < RPC_HEADER_LEN + - DCERPC_AUTH_TRAILER_LENGTH + - pkt->auth_length) { - DEBUG(0,("api_pipe_bind_req: auth_len (%u) " - "too long for fragment %u.\n", - (unsigned int)pkt->auth_length, - (unsigned int)pkt->frag_length)); - goto err_exit; - } - /* * Decode the authentication verifier. */ - status = dcerpc_pull_dcerpc_auth(pkt, - &pkt->u.bind.auth_info, - &auth_info, p->endian); + status = dcerpc_pull_auth_trailer(pkt, pkt, + &pkt->u.bind.auth_info, + &auth_info, NULL, true); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Unable to unmarshall dcerpc_auth.\n")); goto err_exit; @@ -1072,6 +1124,7 @@ static bool api_pipe_bind_req(struct pip p->pipe_bound = True; /* The session key was initialized from the SMB * session in make_internal_rpc_pipe_p */ + p->auth.auth_context_id = 0; } ZERO_STRUCT(u.bind_ack); @@ -1113,15 +1166,15 @@ static bool api_pipe_bind_req(struct pip if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Failed to marshall bind_ack packet. (%s)\n", nt_errstr(status))); + goto err_exit; } if (auth_resp.length) { - status = dcerpc_push_dcerpc_auth(pkt, auth_type, auth_info.auth_level, - 0, - 1, /* auth_context_id */ + 0, /* pad_len */ + p->auth.auth_context_id, &auth_resp, &auth_blob); if (!NT_STATUS_IS_OK(status)) { @@ -1152,6 +1205,22 @@ static bool api_pipe_bind_req(struct pip p->out_data.current_pdu_sent = 0; TALLOC_FREE(auth_blob.data); + + if (bind_ack_ctx.result == 0) { + p->allow_alter = true; + p->allow_auth3 = true; + if (p->auth.auth_type == DCERPC_AUTH_TYPE_NONE) { + status = pipe_auth_verify_final(p); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("pipe_auth_verify_final failed: %s\n", + nt_errstr(status))); + goto err_exit; + } + } + } else { + goto err_exit; + } + return True; err_exit: @@ -1176,18 +1245,39 @@ bool api_pipe_bind_auth3(struct pipes_st DEBUG(5, ("api_pipe_bind_auth3: decode request. %d\n", __LINE__)); - if (pkt->auth_length == 0) { - DEBUG(0, ("No auth field sent for bind request!\n")); + if (!p->allow_auth3) { + DEBUG(1, ("Pipe not in allow auth3 state.\n")); goto err; } - /* Ensure there's enough data for an authenticated request. */ - if (pkt->frag_length < RPC_HEADER_LEN - + DCERPC_AUTH_TRAILER_LENGTH - + pkt->auth_length) { - DEBUG(0,("api_pipe_ntlmssp_auth_process: auth_len " - "%u is too large.\n", - (unsigned int)pkt->auth_length)); + status = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_AUTH3, + pkt->u.auth3.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("api_pipe_bind_auth3: invalid pdu: %s\n", + nt_errstr(status))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + goto err; + } + + /* We can only finish if the pipe is unbound for now */ + if (p->pipe_bound) { + DEBUG(0, (__location__ ": Pipe already bound, " + "AUTH3 not supported!\n")); + goto err; + } + + if (pkt->auth_length == 0) { + DEBUG(1, ("No auth field sent for auth3 request!\n")); goto err; } @@ -1195,9 +1285,9 @@ bool api_pipe_bind_auth3(struct pipes_st * Decode the authentication verifier response. */ - status = dcerpc_pull_dcerpc_auth(pkt, - &pkt->u.auth3.auth_info, - &auth_info, p->endian); + status = dcerpc_pull_auth_trailer(pkt, pkt, + &pkt->u.auth3.auth_info, + &auth_info, NULL, true); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Failed to unmarshall dcerpc_auth.\n")); goto err; @@ -1215,6 +1305,21 @@ bool api_pipe_bind_auth3(struct pipes_st goto err; } + if (auth_info.auth_level != p->auth.auth_level) { + DEBUG(1, ("Auth level mismatch! Client sent %d, " + "but auth was started as level %d!\n", + auth_info.auth_level, p->auth.auth_level)); + goto err; + } + + if (auth_info.auth_context_id != p->auth.auth_context_id) { + DEBUG(0, ("Auth context id mismatch! Client sent %u, " + "but auth was started as level %u!\n", + (unsigned)auth_info.auth_context_id, + (unsigned)p->auth.auth_context_id)); + goto err; + } + switch (auth_info.auth_type) { case DCERPC_AUTH_TYPE_NTLMSSP: ntlmssp_ctx = talloc_get_type_abort(p->auth.auth_ctx, @@ -1267,6 +1372,10 @@ bool api_pipe_bind_auth3(struct pipes_st return true; err: + p->pipe_bound = false; + p->allow_bind = false; + p->allow_alter = false; + p->allow_auth3 = false; TALLOC_FREE(p->auth.auth_ctx); return false; @@ -1284,7 +1393,7 @@ static bool api_pipe_alter_context(struc uint16 assoc_gid; NTSTATUS status; union dcerpc_payload u; - struct dcerpc_ack_ctx bind_ack_ctx; + struct dcerpc_ack_ctx alter_ack_ctx; DATA_BLOB auth_resp = data_blob_null; DATA_BLOB auth_blob = data_blob_null; int pad_len = 0; @@ -1294,8 +1403,42 @@ static bool api_pipe_alter_context(struc DEBUG(5,("api_pipe_alter_context: make response. %d\n", __LINE__)); - if (pkt->u.bind.assoc_group_id != 0) { - assoc_gid = pkt->u.bind.assoc_group_id; + if (!p->allow_alter) { + DEBUG(1, ("Pipe not in allow alter state.\n")); + goto err_exit; + } + + status = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_ALTER, + pkt->u.alter.auth_info.length, + 0, /* required flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("api_pipe_alter_context: invalid pdu: %s\n", + nt_errstr(status))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + goto err_exit; + } + + if (pkt->u.alter.num_contexts == 0) { + DEBUG(1, ("api_pipe_alter_context: no rpc contexts around\n")); + goto err_exit; + } + + if (pkt->u.alter.ctx_list[0].num_transfer_syntaxes == 0) { + DEBUG(1, ("api_pipe_alter_context: no transfer syntaxes around\n")); + goto err_exit; + } + + if (pkt->u.alter.assoc_group_id != 0) { + assoc_gid = pkt->u.alter.assoc_group_id; } else { assoc_gid = 0x53f0; } @@ -1305,59 +1448,45 @@ static bool api_pipe_alter_context(struc */ /* If the requested abstract synt uuid doesn't match our client pipe, - reject the bind_ack & set the transfer interface synt to all 0's, + reject the alter_ack & set the transfer interface synt to all 0's, ver 0 (observed when NT5 attempts to bind to abstract interfaces unknown to NT4) Needed when adding entries to a DACL from NT5 - SK */ if (check_bind_req(p, - &pkt->u.bind.ctx_list[0].abstract_syntax, - &pkt->u.bind.ctx_list[0].transfer_syntaxes[0], - pkt->u.bind.ctx_list[0].context_id)) { - - bind_ack_ctx.result = 0; - bind_ack_ctx.reason = 0; - bind_ack_ctx.syntax = pkt->u.bind.ctx_list[0].transfer_syntaxes[0]; + &pkt->u.alter.ctx_list[0].abstract_syntax, + &pkt->u.alter.ctx_list[0].transfer_syntaxes[0], + pkt->u.alter.ctx_list[0].context_id)) { + + alter_ack_ctx.result = 0; + alter_ack_ctx.reason = 0; + alter_ack_ctx.syntax = pkt->u.alter.ctx_list[0].transfer_syntaxes[0]; } else { - p->pipe_bound = False; /* Rejection reason: abstract syntax not supported */ - bind_ack_ctx.result = DCERPC_BIND_PROVIDER_REJECT; - bind_ack_ctx.reason = DCERPC_BIND_REASON_ASYNTAX; - bind_ack_ctx.syntax = null_ndr_syntax_id; + alter_ack_ctx.result = DCERPC_BIND_PROVIDER_REJECT; + alter_ack_ctx.reason = DCERPC_BIND_REASON_ASYNTAX; + alter_ack_ctx.syntax = null_ndr_syntax_id; } /* * Check if this is an authenticated alter context request. */ if (pkt->auth_length) { - /* Quick length check. Won't catch a bad auth footer, - * prevents overrun. */ - - if (pkt->frag_length < RPC_HEADER_LEN + - DCERPC_AUTH_TRAILER_LENGTH + - pkt->auth_length) { - DEBUG(0,("api_pipe_alter_context: auth_len (%u) " - "too long for fragment %u.\n", - (unsigned int)pkt->auth_length, - (unsigned int)pkt->frag_length )); + /* We can only finish if the pipe is unbound for now */ + if (p->pipe_bound) { + DEBUG(0, (__location__ ": Pipe already bound, " + "Altering Context not yet supported!\n")); goto err_exit; } - status = dcerpc_pull_dcerpc_auth(pkt, - &pkt->u.bind.auth_info, - &auth_info, p->endian); + status = dcerpc_pull_auth_trailer(pkt, pkt, + &pkt->u.alter.auth_info, + &auth_info, NULL, true); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Unable to unmarshall dcerpc_auth.\n")); goto err_exit; } - /* We can only finish if the pipe is unbound for now */ - if (p->pipe_bound) { - DEBUG(0, (__location__ ": Pipe already bound, " - "Altering Context not yet supported!\n")); - goto err_exit; - } - if (auth_info.auth_type != p->auth.auth_type) { DEBUG(0, ("Auth type mismatch! Client sent %d, " "but auth was started as type %d!\n", @@ -1365,6 +1494,20 @@ static bool api_pipe_alter_context(struc goto err_exit; } + if (auth_info.auth_level != p->auth.auth_level) { + DEBUG(0, ("Auth level mismatch! Client sent %d, " + "but auth was started as level %d!\n", + auth_info.auth_level, p->auth.auth_level)); + goto err_exit; + } + + if (auth_info.auth_context_id != p->auth.auth_context_id) { + DEBUG(0, ("Auth context id mismatch! Client sent %u, " + "but auth was started as level %u!\n", + (unsigned)auth_info.auth_context_id, + (unsigned)p->auth.auth_context_id)); + goto err_exit; + } switch (auth_info.auth_type) { case DCERPC_AUTH_TYPE_SPNEGO: @@ -1431,7 +1574,7 @@ static bool api_pipe_alter_context(struc u.alter_resp.secondary_address_size = 1; u.alter_resp.num_results = 1; - u.alter_resp.ctx_list = &bind_ack_ctx; + u.alter_resp.ctx_list = &alter_ack_ctx; /* NOTE: We leave the auth_info empty so we can calculate the padding * later and then append the auth_info --simo */ @@ -1451,8 +1594,9 @@ static bool api_pipe_alter_context(struc &u, &p->out_data.frag); if (!NT_STATUS_IS_OK(status)) { - DEBUG(0, ("Failed to marshall bind_ack packet. (%s)\n", + DEBUG(0, ("Failed to marshall alter_resp packet. (%s)\n", nt_errstr(status))); + goto err_exit; } if (auth_resp.length) { @@ -1469,7 +1613,7 @@ static bool api_pipe_alter_context(struc auth_info.auth_type, auth_info.auth_level, pad_len, - 1, /* auth_context_id */ + p->auth.auth_context_id, &auth_resp, &auth_blob); if (!NT_STATUS_IS_OK(status)) { @@ -1618,6 +1762,7 @@ static bool api_pipe_request(struct pipe if (!srv_pipe_check_verification_trailer(p, pkt, pipe_fns)) { DEBUG(1, ("srv_pipe_check_verification_trailer: failed\n")); + set_incoming_fault(p); setup_fault_pdu(p, NT_STATUS(DCERPC_FAULT_ACCESS_DENIED)); data_blob_free(&p->out_data.rdata); TALLOC_FREE(frame); @@ -1756,7 +1901,11 @@ void set_incoming_fault(struct pipes_str data_blob_free(&p->in_data.data); p->in_data.pdu_needed_len = 0; p->in_data.pdu.length = 0; - p->fault_state = DCERPC_FAULT_CANT_PERFORM; + p->fault_state = DCERPC_NCA_S_PROTO_ERROR; + + p->allow_alter = false; + p->allow_auth3 = false; + p->pipe_bound = false; DEBUG(10, ("Setting fault state\n")); } @@ -1767,7 +1916,6 @@ static NTSTATUS dcesrv_auth_request(stru { NTSTATUS status; size_t hdr_size = DCERPC_REQUEST_LENGTH; - size_t pad_len; DEBUG(10, ("Checking request auth.\n")); @@ -1778,25 +1926,11 @@ static NTSTATUS dcesrv_auth_request(stru /* in case of sealing this function will unseal the data in place */ status = dcerpc_check_auth(auth, pkt, &pkt->u.request.stub_and_verifier, - hdr_size, raw_pkt, - &pad_len); + hdr_size, raw_pkt); if (!NT_STATUS_IS_OK(status)) { return status; } - - /* remove padding and auth trailer, - * this way the caller will get just the data */ - if (pkt->auth_length) { - size_t trail_len = pad_len - + DCERPC_AUTH_TRAILER_LENGTH - + pkt->auth_length; - if (pkt->u.request.stub_and_verifier.length < trail_len) { - return NT_STATUS_INFO_LENGTH_MISMATCH; - } - pkt->u.request.stub_and_verifier.length -= trail_len; - } - return NT_STATUS_OK; } @@ -1816,6 +1950,29 @@ static bool process_request_pdu(struct p return False; } + /* + * We don't ignore DCERPC_PFC_FLAG_PENDING_CANCEL. + * TODO: we can reject it with DCERPC_FAULT_NO_CALL_ACTIVE later. + */ + status = dcerpc_verify_ncacn_packet_header(pkt, + DCERPC_PKT_REQUEST, + pkt->u.request.stub_and_verifier.length, + 0, /* required_flags */ + DCERPC_PFC_FLAG_FIRST | + DCERPC_PFC_FLAG_LAST | + 0x08 | /* this is not defined, but should be ignored */ + DCERPC_PFC_FLAG_CONC_MPX | + DCERPC_PFC_FLAG_DID_NOT_EXECUTE | + DCERPC_PFC_FLAG_MAYBE | + DCERPC_PFC_FLAG_OBJECT_UUID); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("process_request_pdu: invalid pdu: %s\n", + nt_errstr(status))); + NDR_PRINT_DEBUG(ncacn_packet, pkt); + set_incoming_fault(p); + return false; + } + /* Store the opnum */ p->opnum = pkt->u.request.opnum; @@ -2065,7 +2222,7 @@ done: "pipe %s\n", get_pipe_name_from_syntax(talloc_tos(), &p->syntax))); set_incoming_fault(p); - setup_fault_pdu(p, NT_STATUS(DCERPC_FAULT_OP_RNG_ERROR)); + setup_fault_pdu(p, NT_STATUS(DCERPC_NCA_S_PROTO_ERROR)); TALLOC_FREE(pkt); } else { /* --- a/source3/include/ntdomain.h +++ b/source3/include/ntdomain.h @@ -135,6 +135,13 @@ struct pipes_struct { bool pipe_bound; /* + * States we can be in. + */ + bool allow_alter; + bool allow_bind; + bool allow_auth3; + + /* * Set the DCERPC_FAULT to return. */ --- a/source3/rpc_server/rpc_ncacn_np.c +++ b/source3/rpc_server/rpc_ncacn_np.c @@ -171,6 +171,7 @@ struct pipes_struct *make_internal_rpc_p p->syntax = *syntax; p->transport = NCALRPC; + p->allow_bind = true; DEBUG(4,("Created internal pipe %s (pipes_open=%d)\n", get_pipe_name_from_syntax(talloc_tos(), syntax), pipes_open)); @@ -780,6 +781,7 @@ static NTSTATUS rpc_pipe_open_external(T } result->auth->auth_type = DCERPC_AUTH_TYPE_NONE; result->auth->auth_level = DCERPC_AUTH_LEVEL_NONE; + result->auth->auth_context_id = 0; status = rpccli_anon_bind_data(result, &auth); if (!NT_STATUS_IS_OK(status)) { --- a/source3/rpc_server/rpc_server.c +++ b/source3/rpc_server/rpc_server.c @@ -102,6 +102,7 @@ static int make_server_pipes_struct(TALL p->syntax = id; p->transport = transport; p->ncalrpc_as_system = ncalrpc_as_system; + p->allow_bind = true; p->mem_ctx = talloc_named(p, 0, "pipe %s %p", pipe_name, p); if (!p->mem_ctx) { @@ -663,6 +664,12 @@ static void named_pipe_packet_done(struc goto fail; } + if (npc->p->fault_state != 0) { + DEBUG(2, ("Disconnect after fault\n")); + sys_errno = EINVAL; + goto fail; + } + /* clear out any data that may have been left around */ npc->count = 0; TALLOC_FREE(npc->iov); @@ -1391,6 +1398,12 @@ static void dcerpc_ncacn_packet_done(str goto fail; } + if (ncacn_conn->p->fault_state != 0) { + DEBUG(2, ("Disconnect after fault\n")); + sys_errno = EINVAL; + goto fail; + } + /* clear out any data that may have been left around */ ncacn_conn->count = 0; TALLOC_FREE(ncacn_conn->iov);