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_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 */
uint16_t apdu_len)
{
BACNET_PDU_TYPE pdu_type;
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
uint8_t service_choice = 0;
uint8_t *service_request = NULL;
@@ -572,157 +574,166 @@ void apdu_handler(BACNET_ADDRESS *src,
bool server = false;
#endif
if (apdu) {
/* PDU Type */
switch (apdu[0] & 0xF0) {
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
len = apdu_decode_confirmed_service_request(&apdu[0], apdu_len,
&service_data, &service_choice, &service_request,
&service_request_len);
if (len == 0) {
/* service data unable to be decoded - simply drop */
break;
}
if (apdu_confirmed_dcc_disabled(service_choice)) {
/* When network communications are completely disabled,
only DeviceCommunicationControl and ReinitializeDevice
APDUs shall be processed and no messages shall be
initiated. */
break;
}
if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) &&
(Confirmed_Function[service_choice])) {
Confirmed_Function[service_choice](service_request,
service_request_len, src, &service_data);
} else if (Unrecognized_Service_Handler) {
Unrecognized_Service_Handler(service_request,
service_request_len, src, &service_data);
}
break;
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
if (apdu_len < 2) {
break;
}
service_choice = apdu[1];
service_request = &apdu[2];
service_request_len = apdu_len - 2;
if (apdu_unconfirmed_dcc_disabled(service_choice)) {
/* When network communications are disabled,
only DeviceCommunicationControl and
ReinitializeDevice APDUs shall be processed and no
messages shall be initiated. If communications have
been initiation disabled, then WhoIs may be
processed. */
break;
}
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
if (Unconfirmed_Function[service_choice]) {
Unconfirmed_Function[service_choice](
service_request, service_request_len, src);
}
}
break;
#if !BACNET_SVC_SERVER
case PDU_TYPE_SIMPLE_ACK:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
service_choice = apdu[2];
if (apdu_confirmed_simple_ack_service(service_choice)) {
if (Confirmed_ACK_Function[service_choice].simple !=
NULL) {
Confirmed_ACK_Function[service_choice].simple(
src, invoke_id);
}
tsm_free_invoke_id(invoke_id);
}
break;
case PDU_TYPE_COMPLEX_ACK:
if (apdu_len < 3) {
break;
}
service_ack_data.segmented_message =
(apdu[0] & BIT(3)) ? true : false;
service_ack_data.more_follows =
(apdu[0] & BIT(2)) ? true : false;
invoke_id = service_ack_data.invoke_id = apdu[1];
len = 2;
if (service_ack_data.segmented_message) {
service_ack_data.sequence_number = apdu[len++];
service_ack_data.proposed_window_number = apdu[len++];
}
service_choice = apdu[len++];
service_request = &apdu[len];
service_request_len = apdu_len - (uint16_t)len;
if (!apdu_confirmed_simple_ack_service(service_choice)) {
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
if (Confirmed_ACK_Function[service_choice]
.complex != NULL) {
Confirmed_ACK_Function[service_choice].complex(
service_request, service_request_len, src,
&service_ack_data);
}
}
tsm_free_invoke_id(invoke_id);
}
break;
case PDU_TYPE_SEGMENT_ACK:
/* FIXME: what about a denial of service attack here?
we could check src to see if that matched the tsm */
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_ERROR:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
service_choice = apdu[2];
if (apdu_complex_error(service_choice)) {
if (Error_Function[service_choice].complex) {
Error_Function[service_choice].complex(src,
invoke_id, service_choice, &apdu[3],
apdu_len - 3);
}
} else if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
len = bacerror_decode_error_class_and_code(
&apdu[3], apdu_len - 3, &error_class, &error_code);
if ((len != 0) &&
(Error_Function[service_choice].error)) {
Error_Function[service_choice].error(src, invoke_id,
(BACNET_ERROR_CLASS)error_class,
(BACNET_ERROR_CODE)error_code);
}
}
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_REJECT:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
reason = apdu[2];
if (Reject_Function) {
Reject_Function(src, invoke_id, reason);
}
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_ABORT:
if (apdu_len < 3) {
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;
}
if (!apdu) {
return;
}
if (apdu_len == 0) {
return;
}
pdu_type = apdu[0] & 0xF0;
switch (pdu_type) {
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
len = apdu_decode_confirmed_service_request(
apdu, apdu_len, &service_data, &service_choice,
&service_request, &service_request_len);
if (len == 0) {
/* service data unable to be decoded - simply drop */
break;
}
if (apdu_confirmed_dcc_disabled(service_choice)) {
/* When network communications are completely disabled,
only DeviceCommunicationControl and ReinitializeDevice
APDUs shall be processed and no messages shall be
initiated. */
break;
}
if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) &&
(Confirmed_Function[service_choice])) {
Confirmed_Function[service_choice](
service_request, service_request_len, src, &service_data);
} else if (Unrecognized_Service_Handler) {
Unrecognized_Service_Handler(
service_request, service_request_len, src, &service_data);
}
break;
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
if (apdu_len < 2) {
break;
}
service_choice = apdu[1];
/* prepare the service request buffer and length */
service_request_len = apdu_len - 2;
service_request = &apdu[2];
if (apdu_unconfirmed_dcc_disabled(service_choice)) {
/* When network communications are disabled,
only DeviceCommunicationControl and
ReinitializeDevice APDUs shall be processed and no
messages shall be initiated. If communications have
been initiation disabled, then WhoIs may be
processed. */
break;
}
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
if (Unconfirmed_Function[service_choice]) {
Unconfirmed_Function[service_choice](
service_request, service_request_len, src);
}
}
break;
#if !BACNET_SVC_SERVER
case PDU_TYPE_SIMPLE_ACK:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
service_choice = apdu[2];
if (apdu_confirmed_simple_ack_service(service_choice)) {
if (Confirmed_ACK_Function[service_choice].simple != NULL) {
Confirmed_ACK_Function[service_choice].simple(
src, invoke_id);
}
tsm_free_invoke_id(invoke_id);
}
break;
case PDU_TYPE_COMPLEX_ACK:
if (apdu_len < 3) {
break;
}
service_ack_data.segmented_message =
(apdu[0] & BIT(3)) ? true : false;
service_ack_data.more_follows = (apdu[0] & BIT(2)) ? true : false;
invoke_id = service_ack_data.invoke_id = apdu[1];
len = 2;
if (service_ack_data.segmented_message) {
if (apdu_len < 5) {
break;
}
service_ack_data.sequence_number = apdu[len++];
service_ack_data.proposed_window_number = apdu[len++];
}
service_choice = apdu[len++];
/* prepare the service request buffer and length */
service_request_len = apdu_len - (uint16_t)len;
service_request = &apdu[len];
if (!apdu_confirmed_simple_ack_service(service_choice)) {
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
if (Confirmed_ACK_Function[service_choice].complex !=
NULL) {
Confirmed_ACK_Function[service_choice].complex(
service_request, service_request_len, src,
&service_ack_data);
}
}
tsm_free_invoke_id(invoke_id);
}
break;
case PDU_TYPE_SEGMENT_ACK:
/* FIXME: what about a denial of service attack here?
we could check src to see if that matched the tsm */
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_ERROR:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
service_choice = apdu[2];
/* prepare the service request buffer and length */
service_request_len = apdu_len - 3;
service_request = &apdu[3];
if (apdu_complex_error(service_choice)) {
if (Error_Function[service_choice].complex) {
Error_Function[service_choice].complex(
src, invoke_id, service_choice, service_request,
service_request_len);
}
} else if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
len = bacerror_decode_error_class_and_code(
service_request, service_request_len, &error_class,
&error_code);
if ((len != 0) && (Error_Function[service_choice].error)) {
Error_Function[service_choice].error(
src, invoke_id, (BACNET_ERROR_CLASS)error_class,
(BACNET_ERROR_CODE)error_code);
}
}
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_REJECT:
if (apdu_len < 3) {
break;
}
invoke_id = apdu[1];
reason = apdu[2];
if (Reject_Function) {
Reject_Function(src, invoke_id, reason);
}
tsm_free_invoke_id(invoke_id);
break;
case PDU_TYPE_ABORT:
if (apdu_len < 3) {
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;
}