diff options
author | Nicholas Bellinger <nab@linux-iscsi.org> | 2017-05-25 00:47:09 -0400 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2017-05-31 18:12:31 -0400 |
commit | 25cdda95fda78d22d44157da15aa7ea34be3c804 (patch) | |
tree | fd8a89f00e89c70cb8bb4798ee3857de22221694 | |
parent | f3cdbe39b2ab0636dec0d5d43b54f1061ce7566c (diff) |
iscsi-target: Fix initial login PDU asynchronous socket close OOPs
This patch fixes a OOPs originally introduced by:
commit bb048357dad6d604520c91586334c9c230366a14
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date: Thu Sep 5 14:54:04 2013 -0700
iscsi-target: Add sk->sk_state_change to cleanup after TCP failure
which would trigger a NULL pointer dereference when a TCP connection
was closed asynchronously via iscsi_target_sk_state_change(), but only
when the initial PDU processing in iscsi_target_do_login() from iscsi_np
process context was blocked waiting for backend I/O to complete.
To address this issue, this patch makes the following changes.
First, it introduces some common helper functions used for checking
socket closing state, checking login_flags, and atomically checking
socket closing state + setting login_flags.
Second, it introduces a LOGIN_FLAGS_INITIAL_PDU bit to know when a TCP
connection has dropped via iscsi_target_sk_state_change(), but the
initial PDU processing within iscsi_target_do_login() in iscsi_np
context is still running. For this case, it sets LOGIN_FLAGS_CLOSED,
but doesn't invoke schedule_delayed_work().
The original NULL pointer dereference case reported by MNC is now handled
by iscsi_target_do_login() doing a iscsi_target_sk_check_close() before
transitioning to FFP to determine when the socket has already closed,
or iscsi_target_start_negotiation() if the login needs to exchange
more PDUs (eg: iscsi_target_do_login returned 0) but the socket has
closed. For both of these cases, the cleanup up of remaining connection
resources will occur in iscsi_target_start_negotiation() from iscsi_np
process context once the failure is detected.
Finally, to handle to case where iscsi_target_sk_state_change() is
called after the initial PDU procesing is complete, it now invokes
conn->login_work -> iscsi_target_do_login_rx() to perform cleanup once
existing iscsi_target_sk_check_close() checks detect connection failure.
For this case, the cleanup of remaining connection resources will occur
in iscsi_target_do_login_rx() from delayed workqueue process context
once the failure is detected.
Reported-by: Mike Christie <mchristi@redhat.com>
Reviewed-by: Mike Christie <mchristi@redhat.com>
Tested-by: Mike Christie <mchristi@redhat.com>
Cc: Mike Christie <mchristi@redhat.com>
Reported-by: Hannes Reinecke <hare@suse.com>
Cc: Hannes Reinecke <hare@suse.com>
Cc: Sagi Grimberg <sagi@grimberg.me>
Cc: Varun Prakash <varun@chelsio.com>
Cc: <stable@vger.kernel.org> # v3.12+
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
-rw-r--r-- | drivers/target/iscsi/iscsi_target_nego.c | 194 | ||||
-rw-r--r-- | include/target/iscsi/iscsi_target_core.h | 1 |
2 files changed, 133 insertions, 62 deletions
diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c index 7ccc9c1cbfd1..6f88b31242b0 100644 --- a/drivers/target/iscsi/iscsi_target_nego.c +++ b/drivers/target/iscsi/iscsi_target_nego.c | |||
@@ -493,14 +493,60 @@ static void iscsi_target_restore_sock_callbacks(struct iscsi_conn *conn) | |||
493 | 493 | ||
494 | static int iscsi_target_do_login(struct iscsi_conn *, struct iscsi_login *); | 494 | static int iscsi_target_do_login(struct iscsi_conn *, struct iscsi_login *); |
495 | 495 | ||
496 | static bool iscsi_target_sk_state_check(struct sock *sk) | 496 | static bool __iscsi_target_sk_check_close(struct sock *sk) |
497 | { | 497 | { |
498 | if (sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) { | 498 | if (sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) { |
499 | pr_debug("iscsi_target_sk_state_check: TCP_CLOSE_WAIT|TCP_CLOSE," | 499 | pr_debug("__iscsi_target_sk_check_close: TCP_CLOSE_WAIT|TCP_CLOSE," |
500 | "returning FALSE\n"); | 500 | "returning FALSE\n"); |
501 | return false; | 501 | return true; |
502 | } | 502 | } |
503 | return true; | 503 | return false; |
504 | } | ||
505 | |||
506 | static bool iscsi_target_sk_check_close(struct iscsi_conn *conn) | ||
507 | { | ||
508 | bool state = false; | ||
509 | |||
510 | if (conn->sock) { | ||
511 | struct sock *sk = conn->sock->sk; | ||
512 | |||
513 | read_lock_bh(&sk->sk_callback_lock); | ||
514 | state = (__iscsi_target_sk_check_close(sk) || | ||
515 | test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)); | ||
516 | read_unlock_bh(&sk->sk_callback_lock); | ||
517 | } | ||
518 | return state; | ||
519 | } | ||
520 | |||
521 | static bool iscsi_target_sk_check_flag(struct iscsi_conn *conn, unsigned int flag) | ||
522 | { | ||
523 | bool state = false; | ||
524 | |||
525 | if (conn->sock) { | ||
526 | struct sock *sk = conn->sock->sk; | ||
527 | |||
528 | read_lock_bh(&sk->sk_callback_lock); | ||
529 | state = test_bit(flag, &conn->login_flags); | ||
530 | read_unlock_bh(&sk->sk_callback_lock); | ||
531 | } | ||
532 | return state; | ||
533 | } | ||
534 | |||
535 | static bool iscsi_target_sk_check_and_clear(struct iscsi_conn *conn, unsigned int flag) | ||
536 | { | ||
537 | bool state = false; | ||
538 | |||
539 | if (conn->sock) { | ||
540 | struct sock *sk = conn->sock->sk; | ||
541 | |||
542 | write_lock_bh(&sk->sk_callback_lock); | ||
543 | state = (__iscsi_target_sk_check_close(sk) || | ||
544 | test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)); | ||
545 | if (!state) | ||
546 | clear_bit(flag, &conn->login_flags); | ||
547 | write_unlock_bh(&sk->sk_callback_lock); | ||
548 | } | ||
549 | return state; | ||
504 | } | 550 | } |
505 | 551 | ||
506 | static void iscsi_target_login_drop(struct iscsi_conn *conn, struct iscsi_login *login) | 552 | static void iscsi_target_login_drop(struct iscsi_conn *conn, struct iscsi_login *login) |
@@ -540,6 +586,20 @@ static void iscsi_target_do_login_rx(struct work_struct *work) | |||
540 | 586 | ||
541 | pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n", | 587 | pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n", |
542 | conn, current->comm, current->pid); | 588 | conn, current->comm, current->pid); |
589 | /* | ||
590 | * If iscsi_target_do_login_rx() has been invoked by ->sk_data_ready() | ||
591 | * before initial PDU processing in iscsi_target_start_negotiation() | ||
592 | * has completed, go ahead and retry until it's cleared. | ||
593 | * | ||
594 | * Otherwise if the TCP connection drops while this is occuring, | ||
595 | * iscsi_target_start_negotiation() will detect the failure, call | ||
596 | * cancel_delayed_work_sync(&conn->login_work), and cleanup the | ||
597 | * remaining iscsi connection resources from iscsi_np process context. | ||
598 | */ | ||
599 | if (iscsi_target_sk_check_flag(conn, LOGIN_FLAGS_INITIAL_PDU)) { | ||
600 | schedule_delayed_work(&conn->login_work, msecs_to_jiffies(10)); | ||
601 | return; | ||
602 | } | ||
543 | 603 | ||
544 | spin_lock(&tpg->tpg_state_lock); | 604 | spin_lock(&tpg->tpg_state_lock); |
545 | state = (tpg->tpg_state == TPG_STATE_ACTIVE); | 605 | state = (tpg->tpg_state == TPG_STATE_ACTIVE); |
@@ -547,26 +607,12 @@ static void iscsi_target_do_login_rx(struct work_struct *work) | |||
547 | 607 | ||
548 | if (!state) { | 608 | if (!state) { |
549 | pr_debug("iscsi_target_do_login_rx: tpg_state != TPG_STATE_ACTIVE\n"); | 609 | pr_debug("iscsi_target_do_login_rx: tpg_state != TPG_STATE_ACTIVE\n"); |
550 | iscsi_target_restore_sock_callbacks(conn); | 610 | goto err; |
551 | iscsi_target_login_drop(conn, login); | ||
552 | iscsit_deaccess_np(np, tpg, tpg_np); | ||
553 | return; | ||
554 | } | 611 | } |
555 | 612 | ||
556 | if (conn->sock) { | 613 | if (iscsi_target_sk_check_close(conn)) { |
557 | struct sock *sk = conn->sock->sk; | 614 | pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n"); |
558 | 615 | goto err; | |
559 | read_lock_bh(&sk->sk_callback_lock); | ||
560 | state = iscsi_target_sk_state_check(sk); | ||
561 | read_unlock_bh(&sk->sk_callback_lock); | ||
562 | |||
563 | if (!state) { | ||
564 | pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n"); | ||
565 | iscsi_target_restore_sock_callbacks(conn); | ||
566 | iscsi_target_login_drop(conn, login); | ||
567 | iscsit_deaccess_np(np, tpg, tpg_np); | ||
568 | return; | ||
569 | } | ||
570 | } | 616 | } |
571 | 617 | ||
572 | conn->login_kworker = current; | 618 | conn->login_kworker = current; |
@@ -584,34 +630,29 @@ static void iscsi_target_do_login_rx(struct work_struct *work) | |||
584 | flush_signals(current); | 630 | flush_signals(current); |
585 | conn->login_kworker = NULL; | 631 | conn->login_kworker = NULL; |
586 | 632 | ||
587 | if (rc < 0) { | 633 | if (rc < 0) |
588 | iscsi_target_restore_sock_callbacks(conn); | 634 | goto err; |
589 | iscsi_target_login_drop(conn, login); | ||
590 | iscsit_deaccess_np(np, tpg, tpg_np); | ||
591 | return; | ||
592 | } | ||
593 | 635 | ||
594 | pr_debug("iscsi_target_do_login_rx after rx_login_io, %p, %s:%d\n", | 636 | pr_debug("iscsi_target_do_login_rx after rx_login_io, %p, %s:%d\n", |
595 | conn, current->comm, current->pid); | 637 | conn, current->comm, current->pid); |
596 | 638 | ||
597 | rc = iscsi_target_do_login(conn, login); | 639 | rc = iscsi_target_do_login(conn, login); |
598 | if (rc < 0) { | 640 | if (rc < 0) { |
599 | iscsi_target_restore_sock_callbacks(conn); | 641 | goto err; |
600 | iscsi_target_login_drop(conn, login); | ||
601 | iscsit_deaccess_np(np, tpg, tpg_np); | ||
602 | } else if (!rc) { | 642 | } else if (!rc) { |
603 | if (conn->sock) { | 643 | if (iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_READ_ACTIVE)) |
604 | struct sock *sk = conn->sock->sk; | 644 | goto err; |
605 | |||
606 | write_lock_bh(&sk->sk_callback_lock); | ||
607 | clear_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags); | ||
608 | write_unlock_bh(&sk->sk_callback_lock); | ||
609 | } | ||
610 | } else if (rc == 1) { | 645 | } else if (rc == 1) { |
611 | iscsi_target_nego_release(conn); | 646 | iscsi_target_nego_release(conn); |
612 | iscsi_post_login_handler(np, conn, zero_tsih); | 647 | iscsi_post_login_handler(np, conn, zero_tsih); |
613 | iscsit_deaccess_np(np, tpg, tpg_np); | 648 | iscsit_deaccess_np(np, tpg, tpg_np); |
614 | } | 649 | } |
650 | return; | ||
651 | |||
652 | err: | ||
653 | iscsi_target_restore_sock_callbacks(conn); | ||
654 | iscsi_target_login_drop(conn, login); | ||
655 | iscsit_deaccess_np(np, tpg, tpg_np); | ||
615 | } | 656 | } |
616 | 657 | ||
617 | static void iscsi_target_do_cleanup(struct work_struct *work) | 658 | static void iscsi_target_do_cleanup(struct work_struct *work) |
@@ -659,31 +700,54 @@ static void iscsi_target_sk_state_change(struct sock *sk) | |||
659 | orig_state_change(sk); | 700 | orig_state_change(sk); |
660 | return; | 701 | return; |
661 | } | 702 | } |
703 | state = __iscsi_target_sk_check_close(sk); | ||
704 | pr_debug("__iscsi_target_sk_close_change: state: %d\n", state); | ||
705 | |||
662 | if (test_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) { | 706 | if (test_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) { |
663 | pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1 sk_state_change" | 707 | pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1 sk_state_change" |
664 | " conn: %p\n", conn); | 708 | " conn: %p\n", conn); |
709 | if (state) | ||
710 | set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags); | ||
665 | write_unlock_bh(&sk->sk_callback_lock); | 711 | write_unlock_bh(&sk->sk_callback_lock); |
666 | orig_state_change(sk); | 712 | orig_state_change(sk); |
667 | return; | 713 | return; |
668 | } | 714 | } |
669 | if (test_and_set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { | 715 | if (test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { |
670 | pr_debug("Got LOGIN_FLAGS_CLOSED=1 sk_state_change conn: %p\n", | 716 | pr_debug("Got LOGIN_FLAGS_CLOSED=1 sk_state_change conn: %p\n", |
671 | conn); | 717 | conn); |
672 | write_unlock_bh(&sk->sk_callback_lock); | 718 | write_unlock_bh(&sk->sk_callback_lock); |
673 | orig_state_change(sk); | 719 | orig_state_change(sk); |
674 | return; | 720 | return; |
675 | } | 721 | } |
722 | /* | ||
723 | * If the TCP connection has dropped, go ahead and set LOGIN_FLAGS_CLOSED, | ||
724 | * but only queue conn->login_work -> iscsi_target_do_login_rx() | ||
725 | * processing if LOGIN_FLAGS_INITIAL_PDU has already been cleared. | ||
726 | * | ||
727 | * When iscsi_target_do_login_rx() runs, iscsi_target_sk_check_close() | ||
728 | * will detect the dropped TCP connection from delayed workqueue context. | ||
729 | * | ||
730 | * If LOGIN_FLAGS_INITIAL_PDU is still set, which means the initial | ||
731 | * iscsi_target_start_negotiation() is running, iscsi_target_do_login() | ||
732 | * via iscsi_target_sk_check_close() or iscsi_target_start_negotiation() | ||
733 | * via iscsi_target_sk_check_and_clear() is responsible for detecting the | ||
734 | * dropped TCP connection in iscsi_np process context, and cleaning up | ||
735 | * the remaining iscsi connection resources. | ||
736 | */ | ||
737 | if (state) { | ||
738 | pr_debug("iscsi_target_sk_state_change got failed state\n"); | ||
739 | set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags); | ||
740 | state = test_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags); | ||
741 | write_unlock_bh(&sk->sk_callback_lock); | ||
676 | 742 | ||
677 | state = iscsi_target_sk_state_check(sk); | 743 | orig_state_change(sk); |
678 | write_unlock_bh(&sk->sk_callback_lock); | ||
679 | |||
680 | pr_debug("iscsi_target_sk_state_change: state: %d\n", state); | ||
681 | 744 | ||
682 | if (!state) { | 745 | if (!state) |
683 | pr_debug("iscsi_target_sk_state_change got failed state\n"); | 746 | schedule_delayed_work(&conn->login_work, 0); |
684 | schedule_delayed_work(&conn->login_cleanup_work, 0); | ||
685 | return; | 747 | return; |
686 | } | 748 | } |
749 | write_unlock_bh(&sk->sk_callback_lock); | ||
750 | |||
687 | orig_state_change(sk); | 751 | orig_state_change(sk); |
688 | } | 752 | } |
689 | 753 | ||
@@ -946,6 +1010,15 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo | |||
946 | if (iscsi_target_handle_csg_one(conn, login) < 0) | 1010 | if (iscsi_target_handle_csg_one(conn, login) < 0) |
947 | return -1; | 1011 | return -1; |
948 | if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) { | 1012 | if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) { |
1013 | /* | ||
1014 | * Check to make sure the TCP connection has not | ||
1015 | * dropped asynchronously while session reinstatement | ||
1016 | * was occuring in this kthread context, before | ||
1017 | * transitioning to full feature phase operation. | ||
1018 | */ | ||
1019 | if (iscsi_target_sk_check_close(conn)) | ||
1020 | return -1; | ||
1021 | |||
949 | login->tsih = conn->sess->tsih; | 1022 | login->tsih = conn->sess->tsih; |
950 | login->login_complete = 1; | 1023 | login->login_complete = 1; |
951 | iscsi_target_restore_sock_callbacks(conn); | 1024 | iscsi_target_restore_sock_callbacks(conn); |
@@ -972,21 +1045,6 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo | |||
972 | break; | 1045 | break; |
973 | } | 1046 | } |
974 | 1047 | ||
975 | if (conn->sock) { | ||
976 | struct sock *sk = conn->sock->sk; | ||
977 | bool state; | ||
978 | |||
979 | read_lock_bh(&sk->sk_callback_lock); | ||
980 | state = iscsi_target_sk_state_check(sk); | ||
981 | read_unlock_bh(&sk->sk_callback_lock); | ||
982 | |||
983 | if (!state) { | ||
984 | pr_debug("iscsi_target_do_login() failed state for" | ||
985 | " conn: %p\n", conn); | ||
986 | return -1; | ||
987 | } | ||
988 | } | ||
989 | |||
990 | return 0; | 1048 | return 0; |
991 | } | 1049 | } |
992 | 1050 | ||
@@ -1255,10 +1313,22 @@ int iscsi_target_start_negotiation( | |||
1255 | 1313 | ||
1256 | write_lock_bh(&sk->sk_callback_lock); | 1314 | write_lock_bh(&sk->sk_callback_lock); |
1257 | set_bit(LOGIN_FLAGS_READY, &conn->login_flags); | 1315 | set_bit(LOGIN_FLAGS_READY, &conn->login_flags); |
1316 | set_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags); | ||
1258 | write_unlock_bh(&sk->sk_callback_lock); | 1317 | write_unlock_bh(&sk->sk_callback_lock); |
1259 | } | 1318 | } |
1260 | 1319 | /* | |
1320 | * If iscsi_target_do_login returns zero to signal more PDU | ||
1321 | * exchanges are required to complete the login, go ahead and | ||
1322 | * clear LOGIN_FLAGS_INITIAL_PDU but only if the TCP connection | ||
1323 | * is still active. | ||
1324 | * | ||
1325 | * Otherwise if TCP connection dropped asynchronously, go ahead | ||
1326 | * and perform connection cleanup now. | ||
1327 | */ | ||
1261 | ret = iscsi_target_do_login(conn, login); | 1328 | ret = iscsi_target_do_login(conn, login); |
1329 | if (!ret && iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_INITIAL_PDU)) | ||
1330 | ret = -1; | ||
1331 | |||
1262 | if (ret < 0) { | 1332 | if (ret < 0) { |
1263 | cancel_delayed_work_sync(&conn->login_work); | 1333 | cancel_delayed_work_sync(&conn->login_work); |
1264 | cancel_delayed_work_sync(&conn->login_cleanup_work); | 1334 | cancel_delayed_work_sync(&conn->login_cleanup_work); |
diff --git a/include/target/iscsi/iscsi_target_core.h b/include/target/iscsi/iscsi_target_core.h index 275581d483dd..5f17fb770477 100644 --- a/include/target/iscsi/iscsi_target_core.h +++ b/include/target/iscsi/iscsi_target_core.h | |||
@@ -557,6 +557,7 @@ struct iscsi_conn { | |||
557 | #define LOGIN_FLAGS_READ_ACTIVE 1 | 557 | #define LOGIN_FLAGS_READ_ACTIVE 1 |
558 | #define LOGIN_FLAGS_CLOSED 2 | 558 | #define LOGIN_FLAGS_CLOSED 2 |
559 | #define LOGIN_FLAGS_READY 4 | 559 | #define LOGIN_FLAGS_READY 4 |
560 | #define LOGIN_FLAGS_INITIAL_PDU 8 | ||
560 | unsigned long login_flags; | 561 | unsigned long login_flags; |
561 | struct delayed_work login_work; | 562 | struct delayed_work login_work; |
562 | struct delayed_work login_cleanup_work; | 563 | struct delayed_work login_cleanup_work; |