Re: [PATCH-v4.9.y] iscsi-target: Fix initial login PDU asynchronous socket close OOPs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Aug 07, 2017 at 03:16:22AM +0000, Nicholas A. Bellinger wrote:
> From: Nicholas Bellinger <nab@xxxxxxxxxxxxxxx>
> 
> commit 25cdda95fda78d22d44157da15aa7ea34be3c804 upstream.
> 
> This patch fixes a OOPs originally introduced by:
> 
>    commit bb048357dad6d604520c91586334c9c230366a14
>    Author: Nicholas Bellinger <nab@xxxxxxxxxxxxxxx>
>    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@xxxxxxxxxx>
> Reviewed-by: Mike Christie <mchristi@xxxxxxxxxx>
> Tested-by: Mike Christie <mchristi@xxxxxxxxxx>
> Cc: Mike Christie <mchristi@xxxxxxxxxx>
> Reported-by: Hannes Reinecke <hare@xxxxxxxx>
> Cc: Hannes Reinecke <hare@xxxxxxxx>
> Cc: Sagi Grimberg <sagi@xxxxxxxxxxx>
> Cc: Varun Prakash <varun@xxxxxxxxxxx>
> Signed-off-by: Nicholas Bellinger <nab@xxxxxxxxxxxxxxx>
> ---
>  drivers/target/iscsi/iscsi_target_nego.c | 204 +++++++++++++++++++++----------
>  include/target/iscsi/iscsi_target_core.h |   1 +
>  2 files changed, 138 insertions(+), 67 deletions(-)
> 
> diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c
> index 6693d7c..e8efb42 100644
> --- a/drivers/target/iscsi/iscsi_target_nego.c
> +++ b/drivers/target/iscsi/iscsi_target_nego.c
> @@ -490,14 +490,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 false;
> +		return true;
>  	}
> -	return true;
> +	return false;
> +}
> +
> +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)
> @@ -537,6 +583,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);
> @@ -544,26 +604,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) {
> -			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;
> -		}
> +	if (iscsi_target_sk_check_close(conn)) {
> +		pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n");
> +		goto err;
>  	}
>  
>  	conn->login_kworker = current;
> @@ -581,34 +627,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)
> @@ -656,31 +697,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;
>  	}
> +	/*
> +	 * 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);
>  
> -	state = iscsi_target_sk_state_check(sk);
> -	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);
>  }
>  
> @@ -945,6 +1009,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);
> @@ -971,21 +1044,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;
>  }
>  
> @@ -1252,13 +1310,25 @@ int iscsi_target_start_negotiation(
>         if (conn->sock) {
>                 struct sock *sk = conn->sock->sk;
>  
> -               write_lock_bh(&sk->sk_callback_lock);
> -               set_bit(LOGIN_FLAGS_READY, &conn->login_flags);
> -               write_unlock_bh(&sk->sk_callback_lock);
> -       }
> +		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;
>  
> -       ret = iscsi_target_do_login(conn, login);
> -       if (ret < 0) {
> +	if (ret < 0) {
>  		cancel_delayed_work_sync(&conn->login_work);
>  		cancel_delayed_work_sync(&conn->login_cleanup_work);
>  		iscsi_target_restore_sock_callbacks(conn);
> diff --git a/include/target/iscsi/iscsi_target_core.h b/include/target/iscsi/iscsi_target_core.h
> index 33b2e75..c8132b4 100644
> --- a/include/target/iscsi/iscsi_target_core.h
> +++ b/include/target/iscsi/iscsi_target_core.h
> @@ -563,6 +563,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;

Now applied, thanks.

greg k-h



[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]