Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+134 -63
+133 -63
drivers/target/iscsi/iscsi_target_nego.c
··· 493 493 494 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 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 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 552 static void iscsi_target_login_drop(struct iscsi_conn *conn, struct iscsi_login *login) ··· 586 540 587 541 pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n", 588 542 conn, current->comm, current->pid); 543 + /* 544 + * If iscsi_target_do_login_rx() has been invoked by ->sk_data_ready() 545 + * before initial PDU processing in iscsi_target_start_negotiation() 546 + * has completed, go ahead and retry until it's cleared. 547 + * 548 + * Otherwise if the TCP connection drops while this is occuring, 549 + * iscsi_target_start_negotiation() will detect the failure, call 550 + * cancel_delayed_work_sync(&conn->login_work), and cleanup the 551 + * remaining iscsi connection resources from iscsi_np process context. 552 + */ 553 + if (iscsi_target_sk_check_flag(conn, LOGIN_FLAGS_INITIAL_PDU)) { 554 + schedule_delayed_work(&conn->login_work, msecs_to_jiffies(10)); 555 + return; 556 + } 589 557 590 558 spin_lock(&tpg->tpg_state_lock); 591 559 state = (tpg->tpg_state == TPG_STATE_ACTIVE); ··· 607 547 608 548 if (!state) { 609 549 pr_debug("iscsi_target_do_login_rx: tpg_state != TPG_STATE_ACTIVE\n"); 610 - iscsi_target_restore_sock_callbacks(conn); 611 - iscsi_target_login_drop(conn, login); 612 - iscsit_deaccess_np(np, tpg, tpg_np); 613 - return; 550 + goto err; 614 551 } 615 552 616 - if (conn->sock) { 617 - struct sock *sk = conn->sock->sk; 618 - 619 - read_lock_bh(&sk->sk_callback_lock); 620 - state = iscsi_target_sk_state_check(sk); 621 - read_unlock_bh(&sk->sk_callback_lock); 622 - 623 - if (!state) { 624 - pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n"); 625 - iscsi_target_restore_sock_callbacks(conn); 626 - iscsi_target_login_drop(conn, login); 627 - iscsit_deaccess_np(np, tpg, tpg_np); 628 - return; 629 - } 553 + if (iscsi_target_sk_check_close(conn)) { 554 + pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n"); 555 + goto err; 630 556 } 631 557 632 558 conn->login_kworker = current; ··· 630 584 flush_signals(current); 631 585 conn->login_kworker = NULL; 632 586 633 - if (rc < 0) { 634 - iscsi_target_restore_sock_callbacks(conn); 635 - iscsi_target_login_drop(conn, login); 636 - iscsit_deaccess_np(np, tpg, tpg_np); 637 - return; 638 - } 587 + if (rc < 0) 588 + goto err; 639 589 640 590 pr_debug("iscsi_target_do_login_rx after rx_login_io, %p, %s:%d\n", 641 591 conn, current->comm, current->pid); 642 592 643 593 rc = iscsi_target_do_login(conn, login); 644 594 if (rc < 0) { 645 - iscsi_target_restore_sock_callbacks(conn); 646 - iscsi_target_login_drop(conn, login); 647 - iscsit_deaccess_np(np, tpg, tpg_np); 595 + goto err; 648 596 } else if (!rc) { 649 - if (conn->sock) { 650 - struct sock *sk = conn->sock->sk; 651 - 652 - write_lock_bh(&sk->sk_callback_lock); 653 - clear_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags); 654 - write_unlock_bh(&sk->sk_callback_lock); 655 - } 597 + if (iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_READ_ACTIVE)) 598 + goto err; 656 599 } else if (rc == 1) { 657 600 iscsi_target_nego_release(conn); 658 601 iscsi_post_login_handler(np, conn, zero_tsih); 659 602 iscsit_deaccess_np(np, tpg, tpg_np); 660 603 } 604 + return; 605 + 606 + err: 607 + iscsi_target_restore_sock_callbacks(conn); 608 + iscsi_target_login_drop(conn, login); 609 + iscsit_deaccess_np(np, tpg, tpg_np); 661 610 } 662 611 663 612 static void iscsi_target_do_cleanup(struct work_struct *work) ··· 700 659 orig_state_change(sk); 701 660 return; 702 661 } 662 + state = __iscsi_target_sk_check_close(sk); 663 + pr_debug("__iscsi_target_sk_close_change: state: %d\n", state); 664 + 703 665 if (test_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) { 704 666 pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1 sk_state_change" 705 667 " conn: %p\n", conn); 668 + if (state) 669 + set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags); 706 670 write_unlock_bh(&sk->sk_callback_lock); 707 671 orig_state_change(sk); 708 672 return; 709 673 } 710 - if (test_and_set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { 674 + if (test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { 711 675 pr_debug("Got LOGIN_FLAGS_CLOSED=1 sk_state_change conn: %p\n", 712 676 conn); 713 677 write_unlock_bh(&sk->sk_callback_lock); 714 678 orig_state_change(sk); 715 679 return; 716 680 } 717 - 718 - state = iscsi_target_sk_state_check(sk); 719 - write_unlock_bh(&sk->sk_callback_lock); 720 - 721 - pr_debug("iscsi_target_sk_state_change: state: %d\n", state); 722 - 723 - if (!state) { 681 + /* 682 + * If the TCP connection has dropped, go ahead and set LOGIN_FLAGS_CLOSED, 683 + * but only queue conn->login_work -> iscsi_target_do_login_rx() 684 + * processing if LOGIN_FLAGS_INITIAL_PDU has already been cleared. 685 + * 686 + * When iscsi_target_do_login_rx() runs, iscsi_target_sk_check_close() 687 + * will detect the dropped TCP connection from delayed workqueue context. 688 + * 689 + * If LOGIN_FLAGS_INITIAL_PDU is still set, which means the initial 690 + * iscsi_target_start_negotiation() is running, iscsi_target_do_login() 691 + * via iscsi_target_sk_check_close() or iscsi_target_start_negotiation() 692 + * via iscsi_target_sk_check_and_clear() is responsible for detecting the 693 + * dropped TCP connection in iscsi_np process context, and cleaning up 694 + * the remaining iscsi connection resources. 695 + */ 696 + if (state) { 724 697 pr_debug("iscsi_target_sk_state_change got failed state\n"); 725 - schedule_delayed_work(&conn->login_cleanup_work, 0); 698 + set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags); 699 + state = test_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags); 700 + write_unlock_bh(&sk->sk_callback_lock); 701 + 702 + orig_state_change(sk); 703 + 704 + if (!state) 705 + schedule_delayed_work(&conn->login_work, 0); 726 706 return; 727 707 } 708 + write_unlock_bh(&sk->sk_callback_lock); 709 + 728 710 orig_state_change(sk); 729 711 } 730 712 ··· 1010 946 if (iscsi_target_handle_csg_one(conn, login) < 0) 1011 947 return -1; 1012 948 if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) { 949 + /* 950 + * Check to make sure the TCP connection has not 951 + * dropped asynchronously while session reinstatement 952 + * was occuring in this kthread context, before 953 + * transitioning to full feature phase operation. 954 + */ 955 + if (iscsi_target_sk_check_close(conn)) 956 + return -1; 957 + 1013 958 login->tsih = conn->sess->tsih; 1014 959 login->login_complete = 1; 1015 960 iscsi_target_restore_sock_callbacks(conn); ··· 1043 970 login_rsp->flags &= ~ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK; 1044 971 } 1045 972 break; 1046 - } 1047 - 1048 - if (conn->sock) { 1049 - struct sock *sk = conn->sock->sk; 1050 - bool state; 1051 - 1052 - read_lock_bh(&sk->sk_callback_lock); 1053 - state = iscsi_target_sk_state_check(sk); 1054 - read_unlock_bh(&sk->sk_callback_lock); 1055 - 1056 - if (!state) { 1057 - pr_debug("iscsi_target_do_login() failed state for" 1058 - " conn: %p\n", conn); 1059 - return -1; 1060 - } 1061 973 } 1062 974 1063 975 return 0; ··· 1313 1255 1314 1256 write_lock_bh(&sk->sk_callback_lock); 1315 1257 set_bit(LOGIN_FLAGS_READY, &conn->login_flags); 1258 + set_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags); 1316 1259 write_unlock_bh(&sk->sk_callback_lock); 1317 1260 } 1318 - 1261 + /* 1262 + * If iscsi_target_do_login returns zero to signal more PDU 1263 + * exchanges are required to complete the login, go ahead and 1264 + * clear LOGIN_FLAGS_INITIAL_PDU but only if the TCP connection 1265 + * is still active. 1266 + * 1267 + * Otherwise if TCP connection dropped asynchronously, go ahead 1268 + * and perform connection cleanup now. 1269 + */ 1319 1270 ret = iscsi_target_do_login(conn, login); 1271 + if (!ret && iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_INITIAL_PDU)) 1272 + ret = -1; 1273 + 1320 1274 if (ret < 0) { 1321 1275 cancel_delayed_work_sync(&conn->login_work); 1322 1276 cancel_delayed_work_sync(&conn->login_cleanup_work);
+1
include/target/iscsi/iscsi_target_core.h
··· 557 557 #define LOGIN_FLAGS_READ_ACTIVE 1 558 558 #define LOGIN_FLAGS_CLOSED 2 559 559 #define LOGIN_FLAGS_READY 4 560 + #define LOGIN_FLAGS_INITIAL_PDU 8 560 561 unsigned long login_flags; 561 562 struct delayed_work login_work; 562 563 struct delayed_work login_cleanup_work;