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 718 "Illegal nb of values %d in read_bits (max %d)\n",
719 719 nb, MODBUS_MAX_READ_BITS);
720 720 }
  721 + _sleep_response_timeout(ctx);
  722 + modbus_flush(ctx);
721 723 rsp_length = response_exception(
722 724 ctx, &sft,
723 725 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
... ... @@ -749,6 +751,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
749 751 "Illegal nb of values %d in read_input_bits (max %d)\n",
750 752 nb, MODBUS_MAX_READ_BITS);
751 753 }
  754 + _sleep_response_timeout(ctx);
  755 + modbus_flush(ctx);
752 756 rsp_length = response_exception(
753 757 ctx, &sft,
754 758 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
... ... @@ -778,6 +782,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
778 782 "Illegal nb of values %d in read_holding_registers (max %d)\n",
779 783 nb, MODBUS_MAX_READ_REGISTERS);
780 784 }
  785 + _sleep_response_timeout(ctx);
  786 + modbus_flush(ctx);
781 787 rsp_length = response_exception(
782 788 ctx, &sft,
783 789 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
... ... @@ -812,6 +818,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
812 818 "Illegal number of values %d in read_input_registers (max %d)\n",
813 819 nb, MODBUS_MAX_READ_REGISTERS);
814 820 }
  821 + _sleep_response_timeout(ctx);
  822 + modbus_flush(ctx);
815 823 rsp_length = response_exception(
816 824 ctx, &sft,
817 825 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
... ... @@ -842,6 +850,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
842 850 "Illegal data address %0X in write_bit\n",
843 851 address);
844 852 }
  853 + _sleep_response_timeout(ctx);
  854 + modbus_flush(ctx);
845 855 rsp_length = response_exception(
846 856 ctx, &sft,
847 857 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp);
... ... @@ -870,6 +880,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
870 880 fprintf(stderr, "Illegal data address %0X in write_register\n",
871 881 address);
872 882 }
  883 + _sleep_response_timeout(ctx);
  884 + modbus_flush(ctx);
873 885 rsp_length = response_exception(
874 886 ctx, &sft,
875 887 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp);
... ... @@ -884,7 +896,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
884 896 case MODBUS_FC_WRITE_MULTIPLE_COILS: {
885 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 914 if (ctx->debug) {
889 915 fprintf(stderr, "Illegal data address %0X in write_bits\n",
890 916 address + nb);
... ... @@ -905,8 +931,21 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
905 931 break;
906 932 case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
907 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 949 if (ctx->debug) {
911 950 fprintf(stderr, "Illegal data address %0X in write_registers\n",
912 951 address + nb);
... ... @@ -988,6 +1027,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
988 1027 nb_write, nb,
989 1028 MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS);
990 1029 }
  1030 + _sleep_response_timeout(ctx);
  1031 + modbus_flush(ctx);
991 1032 rsp_length = response_exception(
992 1033 ctx, &sft,
993 1034 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp);
... ...
tests/unit-test-client.c
... ... @@ -30,7 +30,11 @@ enum {
30 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 39 #define BUG_REPORT(_cond, _format, _args ...) \
36 40 printf("\nLine %d: assertion error for '%s': " _format "\n", __LINE__, # _cond, ## _args)
... ... @@ -205,7 +209,6 @@ int main(int argc, char *argv[])
205 209 ASSERT_TRUE(rc == 1, "FAILED (nb points %d)\n", rc);
206 210 ASSERT_TRUE(tab_rp_registers[0] == 0x1234, "FAILED (%0X != %0X)\n",
207 211 tab_rp_registers[0], 0x1234);
208   -
209 212 /* End of single register */
210 213  
211 214 /* Many registers */
... ... @@ -509,7 +512,7 @@ int main(int argc, char *argv[])
509 512  
510 513 /* A wait and flush operation is done by the error recovery code of
511 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 517 usleep(old_response_to_sec * 1000000 + old_response_to_usec);
515 518 modbus_flush(ctx);
... ... @@ -590,8 +593,8 @@ int main(int argc, char *argv[])
590 593 printf("* modbus_read_registers at special address: ");
591 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 598 goto close;
596 599 }
597 600  
... ... @@ -617,19 +620,23 @@ close:
617 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 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 632 /* slave */
627 633 (use_backend == RTU) ? SERVER_ID : 0xFF,
628 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 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 640 /* slave */
634 641 (use_backend == RTU) ? SERVER_ID : 0xFF,
635 642 /* function, addr to read, nb to read */
... ... @@ -646,104 +653,138 @@ int test_raw_request(modbus_t *ctx, int use_backend)
646 653 /* One data to write... */
647 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 670 int req_length;
651 671 uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
652   - int tab_function[] = {
  672 + int tab_read_function[] = {
653 673 MODBUS_FC_READ_COILS,
654 674 MODBUS_FC_READ_DISCRETE_INPUTS,
655 675 MODBUS_FC_READ_HOLDING_REGISTERS,
656 676 MODBUS_FC_READ_INPUT_REGISTERS
657 677 };
658   - int tab_nb_max[] = {
  678 + int tab_read_nb_max[] = {
659 679 MODBUS_MAX_READ_BITS + 1,
660 680 MODBUS_MAX_READ_BITS + 1,
661 681 MODBUS_MAX_READ_REGISTERS + 1,
662 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 687 if (use_backend == RTU) {
669   - length = 3;
670   - offset = 1;
  688 + backend_length = 3;
  689 + backend_offset = 1;
671 690 } else {
672   - length = 7;
673   - offset = 7;
  691 + backend_length = 7;
  692 + backend_offset = 7;
674 693 }
675 694  
676 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 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 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 705 /* Try to read more values than a response could hold for all data
694 706 * types.
695 707 */
696 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 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 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 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 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 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 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 790 return 0;
... ...
tests/unit-test-server.c
... ... @@ -156,12 +156,15 @@ int main(int argc, char*argv[])
156 156  
157 157 if (rc == -1) {
158 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 161 break;
160 162 }
161 163  
162   -
163   - /* Read holding registers */
  164 + /* Special server behavior to test client */
164 165 if (query[header_length] == 0x03) {
  166 + /* Read holding registers */
  167 +
165 168 if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
166 169 == UT_REGISTERS_NB_SPECIAL) {
167 170 printf("Set an incorrect number of values\n");
... ...