mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	iscsi-target: Fix initial login PDU asynchronous socket close OOPs
This patch fixes a OOPs originally introduced by:
   commit bb048357da
   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>
			
			
This commit is contained in:
		
							parent
							
								
									f3cdbe39b2
								
							
						
					
					
						commit
						25cdda95fd
					
				
					 2 changed files with 134 additions and 63 deletions
				
			
		| 
						 | 
				
			
			@ -493,14 +493,60 @@ static void iscsi_target_restore_sock_callbacks(struct iscsi_conn *conn)
 | 
			
		|||
 | 
			
		||||
static int iscsi_target_do_login(struct iscsi_conn *, struct iscsi_login *);
 | 
			
		||||
 | 
			
		||||
static bool iscsi_target_sk_state_check(struct sock *sk)
 | 
			
		||||
static bool __iscsi_target_sk_check_close(struct sock *sk)
 | 
			
		||||
{
 | 
			
		||||
	if (sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) {
 | 
			
		||||
		pr_debug("iscsi_target_sk_state_check: TCP_CLOSE_WAIT|TCP_CLOSE,"
 | 
			
		||||
		pr_debug("__iscsi_target_sk_check_close: TCP_CLOSE_WAIT|TCP_CLOSE,"
 | 
			
		||||
			"returning FALSE\n");
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
	return true;
 | 
			
		||||
 | 
			
		||||
static bool iscsi_target_sk_check_close(struct iscsi_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
	bool state = false;
 | 
			
		||||
 | 
			
		||||
	if (conn->sock) {
 | 
			
		||||
		struct sock *sk = conn->sock->sk;
 | 
			
		||||
 | 
			
		||||
		read_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		state = (__iscsi_target_sk_check_close(sk) ||
 | 
			
		||||
			 test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags));
 | 
			
		||||
		read_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
	}
 | 
			
		||||
	return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool iscsi_target_sk_check_flag(struct iscsi_conn *conn, unsigned int flag)
 | 
			
		||||
{
 | 
			
		||||
	bool state = false;
 | 
			
		||||
 | 
			
		||||
	if (conn->sock) {
 | 
			
		||||
		struct sock *sk = conn->sock->sk;
 | 
			
		||||
 | 
			
		||||
		read_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		state = test_bit(flag, &conn->login_flags);
 | 
			
		||||
		read_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
	}
 | 
			
		||||
	return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool iscsi_target_sk_check_and_clear(struct iscsi_conn *conn, unsigned int flag)
 | 
			
		||||
{
 | 
			
		||||
	bool state = false;
 | 
			
		||||
 | 
			
		||||
	if (conn->sock) {
 | 
			
		||||
		struct sock *sk = conn->sock->sk;
 | 
			
		||||
 | 
			
		||||
		write_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		state = (__iscsi_target_sk_check_close(sk) ||
 | 
			
		||||
			 test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags));
 | 
			
		||||
		if (!state)
 | 
			
		||||
			clear_bit(flag, &conn->login_flags);
 | 
			
		||||
		write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
	}
 | 
			
		||||
	return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		|||
 | 
			
		||||
	pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n",
 | 
			
		||||
			conn, current->comm, current->pid);
 | 
			
		||||
	/*
 | 
			
		||||
	 * If iscsi_target_do_login_rx() has been invoked by ->sk_data_ready()
 | 
			
		||||
	 * before initial PDU processing in iscsi_target_start_negotiation()
 | 
			
		||||
	 * has completed, go ahead and retry until it's cleared.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Otherwise if the TCP connection drops while this is occuring,
 | 
			
		||||
	 * iscsi_target_start_negotiation() will detect the failure, call
 | 
			
		||||
	 * cancel_delayed_work_sync(&conn->login_work), and cleanup the
 | 
			
		||||
	 * remaining iscsi connection resources from iscsi_np process context.
 | 
			
		||||
	 */
 | 
			
		||||
	if (iscsi_target_sk_check_flag(conn, LOGIN_FLAGS_INITIAL_PDU)) {
 | 
			
		||||
		schedule_delayed_work(&conn->login_work, msecs_to_jiffies(10));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spin_lock(&tpg->tpg_state_lock);
 | 
			
		||||
	state = (tpg->tpg_state == TPG_STATE_ACTIVE);
 | 
			
		||||
| 
						 | 
				
			
			@ -547,26 +607,12 @@ static void iscsi_target_do_login_rx(struct work_struct *work)
 | 
			
		|||
 | 
			
		||||
	if (!state) {
 | 
			
		||||
		pr_debug("iscsi_target_do_login_rx: tpg_state != TPG_STATE_ACTIVE\n");
 | 
			
		||||
		iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
		iscsi_target_login_drop(conn, login);
 | 
			
		||||
		iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
		return;
 | 
			
		||||
		goto err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (conn->sock) {
 | 
			
		||||
		struct sock *sk = conn->sock->sk;
 | 
			
		||||
 | 
			
		||||
		read_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		state = iscsi_target_sk_state_check(sk);
 | 
			
		||||
		read_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
 | 
			
		||||
		if (!state) {
 | 
			
		||||
	if (iscsi_target_sk_check_close(conn)) {
 | 
			
		||||
		pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n");
 | 
			
		||||
			iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
			iscsi_target_login_drop(conn, login);
 | 
			
		||||
			iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		goto err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn->login_kworker = current;
 | 
			
		||||
| 
						 | 
				
			
			@ -584,34 +630,29 @@ static void iscsi_target_do_login_rx(struct work_struct *work)
 | 
			
		|||
	flush_signals(current);
 | 
			
		||||
	conn->login_kworker = NULL;
 | 
			
		||||
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
		iscsi_target_login_drop(conn, login);
 | 
			
		||||
		iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		goto err;
 | 
			
		||||
 | 
			
		||||
	pr_debug("iscsi_target_do_login_rx after rx_login_io, %p, %s:%d\n",
 | 
			
		||||
			conn, current->comm, current->pid);
 | 
			
		||||
 | 
			
		||||
	rc = iscsi_target_do_login(conn, login);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
		iscsi_target_login_drop(conn, login);
 | 
			
		||||
		iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
		goto err;
 | 
			
		||||
	} else if (!rc) {
 | 
			
		||||
		if (conn->sock) {
 | 
			
		||||
			struct sock *sk = conn->sock->sk;
 | 
			
		||||
 | 
			
		||||
			write_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
			clear_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags);
 | 
			
		||||
			write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		}
 | 
			
		||||
		if (iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_READ_ACTIVE))
 | 
			
		||||
			goto err;
 | 
			
		||||
	} else if (rc == 1) {
 | 
			
		||||
		iscsi_target_nego_release(conn);
 | 
			
		||||
		iscsi_post_login_handler(np, conn, zero_tsih);
 | 
			
		||||
		iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
	}
 | 
			
		||||
	return;
 | 
			
		||||
 | 
			
		||||
err:
 | 
			
		||||
	iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
	iscsi_target_login_drop(conn, login);
 | 
			
		||||
	iscsit_deaccess_np(np, tpg, tpg_np);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void iscsi_target_do_cleanup(struct work_struct *work)
 | 
			
		||||
| 
						 | 
				
			
			@ -659,31 +700,54 @@ static void iscsi_target_sk_state_change(struct sock *sk)
 | 
			
		|||
		orig_state_change(sk);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	state = __iscsi_target_sk_check_close(sk);
 | 
			
		||||
	pr_debug("__iscsi_target_sk_close_change: state: %d\n", state);
 | 
			
		||||
 | 
			
		||||
	if (test_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) {
 | 
			
		||||
		pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1 sk_state_change"
 | 
			
		||||
			 " conn: %p\n", conn);
 | 
			
		||||
		if (state)
 | 
			
		||||
			set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags);
 | 
			
		||||
		write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		orig_state_change(sk);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (test_and_set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) {
 | 
			
		||||
	if (test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) {
 | 
			
		||||
		pr_debug("Got LOGIN_FLAGS_CLOSED=1 sk_state_change conn: %p\n",
 | 
			
		||||
			 conn);
 | 
			
		||||
		write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		orig_state_change(sk);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	state = iscsi_target_sk_state_check(sk);
 | 
			
		||||
	/*
 | 
			
		||||
	 * If the TCP connection has dropped, go ahead and set LOGIN_FLAGS_CLOSED,
 | 
			
		||||
	 * but only queue conn->login_work -> iscsi_target_do_login_rx()
 | 
			
		||||
	 * processing if LOGIN_FLAGS_INITIAL_PDU has already been cleared.
 | 
			
		||||
	 *
 | 
			
		||||
	 * When iscsi_target_do_login_rx() runs, iscsi_target_sk_check_close()
 | 
			
		||||
	 * will detect the dropped TCP connection from delayed workqueue context.
 | 
			
		||||
	 *
 | 
			
		||||
	 * If LOGIN_FLAGS_INITIAL_PDU is still set, which means the initial
 | 
			
		||||
	 * iscsi_target_start_negotiation() is running, iscsi_target_do_login()
 | 
			
		||||
	 * via iscsi_target_sk_check_close() or iscsi_target_start_negotiation()
 | 
			
		||||
	 * via iscsi_target_sk_check_and_clear() is responsible for detecting the
 | 
			
		||||
	 * dropped TCP connection in iscsi_np process context, and cleaning up
 | 
			
		||||
	 * the remaining iscsi connection resources.
 | 
			
		||||
	 */
 | 
			
		||||
	if (state) {
 | 
			
		||||
		pr_debug("iscsi_target_sk_state_change got failed state\n");
 | 
			
		||||
		set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags);
 | 
			
		||||
		state = test_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags);
 | 
			
		||||
		write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
 | 
			
		||||
	pr_debug("iscsi_target_sk_state_change: state: %d\n", state);
 | 
			
		||||
		orig_state_change(sk);
 | 
			
		||||
 | 
			
		||||
	if (!state) {
 | 
			
		||||
		pr_debug("iscsi_target_sk_state_change got failed state\n");
 | 
			
		||||
		schedule_delayed_work(&conn->login_cleanup_work, 0);
 | 
			
		||||
		if (!state)
 | 
			
		||||
			schedule_delayed_work(&conn->login_work, 0);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
 | 
			
		||||
	orig_state_change(sk);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -946,6 +1010,15 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo
 | 
			
		|||
			if (iscsi_target_handle_csg_one(conn, login) < 0)
 | 
			
		||||
				return -1;
 | 
			
		||||
			if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) {
 | 
			
		||||
				/*
 | 
			
		||||
				 * Check to make sure the TCP connection has not
 | 
			
		||||
				 * dropped asynchronously while session reinstatement
 | 
			
		||||
				 * was occuring in this kthread context, before
 | 
			
		||||
				 * transitioning to full feature phase operation.
 | 
			
		||||
				 */
 | 
			
		||||
				if (iscsi_target_sk_check_close(conn))
 | 
			
		||||
					return -1;
 | 
			
		||||
 | 
			
		||||
				login->tsih = conn->sess->tsih;
 | 
			
		||||
				login->login_complete = 1;
 | 
			
		||||
				iscsi_target_restore_sock_callbacks(conn);
 | 
			
		||||
| 
						 | 
				
			
			@ -972,21 +1045,6 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo
 | 
			
		|||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (conn->sock) {
 | 
			
		||||
		struct sock *sk = conn->sock->sk;
 | 
			
		||||
		bool state;
 | 
			
		||||
 | 
			
		||||
		read_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		state = iscsi_target_sk_state_check(sk);
 | 
			
		||||
		read_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
 | 
			
		||||
		if (!state) {
 | 
			
		||||
			pr_debug("iscsi_target_do_login() failed state for"
 | 
			
		||||
				 " conn: %p\n", conn);
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1255,10 +1313,22 @@ int iscsi_target_start_negotiation(
 | 
			
		|||
 | 
			
		||||
		write_lock_bh(&sk->sk_callback_lock);
 | 
			
		||||
		set_bit(LOGIN_FLAGS_READY, &conn->login_flags);
 | 
			
		||||
		set_bit(LOGIN_FLAGS_INITIAL_PDU, &conn->login_flags);
 | 
			
		||||
		write_unlock_bh(&sk->sk_callback_lock);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If iscsi_target_do_login returns zero to signal more PDU
 | 
			
		||||
	 * exchanges are required to complete the login, go ahead and
 | 
			
		||||
	 * clear LOGIN_FLAGS_INITIAL_PDU but only if the TCP connection
 | 
			
		||||
	 * is still active.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Otherwise if TCP connection dropped asynchronously, go ahead
 | 
			
		||||
	 * and perform connection cleanup now.
 | 
			
		||||
	 */
 | 
			
		||||
	ret = iscsi_target_do_login(conn, login);
 | 
			
		||||
	if (!ret && iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_INITIAL_PDU))
 | 
			
		||||
		ret = -1;
 | 
			
		||||
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		cancel_delayed_work_sync(&conn->login_work);
 | 
			
		||||
		cancel_delayed_work_sync(&conn->login_cleanup_work);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -557,6 +557,7 @@ struct iscsi_conn {
 | 
			
		|||
#define LOGIN_FLAGS_READ_ACTIVE		1
 | 
			
		||||
#define LOGIN_FLAGS_CLOSED		2
 | 
			
		||||
#define LOGIN_FLAGS_READY		4
 | 
			
		||||
#define LOGIN_FLAGS_INITIAL_PDU		8
 | 
			
		||||
	unsigned long		login_flags;
 | 
			
		||||
	struct delayed_work	login_work;
 | 
			
		||||
	struct delayed_work	login_cleanup_work;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue