Commit 83c3410267d1383b36e0928d994fefdd634f1a1d

Authored by Stéphane Raimbault
1 parent 29851c2f

Fix remote buffer overflow vulnerability on write requests

Protects against crafted write requests with a large quantity but a small
byte count. If address + quantity was in the mapping space of the server
and quantity greater than the response size, it was possible to crash
the server.

The sleep/flush sequence improves the handling of following requests.
src/modbus.c
@@ -718,6 +718,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -718,6 +718,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
718 "Illegal nb of values %d in read_bits (max %d)\n", 718 "Illegal nb of values %d in read_bits (max %d)\n",
719 nb, MODBUS_MAX_READ_BITS); 719 nb, MODBUS_MAX_READ_BITS);
720 } 720 }
  721 + _sleep_response_timeout(ctx);
  722 + modbus_flush(ctx);
721 rsp_length = response_exception( 723 rsp_length = response_exception(
722 ctx, &sft, 724 ctx, &sft,
723 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp); 725 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
@@ -749,6 +751,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -749,6 +751,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
749 "Illegal nb of values %d in read_input_bits (max %d)\n", 751 "Illegal nb of values %d in read_input_bits (max %d)\n",
750 nb, MODBUS_MAX_READ_BITS); 752 nb, MODBUS_MAX_READ_BITS);
751 } 753 }
  754 + _sleep_response_timeout(ctx);
  755 + modbus_flush(ctx);
752 rsp_length = response_exception( 756 rsp_length = response_exception(
753 ctx, &sft, 757 ctx, &sft,
754 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp); 758 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
@@ -778,6 +782,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -778,6 +782,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
778 "Illegal nb of values %d in read_holding_registers (max %d)\n", 782 "Illegal nb of values %d in read_holding_registers (max %d)\n",
779 nb, MODBUS_MAX_READ_REGISTERS); 783 nb, MODBUS_MAX_READ_REGISTERS);
780 } 784 }
  785 + _sleep_response_timeout(ctx);
  786 + modbus_flush(ctx);
781 rsp_length = response_exception( 787 rsp_length = response_exception(
782 ctx, &sft, 788 ctx, &sft,
783 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp); 789 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
@@ -812,6 +818,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -812,6 +818,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
812 "Illegal number of values %d in read_input_registers (max %d)\n", 818 "Illegal number of values %d in read_input_registers (max %d)\n",
813 nb, MODBUS_MAX_READ_REGISTERS); 819 nb, MODBUS_MAX_READ_REGISTERS);
814 } 820 }
  821 + _sleep_response_timeout(ctx);
  822 + modbus_flush(ctx);
815 rsp_length = response_exception( 823 rsp_length = response_exception(
816 ctx, &sft, 824 ctx, &sft,
817 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp); 825 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
@@ -842,6 +850,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -842,6 +850,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
842 "Illegal data address %0X in write_bit\n", 850 "Illegal data address %0X in write_bit\n",
843 address); 851 address);
844 } 852 }
  853 + _sleep_response_timeout(ctx);
  854 + modbus_flush(ctx);
845 rsp_length = response_exception( 855 rsp_length = response_exception(
846 ctx, &sft, 856 ctx, &sft,
847 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp); 857 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp);
@@ -870,6 +880,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -870,6 +880,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
870 fprintf(stderr, "Illegal data address %0X in write_register\n", 880 fprintf(stderr, "Illegal data address %0X in write_register\n",
871 address); 881 address);
872 } 882 }
  883 + _sleep_response_timeout(ctx);
  884 + modbus_flush(ctx);
