Bugfix/secure apdu handler unconfirmed (#645)

* Secured APDU handler by avoiding read ahead.
This commit is contained in:
Steve Karg
2024-05-20 09:00:18 -05:00
committed by GitHub
parent cbd9b3f04f
commit 7baf912acd
+164 -153
View File
@@ -554,10 +554,12 @@ static bool apdu_unconfirmed_dcc_disabled(uint8_t service_choice)
* @param apdu [in] The apdu portion of the request, to be processed. * @param apdu [in] The apdu portion of the request, to be processed.
* @param apdu_len [in] The total (remaining) length of the apdu. * @param apdu_len [in] The total (remaining) length of the apdu.
*/ */
void apdu_handler(BACNET_ADDRESS *src, void apdu_handler(
BACNET_ADDRESS *src,
uint8_t *apdu, /* APDU data */ uint8_t *apdu, /* APDU data */
uint16_t apdu_len) uint16_t apdu_len)
{ {
BACNET_PDU_TYPE pdu_type;
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 }; BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
uint8_t service_choice = 0; uint8_t service_choice = 0;
uint8_t *service_request = NULL; uint8_t *service_request = NULL;
@@ -572,157 +574,166 @@ void apdu_handler(BACNET_ADDRESS *src,
bool server = false; bool server = false;
#endif #endif
if (apdu) { if (!apdu) {
/* PDU Type */ return;
switch (apdu[0] & 0xF0) { }
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST: if (apdu_len == 0) {
len = apdu_decode_confirmed_service_request(&apdu[0], apdu_len, return;
&service_data, &service_choice, &service_request, }
&service_request_len); pdu_type = apdu[0] & 0xF0;
if (len == 0) { switch (pdu_type) {
/* service data unable to be decoded - simply drop */ case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
break; len = apdu_decode_confirmed_service_request(
} apdu, apdu_len, &service_data, &service_choice,
if (apdu_confirmed_dcc_disabled(service_choice)) { &service_request, &service_request_len);
/* When network communications are completely disabled, if (len == 0) {
only DeviceCommunicationControl and ReinitializeDevice /* service data unable to be decoded - simply drop */
APDUs shall be processed and no messages shall be break;
initiated. */ }
break; if (apdu_confirmed_dcc_disabled(service_choice)) {
} /* When network communications are completely disabled,
if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) && only DeviceCommunicationControl and ReinitializeDevice
(Confirmed_Function[service_choice])) { APDUs shall be processed and no messages shall be
Confirmed_Function[service_choice](service_request, initiated. */
service_request_len, src, &service_data); break;
} else if (Unrecognized_Service_Handler) { }
Unrecognized_Service_Handler(service_request, if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) &&
service_request_len, src, &service_data); (Confirmed_Function[service_choice])) {
} Confirmed_Function[service_choice](
break; service_request, service_request_len, src, &service_data);
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST: } else if (Unrecognized_Service_Handler) {
if (apdu_len < 2) { Unrecognized_Service_Handler(
break; service_request, service_request_len, src, &service_data);
} }
service_choice = apdu[1]; break;
service_request = &apdu[2]; case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
service_request_len = apdu_len - 2; if (apdu_len < 2) {
if (apdu_unconfirmed_dcc_disabled(service_choice)) { break;
/* When network communications are disabled, }
only DeviceCommunicationControl and service_choice = apdu[1];
ReinitializeDevice APDUs shall be processed and no /* prepare the service request buffer and length */
messages shall be initiated. If communications have service_request_len = apdu_len - 2;
been initiation disabled, then WhoIs may be service_request = &apdu[2];
processed. */ if (apdu_unconfirmed_dcc_disabled(service_choice)) {
break; /* When network communications are disabled,
} only DeviceCommunicationControl and
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) { ReinitializeDevice APDUs shall be processed and no
if (Unconfirmed_Function[service_choice]) { messages shall be initiated. If communications have
Unconfirmed_Function[service_choice]( been initiation disabled, then WhoIs may be
service_request, service_request_len, src); processed. */
} break;
} }
break; if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
#if !BACNET_SVC_SERVER if (Unconfirmed_Function[service_choice]) {
case PDU_TYPE_SIMPLE_ACK: Unconfirmed_Function[service_choice](
if (apdu_len < 3) { service_request, service_request_len, src);
break; }
} }
invoke_id = apdu[1]; break;
service_choice = apdu[2]; #if !BACNET_SVC_SERVER
if (apdu_confirmed_simple_ack_service(service_choice)) { case PDU_TYPE_SIMPLE_ACK:
if (Confirmed_ACK_Function[service_choice].simple != if (apdu_len < 3) {
NULL) { break;
Confirmed_ACK_Function[service_choice].simple( }
src, invoke_id); invoke_id = apdu[1];
} service_choice = apdu[2];
tsm_free_invoke_id(invoke_id); if (apdu_confirmed_simple_ack_service(service_choice)) {
} if (Confirmed_ACK_Function[service_choice].simple != NULL) {
break; Confirmed_ACK_Function[service_choice].simple(
case PDU_TYPE_COMPLEX_ACK: src, invoke_id);
if (apdu_len < 3) { }
break; tsm_free_invoke_id(invoke_id);
} }
service_ack_data.segmented_message = break;
(apdu[0] & BIT(3)) ? true : false; case PDU_TYPE_COMPLEX_ACK:
service_ack_data.more_follows = if (apdu_len < 3) {
(apdu[0] & BIT(2)) ? true : false; break;
invoke_id = service_ack_data.invoke_id = apdu[1]; }
len = 2; service_ack_data.segmented_message =
if (service_ack_data.segmented_message) { (apdu[0] & BIT(3)) ? true : false;
service_ack_data.sequence_number = apdu[len++]; service_ack_data.more_follows = (apdu[0] & BIT(2)) ? true : false;
service_ack_data.proposed_window_number = apdu[len++]; invoke_id = service_ack_data.invoke_id = apdu[1];
} len = 2;
service_choice = apdu[len++]; if (service_ack_data.segmented_message) {
service_request = &apdu[len]; if (apdu_len < 5) {
service_request_len = apdu_len - (uint16_t)len; break;
if (!apdu_confirmed_simple_ack_service(service_choice)) { }
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { service_ack_data.sequence_number = apdu[len++];
if (Confirmed_ACK_Function[service_choice] service_ack_data.proposed_window_number = apdu[len++];
.complex != NULL) { }
Confirmed_ACK_Function[service_choice].complex( service_choice = apdu[len++];
service_request, service_request_len, src, /* prepare the service request buffer and length */
&service_ack_data); service_request_len = apdu_len - (uint16_t)len;
} service_request = &apdu[len];
} if (!apdu_confirmed_simple_ack_service(service_choice)) {
tsm_free_invoke_id(invoke_id); if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
} if (Confirmed_ACK_Function[service_choice].complex !=
break; NULL) {
case PDU_TYPE_SEGMENT_ACK: Confirmed_ACK_Function[service_choice].complex(
/* FIXME: what about a denial of service attack here? service_request, service_request_len, src,
we could check src to see if that matched the tsm */ &service_ack_data);
tsm_free_invoke_id(invoke_id); }
break; }
case PDU_TYPE_ERROR: tsm_free_invoke_id(invoke_id);
if (apdu_len < 3) { }
break; break;
} case PDU_TYPE_SEGMENT_ACK:
invoke_id = apdu[1]; /* FIXME: what about a denial of service attack here?
service_choice = apdu[2]; we could check src to see if that matched the tsm */
if (apdu_complex_error(service_choice)) { tsm_free_invoke_id(invoke_id);
if (Error_Function[service_choice].complex) { break;
Error_Function[service_choice].complex(src, case PDU_TYPE_ERROR:
invoke_id, service_choice, &apdu[3], if (apdu_len < 3) {
apdu_len - 3); break;
} }
} else if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { invoke_id = apdu[1];
len = bacerror_decode_error_class_and_code( service_choice = apdu[2];
&apdu[3], apdu_len - 3, &error_class, &error_code); /* prepare the service request buffer and length */
if ((len != 0) && service_request_len = apdu_len - 3;
(Error_Function[service_choice].error)) { service_request = &apdu[3];
Error_Function[service_choice].error(src, invoke_id, if (apdu_complex_error(service_choice)) {
(BACNET_ERROR_CLASS)error_class, if (Error_Function[service_choice].complex) {
(BACNET_ERROR_CODE)error_code); Error_Function[service_choice].complex(
} src, invoke_id, service_choice, service_request,
} service_request_len);
tsm_free_invoke_id(invoke_id); }
break; } else if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
case PDU_TYPE_REJECT: len = bacerror_decode_error_class_and_code(
if (apdu_len < 3) { service_request, service_request_len, &error_class,
break; &error_code);
} if ((len != 0) && (Error_Function[service_choice].error)) {
invoke_id = apdu[1]; Error_Function[service_choice].error(
reason = apdu[2]; src, invoke_id, (BACNET_ERROR_CLASS)error_class,
if (Reject_Function) { (BACNET_ERROR_CODE)error_code);
Reject_Function(src, invoke_id, reason); }
} }
tsm_free_invoke_id(invoke_id); tsm_free_invoke_id(invoke_id);
break; break;
case PDU_TYPE_ABORT: case PDU_TYPE_REJECT:
if (apdu_len < 3) { if (apdu_len < 3) {
break; break;
} }
server = apdu[0] & 0x01; invoke_id = apdu[1];
invoke_id = apdu[1]; reason = apdu[2];
reason = apdu[2]; if (Reject_Function) {
if (Abort_Function) { Reject_Function(src, invoke_id, reason);
Abort_Function(src, invoke_id, reason, server); }
} tsm_free_invoke_id(invoke_id);
tsm_free_invoke_id(invoke_id); break;
break; case PDU_TYPE_ABORT:
#endif if (apdu_len < 3) {
default: break;
break; }
} server = apdu[0] & 0x01;
invoke_id = apdu[1];
reason = apdu[2];
if (Abort_Function) {
Abort_Function(src, invoke_id, reason, server);
}
tsm_free_invoke_id(invoke_id);
break;
#endif
default:
break;
} }
return;
} }