From df79a02feb253c0a9a009bcdbb21e47581315111 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Raimbault?= <stephane.raimbault@gmail.com>
Date: Fri, 18 Oct 2024 10:47:14 +0200
Subject: [PATCH] Check length passed to modbus_reply (write_bit)

The modbus_reply function is designed to receive arguments
from modbus_receive. This patch avoid a wrong use of memcpy if
the user chooses to inject a bad length argument.

Thank you Nozomi Networks Labs Advisory for the report.

CVE: CVE-2024-10918
Upstream-Status: Backport [https://github.com/stephane/libmodbus/commit/df79a02feb253c0a9a009bcdbb21e47581315111]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 src/modbus.c             | 42 ++++++++++++++++++++++++++--------------
 tests/unit-test-client.c |  9 +++++++--
 tests/unit-test-server.c | 16 ++++++++++++---
 tests/unit-test.h.in     |  1 +
 4 files changed, 49 insertions(+), 19 deletions(-)

diff --git a/src/modbus.c b/src/modbus.c
index 5a9b867..33086ec 100644
--- a/src/modbus.c
+++ b/src/modbus.c
@@ -802,20 +802,33 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
                 ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
                 "Illegal data address 0x%0X in write_bit\n",
                 address);
+            break;
+        }
+
+        /* This check is only done here to ensure using memcpy is safe. */
+        rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+        if (rsp_length != req_length) {
+            /* Bad use of modbus_reply */
+            rsp_length = response_exception(ctx,
+                                            &sft,
+                                            MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+                                            rsp,
+                                            FALSE,
+                                            "Invalid request length (%d)\n",
+                                            req_length);
+            break;
+        }
+
+        int data = (req[offset + 3] << 8) + req[offset + 4];
+        if (data == 0xFF00 || data == 0x0) {
+            mb_mapping->tab_bits[mapping_address] = data ? ON : OFF;
+            memcpy(rsp, req, rsp_length);
         } else {
-            int data = (req[offset + 3] << 8) + req[offset + 4];
-
-            if (data == 0xFF00 || data == 0x0) {
-                mb_mapping->tab_bits[mapping_address] = data ? ON : OFF;
-                memcpy(rsp, req, req_length);
-                rsp_length = req_length;
-            } else {
-                rsp_length = response_exception(
-                    ctx, &sft,
-                    MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, FALSE,
-                    "Illegal data value 0x%0X in write_bit request at address %0X\n",
-                    data, address);
-            }
+            rsp_length = response_exception(
+                ctx, &sft,
+                MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, FALSE,
+                "Illegal data value 0x%0X in write_bit request at address %0X\n",
+                data, address);
         }
     }
         break;
@@ -1687,7 +1700,8 @@ int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
     return 0;
 }
 
-/* Get the timeout interval used by the server to wait for an indication from a client */
+/* Get the timeout interval used by the server to wait for an indication from a client
+ */
 int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
 {
     if (ctx == NULL) {
diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c
index a441766..50859f8 100644
--- a/tests/unit-test-client.c
+++ b/tests/unit-test-client.c
@@ -362,12 +362,12 @@ int main(int argc, char *argv[])
     ASSERT_TRUE(rc == -1 && errno == EMBXILADD, "");
 
     rc = modbus_write_bits(ctx, 0, 1, tab_rp_bits);
-    printf("* modbus_write_coils (0): ");
+    printf("* modbus_write_bits (0): ");
     ASSERT_TRUE(rc == -1 && errno == EMBXILADD, "");
 
     rc = modbus_write_bits(ctx, UT_BITS_ADDRESS + UT_BITS_NB,
                            UT_BITS_NB, tab_rp_bits);
-    printf("* modbus_write_coils (max): ");
+    printf("* modbus_write_bits (max): ");
     ASSERT_TRUE(rc == -1 && errno == EMBXILADD, "");
 
     rc = modbus_write_register(ctx, 0, tab_rp_registers[0]);
@@ -445,6 +445,11 @@ int main(int argc, char *argv[])
     printf("* modbus_write_registers: ");
     ASSERT_TRUE(rc == -1 && errno == EMBMDATA, "");
 
+
+    /** BAD USE OF REPLY FUNCTION **/
+    rc = modbus_write_bit(ctx, UT_BITS_ADDRESS_INVALID_REQUEST_LENGTH, ON);
+    printf("* modbus_write_bit (triggers invalid reply): ");
+    ASSERT_TRUE(rc == -1 && errno == EMBXILVAL, "");
     /** SLAVE REPLY **/
     old_slave = modbus_get_slave(ctx);
 
diff --git a/tests/unit-test-server.c b/tests/unit-test-server.c
index 561d64d..8e28124 100644
--- a/tests/unit-test-server.c
+++ b/tests/unit-test-server.c
@@ -123,9 +123,8 @@ int main(int argc, char*argv[])
             break;
         }
 
-        /* Special server behavior to test client */
-        if (query[header_length] == 0x03) {
-            /* Read holding registers */
+        /** Special server behavior to test client **/
+        if (query[header_length] == MODBUS_FC_READ_HOLDING_REGISTERS) {
 
             if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
                 == UT_REGISTERS_NB_SPECIAL) {
@@ -178,6 +177,17 @@ int main(int argc, char*argv[])
                 }
                 continue;
             }
+
+        } else if (query[header_length] == MODBUS_FC_WRITE_SINGLE_COIL) {
+            if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1) ==
+                UT_BITS_ADDRESS_INVALID_REQUEST_LENGTH) {
+                // The valid length is lengths of header + checkum + FC + address + value
+                // (max 12)
+                rc = 34;
+                printf("Special modbus_write_bit detected. Inject a wrong rc value (%d) "
+                       "in modbus_reply\n",
+                       rc);
+            }
         }
 
         rc = modbus_reply(ctx, query, rc, mb_mapping);
diff --git a/tests/unit-test.h.in b/tests/unit-test.h.in
index 5e379bb..21d09e3 100644
--- a/tests/unit-test.h.in
+++ b/tests/unit-test.h.in
@@ -28,6 +28,7 @@
 const uint16_t UT_BITS_ADDRESS = 0x130;
 const uint16_t UT_BITS_NB = 0x25;
 const uint8_t UT_BITS_TAB[] = { 0xCD, 0x6B, 0xB2, 0x0E, 0x1B };
+const uint16_t UT_BITS_ADDRESS_INVALID_REQUEST_LENGTH = UT_BITS_ADDRESS + 2;
 
 const uint16_t UT_INPUT_BITS_ADDRESS = 0x1C4;
 const uint16_t UT_INPUT_BITS_NB = 0x16;