873 rsp_length = response_exception( 885 rsp_length = response_exception(
874 ctx, &sft, 886 ctx, &sft,
875 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp); 887 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp);
@@ -884,7 +896,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -884,7 +896,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
884 case MODBUS_FC_WRITE_MULTIPLE_COILS: { 896 case MODBUS_FC_WRITE_MULTIPLE_COILS: {
885 int nb = (req[offset + 3] << 8) + req[offset + 4]; 897 int nb = (req[offset + 3] << 8) + req[offset + 4];
886 898
887 - if ((address + nb) > mb_mapping->nb_bits) { 899 + if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb) {
  900 + if (ctx->debug) {
  901 + fprintf(stderr,
  902 + "Illegal number of values %d in write_bits (max %d)\n",
  903 + nb, MODBUS_MAX_WRITE_BITS);
  904 + }
  905 + /* May be the indication has been truncated on reading because of
  906 + * invalid address (eg. nb is 0 but the request contains values to
  907 + * write) so it's necessary to flush. */
  908 + _sleep_response_timeout(ctx);
  909 + modbus_flush(ctx);
  910 + rsp_length = response_exception(
  911 + ctx, &sft,
  912 + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
  913 + } else if ((address + nb) > mb_mapping->nb_bits) {
888 if (ctx->debug) { 914 if (ctx->debug) {
889 fprintf(stderr, "Illegal data address %0X in write_bits\n", 915 fprintf(stderr, "Illegal data address %0X in write_bits\n",
890 address + nb); 916 address + nb);
@@ -905,8 +931,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -905,8 +931,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
905 break; 931 break;
906 case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { 932 case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
907 int nb = (req[offset + 3] << 8) + req[offset + 4]; 933 int nb = (req[offset + 3] << 8) + req[offset + 4];
908 -  
909 - if ((address + nb) > mb_mapping->nb_registers) { 934 + if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb) {
  935 + if (ctx->debug) {
  936 + fprintf(stderr,
  937 + "Illegal number of values %d in write_registers (max %d)\n",
  938 + nb, MODBUS_MAX_WRITE_REGISTERS);
  939 + }
  940 + /* May be the indication has been truncated on reading because of
  941 + * invalid address (eg. nb is 0 but the request contains values to
  942 + * write) so it's necessary to flush. */
  943 + _sleep_response_timeout(ctx);
  944 + modbus_flush(ctx);
  945 + rsp_length = response_exception(
  946 + ctx, &sft,
  947 + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
  948 + } else if ((address + nb) > mb_mapping->nb_registers) {
910 if (ctx->debug) { 949 if (ctx->debug) {
911 fprintf(stderr, "Illegal data address %0X in write_registers\n", 950 fprintf(stderr, "Illegal data address %0X in write_registers\n",
912 address + nb); 951 address + nb);
@@ -988,6 +1027,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, @@ -988,6 +1027,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
988 nb_write, nb, 1027 nb_write, nb,
989 MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS); 1028 MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS);
990 } 1029 }
  1030 + _sleep_response_timeout(ctx);
  1031 + modbus_flush(ctx);
991 rsp_length = response_exception( 1032 rsp_length = response_exception(
992 ctx, &sft, 1033 ctx, &sft,
993 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp); 1034 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
tests/unit-test-client.c
@@ -30,7 +30,11 @@ enum { @@ -30,7 +30,11 @@ enum {
30 RTU 30 RTU
31 }; 31 };
32 32
33 -int test_raw_request(modbus_t *, int); 33 +int test_server(modbus_t *ctx, int use_backend);
  34 +int send_crafted_request(modbus_t *ctx, int function,
  35 + uint8_t *req, int req_size,
  36 + uint16_t max_value, uint16_t bytes,
  37 + int backend_length, int backend_offset);
34 38
35 #define BUG_REPORT(_cond, _format, _args ...) \ 39 #define BUG_REPORT(_cond, _format, _args ...) \
36 printf("\nLine %d: assertion error for '%s': " _format "\n", __LINE__, # _cond, ## _args) 40 printf("\nLine %d: assertion error for '%s': " _format "\n", __LINE__, # _cond, ## _args)
@@ -205,7 +209,6 @@ int main(int argc, char *argv[]) @@ -205,7 +209,6 @@ int main(int argc, char *argv[])
205 ASSERT_TRUE(rc == 1, "FAILED (nb points %d)\n", rc); 209 ASSERT_TRUE(rc == 1, "FAILED (nb points %d)\n", rc);
206 ASSERT_TRUE(tab_rp_registers[0] == 0x1234, "FAILED (%0X != %0X)\n", 210 ASSERT_TRUE(tab_rp_registers[0] == 0x1234, "FAILED (%0X != %0X)\n",
207 tab_rp_registers[0], 0x1234); 211 tab_rp_registers[0], 0x1234);
208 -  
209 /* End of single register */ 212 /* End of single register */
210 213
211 /* Many registers */ 214 /* Many registers */
@@ -509,7 +512,7 @@ int main(int argc, char *argv[]) @@ -509,7 +512,7 @@ int main(int argc, char *argv[])
509 512
510 /* A wait and flush operation is done by the error recovery code of 513 /* A wait and flush operation is done by the error recovery code of
511 * libmodbus but after a sleep of current response timeout 514 * libmodbus but after a sleep of current response timeout
512 - * so 0 can't be too short! 515 + * so 0 can be too short!
513 */ 516 */
514 usleep(old_response_to_sec * 1000000 + old_response_to_usec); 517 usleep(old_response_to_sec * 1000000 + old_response_to_usec);
515 modbus_flush(ctx); 518 modbus_flush(ctx);
@@ -590,8 +593,8 @@ int main(int argc, char *argv[]) @@ -590,8 +593,8 @@ int main(int argc, char *argv[])
590 printf("* modbus_read_registers at special address: "); 593 printf("* modbus_read_registers at special address: ");
591 ASSERT_TRUE(rc == -1 && errno == EMBXSBUSY, ""); 594 ASSERT_TRUE(rc == -1 && errno == EMBXSBUSY, "");
592 595
593 - /** RAW REQUEST */  
594 - if (test_raw_request(ctx, use_backend) == -1) { 596 + /** SERVER **/
  597 + if (test_server(ctx, use_backend) == -1) {
595 goto close; 598 goto close;
596 } 599 }
597 600
@@ -617,19 +620,23 @@ close: @@ -617,19 +620,23 @@ close:
617 return 0; 620 return 0;
618 } 621 }
619 622
620 -int test_raw_request(modbus_t *ctx, int use_backend) 623 +/* Send crafted requests to test server resilience
  624 + and ensure proper exceptions are returned. */
  625 +int test_server(modbus_t *ctx, int use_backend)
621 { 626 {
622 int rc; 627 int rc;
623 - int i, j;  
624 - const int RAW_REQ_LENGTH = 6;  
625 - uint8_t raw_req[] = { 628 + int i;
  629 + /* Read requests */
  630 + const int READ_RAW_REQ_LEN = 6;
  631 + uint8_t read_raw_req[] = {
626 /* slave */ 632 /* slave */
627 (use_backend == RTU) ? SERVER_ID : 0xFF, 633 (use_backend == RTU) ? SERVER_ID : 0xFF,
628 /* function, addr 1, 5 values */ 634 /* function, addr 1, 5 values */
629 - MODBUS_FC_READ_HOLDING_REGISTERS, 0x00, 0x01, 0x0, 0x05, 635 + MODBUS_FC_READ_HOLDING_REGISTERS, 0x00, 0x01, 0x0, 0x05
630 }; 636 };
631 /* Write and read registers request */ 637 /* Write and read registers request */
632 - uint8_t raw_rw_req[] = { 638 + const int RW_RAW_REQ_LEN = 13;
  639 + uint8_t rw_raw_req[] = {
633 /* slave */ 640 /* slave */
634 (use_backend == RTU) ? SERVER_ID : 0xFF, 641 (use_backend == RTU) ? SERVER_ID : 0xFF,
635 /* function, addr to read, nb to read */ 642 /* function, addr to read, nb to read */
@@ -646,104 +653,138 @@ int test_raw_request(modbus_t *ctx, int use_backend) @@ -646,104 +653,138 @@ int test_raw_request(modbus_t *ctx, int use_backend)
646 /* One data to write... */ 653 /* One data to write... */
647 0x12, 0x34 654 0x12, 0x34
648 }; 655 };
649 - /* See issue #143, test with MAX_WR_WRITE_REGISTERS */ 656 + const int WRITE_RAW_REQ_LEN = 13;
  657 + uint8_t write_raw_req[] = {
  658 + /* slave */
  659 + (use_backend == RTU) ? SERVER_ID : 0xFF,
  660 + /* function will be set in the loop */
  661 + MODBUS_FC_WRITE_MULTIPLE_REGISTERS,
  662 + /* Address */
  663 + UT_REGISTERS_ADDRESS >> 8,
  664 + UT_REGISTERS_ADDRESS & 0xFF,
  665 + /* 3 values, 6 bytes */
  666 + 0x00, 0x03, 0x06,
  667 + /* Dummy data to write */
  668 + 0x02, 0x2B, 0x00, 0x01, 0x00, 0x64
  669 + };
650 int req_length; 670 int req_length;
651 uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH]; 671 uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
652 - int tab_function[] = { 672 + int tab_read_function[] = {
653 MODBUS_FC_READ_COILS, 673 MODBUS_FC_READ_COILS,
654 MODBUS_FC_READ_DISCRETE_INPUTS, 674 MODBUS_FC_READ_DISCRETE_INPUTS,
655 MODBUS_FC_READ_HOLDING_REGISTERS, 675 MODBUS_FC_READ_HOLDING_REGISTERS,
656 MODBUS_FC_READ_INPUT_REGISTERS 676 MODBUS_FC_READ_INPUT_REGISTERS
657 }; 677 };
658 - int tab_nb_max[] = { 678 + int tab_read_nb_max[] = {
659 MODBUS_MAX_READ_BITS + 1, 679 MODBUS_MAX_READ_BITS + 1,
660 MODBUS_MAX_READ_BITS + 1, 680 MODBUS_MAX_READ_BITS + 1,
661 MODBUS_MAX_READ_REGISTERS + 1, 681 MODBUS_MAX_READ_REGISTERS + 1,
662 MODBUS_MAX_READ_REGISTERS + 1 682 MODBUS_MAX_READ_REGISTERS + 1
663 }; 683 };
664 - int length;  
665 - int offset;  
666 - const int EXCEPTION_RC = 2; 684 + int backend_length;
  685 + int backend_offset;
667 686
668 if (use_backend == RTU) { 687 if (use_backend == RTU) {
669 - length = 3;  
670 - offset = 1; 688 + backend_length = 3;
  689 + backend_offset = 1;
671 } else { 690 } else {
672 - length = 7;  
673 - offset = 7; 691 + backend_length = 7;
  692 + backend_offset = 7;
674 } 693 }
675 694
676 printf("\nTEST RAW REQUESTS:\n"); 695 printf("\nTEST RAW REQUESTS:\n");
677 696
678 - req_length = modbus_send_raw_request(ctx, raw_req,  
679 - RAW_REQ_LENGTH * sizeof(uint8_t)); 697 + req_length = modbus_send_raw_request(ctx, read_raw_req, READ_RAW_REQ_LEN);
680 printf("* modbus_send_raw_request: "); 698 printf("* modbus_send_raw_request: ");
681 - ASSERT_TRUE(req_length == (length + 5), "FAILED (%d)\n", req_length); 699 + ASSERT_TRUE(req_length == (backend_length + 5), "FAILED (%d)\n", req_length);
682 700
683 printf("* modbus_receive_confirmation: "); 701 printf("* modbus_receive_confirmation: ");
684 - rc = modbus_receive_confirmation(ctx, rsp);  
685 - ASSERT_TRUE(rc == (length + 12), "FAILED (%d)\n", rc);  
686 -  
687 - /* Try to crash server with raw requests to bypass checks of client. */  
688 -  
689 - /* Address */  
690 - raw_req[2] = 0;  
691 - raw_req[3] = 0; 702 + rc = modbus_receive_confirmation(ctx, rsp);
  703 + ASSERT_TRUE(rc == (backend_length + 12), "FAILED (%d)\n", rc);
692 704
693 /* Try to read more values than a response could hold for all data 705 /* Try to read more values than a response could hold for all data
694 * types. 706 * types.
695 */ 707 */
696 for (i=0; i<4; i++) { 708 for (i=0; i<4; i++) {
697 - raw_req[1] = tab_function[i];  
698 -  
699 - for (j=0; j<2; j++) {  
700 - if (j == 0) {  
701 - /* Try to read zero values on first iteration */  
702 - raw_req[4] = 0x00;  
703 - raw_req[5] = 0x00;  
704 - } else {  
705 - /* Try to read max values + 1 on second iteration */  
706 - raw_req[4] = (tab_nb_max[i] >> 8) & 0xFF;  
707 - raw_req[5] = tab_nb_max[i] & 0xFF;  
708 - }  
709 -  
710 - req_length = modbus_send_raw_request(ctx, raw_req,  
711 - RAW_REQ_LENGTH * sizeof(uint8_t));  
712 - if (j == 0) {  
713 - printf("* try to read 0 values with function %d: ", tab_function[i]);  
714 - } else {  
715 - printf("* try an exploit with function %d: ", tab_function[i]);  
716 - }  
717 - rc = modbus_receive_confirmation(ctx, rsp);  
718 - ASSERT_TRUE(rc == (length + EXCEPTION_RC) &&  
719 - rsp[offset] == (0x80 + tab_function[i]) &&  
720 - rsp[offset + 1] == MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, "");  
721 - } 709 + rc = send_crafted_request(ctx, tab_read_function[i],
  710 + read_raw_req, READ_RAW_REQ_LEN,
  711 + tab_read_nb_max[i], 0,
  712 + backend_length, backend_offset);
  713 + if (rc == -1)
  714 + goto close;
722 } 715 }
723 716
724 /* Modbus write and read multiple registers */ 717 /* Modbus write and read multiple registers */
725 - i = 0;  
726 - tab_function[i] = MODBUS_FC_WRITE_AND_READ_REGISTERS;  
727 - for (j=0; j<2; j++) { 718 + rc = send_crafted_request(ctx, MODBUS_FC_WRITE_AND_READ_REGISTERS,
  719 + rw_raw_req, RW_RAW_REQ_LEN,
  720 + MODBUS_MAX_WR_READ_REGISTERS + 1, 0,
  721 + backend_length, backend_offset);
  722 + if (rc == -1)
  723 + goto close;
  724 +
  725 + /* Modbus write multiple registers with large number of values but a set a
  726 + small number of bytes in requests (not nb * 2 as usual). */
  727 + rc = send_crafted_request(ctx, MODBUS_FC_WRITE_MULTIPLE_REGISTERS,
  728 + write_raw_req, WRITE_RAW_REQ_LEN,
  729 + MODBUS_MAX_WRITE_REGISTERS + 1, 6,
  730 + backend_length, backend_offset);
  731 + if (rc == -1)
  732 + goto close;
  733 +
  734 + rc = send_crafted_request(ctx, MODBUS_FC_WRITE_MULTIPLE_COILS,
  735 + write_raw_req, WRITE_RAW_REQ_LEN,
  736 + MODBUS_MAX_WRITE_BITS + 1, 6,
  737 + backend_length, backend_offset);
  738 + if (rc == -1)
  739 + goto close;
  740 +
  741 + return 0;
  742 +close:
  743 + return -1;
  744 +}
  745 +
  746 +
  747 +int send_crafted_request(modbus_t *ctx, int function,
  748 + uint8_t *req, int req_len,
  749 + uint16_t max_value, uint16_t bytes,
  750 + int backend_length, int backend_offset)
  751 +{
  752 + const int EXCEPTION_RC = 2;
  753 + uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
  754 +
  755 + for (int j=0; j<2; j++) {
  756 + int rc;
  757 +
  758 + req[1] = function;
728 if (j == 0) { 759 if (j == 0) {
729 - /* Try to read zero values on first iteration */  
730 - raw_rw_req[4] = 0x00;  
731 - raw_rw_req[5] = 0x00; 760 + /* Try to read or write zero values on first iteration */
  761 + req[4] = 0x00;
  762 + req[5] = 0x00;
  763 + if (bytes) {
  764 + /* Write query */
  765 + req[6] = 0x00;
  766 + }
732 } else { 767 } else {
733 - /* Try to read max values + 1 on second iteration */  
734 - raw_rw_req[4] = (MODBUS_MAX_WR_READ_REGISTERS + 1) >> 8;  
735 - raw_rw_req[5] = (MODBUS_MAX_WR_READ_REGISTERS + 1) & 0xFF; 768 + /* Try to read or write max values + 1 on second iteration */
  769 + req[4] = (max_value >> 8) & 0xFF;
  770 + req[5] = max_value & 0xFF;
  771 + if (bytes) {
  772 + /* Write query (nb values * 2 to convert in bytes for registers) */
  773 + req[6] = bytes;
  774 + }
736 } 775 }
737 - req_length = modbus_send_raw_request(ctx, raw_rw_req, 13 * sizeof(uint8_t)); 776 +
  777 + modbus_send_raw_request(ctx, req, req_len * sizeof(uint8_t));
738 if (j == 0) { 778 if (j == 0) {
739 - printf("* try to read 0 values with function %d: ", tab_function[i]); 779 + printf("* try function 0x%X: %s 0 values: ", function, bytes ? "write": "read");
740 } else { 780 } else {
741 - printf("* try an exploit with function %d: ", tab_function[i]); 781 + printf("* try function 0x%X: %s %d values: ", function, bytes ? "write": "read",
  782 + max_value);
742 } 783 }
743 rc = modbus_receive_confirmation(ctx, rsp); 784 rc = modbus_receive_confirmation(ctx, rsp);
744 - ASSERT_TRUE(rc == length + EXCEPTION_RC &&  
745 - rsp[offset] == (0x80 + tab_function[i]) &&  
746 - rsp[offset + 1] == MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, ""); 785 + ASSERT_TRUE(rc == (backend_length + EXCEPTION_RC) &&
  786 + rsp[backend_offset] == (0x80 + function) &&
  787 + rsp[backend_offset + 1] == MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, "");
747 } 788 }
748 789
749 return 0; 790 return 0;
tests/unit-test-server.c
@@ -156,12 +156,15 @@ int main(int argc, char*argv[]) @@ -156,12 +156,15 @@ int main(int argc, char*argv[])
156 156
157 if (rc == -1) { 157 if (rc == -1) {
158 /* Connection closed by the client or error */ 158 /* Connection closed by the client or error */
  159 + /* We could answer with an exception on EMBBADDATA to indicate
  160 + illegal data for example */
159 break; 161 break;
160 } 162 }
161 163
162 -  
163 - /* Read holding registers */ 164 + /* Special server behavior to test client */
164 if (query[header_length] == 0x03) { 165 if (query[header_length] == 0x03) {
  166 + /* Read holding registers */
  167 +
165 if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3) 168 if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
166 == UT_REGISTERS_NB_SPECIAL) { 169 == UT_REGISTERS_NB_SPECIAL) {
167 printf("Set an incorrect number of values\n"); 170 printf("Set an incorrect number of values\n");