Commit 83c3410267d1383b36e0928d994fefdd634f1a1d
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.
Showing
3 changed files
with
162 additions
and
77 deletions
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"); |