Skip to content

Commit

Permalink
scsi: target: iscsi: Fix hang in the iSCSI login code
Browse files Browse the repository at this point in the history
If the initiator suddenly stops sending data during a login while keeping
the TCP connection open, the login_work won't be scheduled and will never
release the login semaphore; concurrent login operations will therefore get
stuck and fail.

The bug is due to the inability of the login timeout code to properly
handle this particular case.

Fix the problem by replacing the old per-NP login timer with a new
per-connection timer.

The timer is started when an initiator connects to the target; if it
expires, it sends a SIGINT signal to the thread pointed at by the
conn->login_kworker pointer.

conn->login_kworker is set by calling the iscsit_set_login_timer_kworker()
helper, initially it will point to the np thread; When the login
operation's control is in the process of being passed from the NP-thread to
login_work, the conn->login_worker pointer is set to NULL.  Finally,
login_kworker will be changed to point to the worker thread executing the
login_work job.

If conn->login_kworker is NULL when the timer expires, it means that the
login operation hasn't been completed yet but login_work isn't running, in
this case the timer will mark the login process as failed and will schedule
login_work so the latter will be forced to free the resources it holds.

Signed-off-by: Maurizio Lombardi <mlombard@redhat.com>
Link: https://lore.kernel.org/r/20230508162219.1731964-2-mlombard@redhat.com
Reviewed-by: Mike Christie <michael.christie@oracle.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
  • Loading branch information
Maurizio Lombardi authored and Martin K. Petersen committed May 22, 2023
1 parent 09e797c commit 1324701
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 93 deletions.
2 changes: 0 additions & 2 deletions drivers/target/iscsi/iscsi_target.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,6 @@ struct iscsi_np *iscsit_add_np(
init_completion(&np->np_restart_comp);
INIT_LIST_HEAD(&np->np_list);

timer_setup(&np->np_login_timer, iscsi_handle_login_thread_timeout, 0);

ret = iscsi_target_setup_login_socket(np, sockaddr);
if (ret != 0) {
kfree(np);
Expand Down
63 changes: 5 additions & 58 deletions drivers/target/iscsi/iscsi_target_login.c
Original file line number Diff line number Diff line change
Expand Up @@ -811,59 +811,6 @@ void iscsi_post_login_handler(
iscsit_dec_conn_usage_count(conn);
}

void iscsi_handle_login_thread_timeout(struct timer_list *t)
{
struct iscsi_np *np = from_timer(np, t, np_login_timer);

spin_lock_bh(&np->np_thread_lock);
pr_err("iSCSI Login timeout on Network Portal %pISpc\n",
&np->np_sockaddr);

if (np->np_login_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&np->np_thread_lock);
return;
}

if (np->np_thread)
send_sig(SIGINT, np->np_thread, 1);

np->np_login_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&np->np_thread_lock);
}

static void iscsi_start_login_thread_timer(struct iscsi_np *np)
{
/*
* This used the TA_LOGIN_TIMEOUT constant because at this
* point we do not have access to ISCSI_TPG_ATTRIB(tpg)->login_timeout
*/
spin_lock_bh(&np->np_thread_lock);
np->np_login_timer_flags &= ~ISCSI_TF_STOP;
np->np_login_timer_flags |= ISCSI_TF_RUNNING;
mod_timer(&np->np_login_timer, jiffies + TA_LOGIN_TIMEOUT * HZ);

pr_debug("Added timeout timer to iSCSI login request for"
" %u seconds.\n", TA_LOGIN_TIMEOUT);
spin_unlock_bh(&np->np_thread_lock);
}

static void iscsi_stop_login_thread_timer(struct iscsi_np *np)
{
spin_lock_bh(&np->np_thread_lock);
if (!(np->np_login_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&np->np_thread_lock);
return;
}
np->np_login_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&np->np_thread_lock);

del_timer_sync(&np->np_login_timer);

spin_lock_bh(&np->np_thread_lock);
np->np_login_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&np->np_thread_lock);
}

int iscsit_setup_np(
struct iscsi_np *np,
struct sockaddr_storage *sockaddr)
Expand Down Expand Up @@ -1123,10 +1070,13 @@ static struct iscsit_conn *iscsit_alloc_conn(struct iscsi_np *np)
spin_lock_init(&conn->nopin_timer_lock);
spin_lock_init(&conn->response_queue_lock);
spin_lock_init(&conn->state_lock);
spin_lock_init(&conn->login_worker_lock);
spin_lock_init(&conn->login_timer_lock);

timer_setup(&conn->nopin_response_timer,
iscsit_handle_nopin_response_timeout, 0);
timer_setup(&conn->nopin_timer, iscsit_handle_nopin_timeout, 0);
timer_setup(&conn->login_timer, iscsit_login_timeout, 0);

if (iscsit_conn_set_transport(conn, np->np_transport) < 0)
goto free_conn;
Expand Down Expand Up @@ -1304,7 +1254,7 @@ static int __iscsi_target_login_thread(struct iscsi_np *np)
goto new_sess_out;
}

iscsi_start_login_thread_timer(np);
iscsit_start_login_timer(conn, current);

pr_debug("Moving to TARG_CONN_STATE_XPT_UP.\n");
conn->conn_state = TARG_CONN_STATE_XPT_UP;
Expand Down Expand Up @@ -1417,8 +1367,6 @@ static int __iscsi_target_login_thread(struct iscsi_np *np)
if (ret < 0)
goto new_sess_out;

iscsi_stop_login_thread_timer(np);

if (ret == 1) {
tpg_np = conn->tpg_np;

Expand All @@ -1434,7 +1382,7 @@ static int __iscsi_target_login_thread(struct iscsi_np *np)
new_sess_out:
new_sess = true;
old_sess_out:
iscsi_stop_login_thread_timer(np);
iscsit_stop_login_timer(conn);
tpg_np = conn->tpg_np;
iscsi_target_login_sess_out(conn, zero_tsih, new_sess);
new_sess = false;
Expand All @@ -1448,7 +1396,6 @@ static int __iscsi_target_login_thread(struct iscsi_np *np)
return 1;

exit:
iscsi_stop_login_thread_timer(np);
spin_lock_bh(&np->np_thread_lock);
np->np_thread_state = ISCSI_NP_THREAD_EXIT;
spin_unlock_bh(&np->np_thread_lock);
Expand Down
70 changes: 39 additions & 31 deletions drivers/target/iscsi/iscsi_target_nego.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,25 +535,6 @@ static void iscsi_target_login_drop(struct iscsit_conn *conn, struct iscsi_login
iscsi_target_login_sess_out(conn, zero_tsih, true);
}

struct conn_timeout {
struct timer_list timer;
struct iscsit_conn *conn;
};

static void iscsi_target_login_timeout(struct timer_list *t)
{
struct conn_timeout *timeout = from_timer(timeout, t, timer);
struct iscsit_conn *conn = timeout->conn;

pr_debug("Entering iscsi_target_login_timeout >>>>>>>>>>>>>>>>>>>\n");

if (conn->login_kworker) {
pr_debug("Sending SIGINT to conn->login_kworker %s/%d\n",
conn->login_kworker->comm, conn->login_kworker->pid);
send_sig(SIGINT, conn->login_kworker, 1);
}
}

static void iscsi_target_do_login_rx(struct work_struct *work)
{
struct iscsit_conn *conn = container_of(work,
Expand All @@ -562,12 +543,15 @@ static void iscsi_target_do_login_rx(struct work_struct *work)
struct iscsi_np *np = login->np;
struct iscsi_portal_group *tpg = conn->tpg;
struct iscsi_tpg_np *tpg_np = conn->tpg_np;
struct conn_timeout timeout;
int rc, zero_tsih = login->zero_tsih;
bool state;

pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n",
conn, current->comm, current->pid);

spin_lock(&conn->login_worker_lock);
set_bit(LOGIN_FLAGS_WORKER_RUNNING, &conn->login_flags);
spin_unlock(&conn->login_worker_lock);
/*
* If iscsi_target_do_login_rx() has been invoked by ->sk_data_ready()
* before initial PDU processing in iscsi_target_start_negotiation()
Expand Down Expand Up @@ -597,19 +581,16 @@ static void iscsi_target_do_login_rx(struct work_struct *work)
goto err;
}

conn->login_kworker = current;
allow_signal(SIGINT);

timeout.conn = conn;
timer_setup_on_stack(&timeout.timer, iscsi_target_login_timeout, 0);
mod_timer(&timeout.timer, jiffies + TA_LOGIN_TIMEOUT * HZ);
pr_debug("Starting login timer for %s/%d\n", current->comm, current->pid);
rc = iscsit_set_login_timer_kworker(conn, current);
if (rc < 0) {
/* The login timer has already expired */
pr_debug("iscsi_target_do_login_rx, login failed\n");
goto err;
}

rc = conn->conn_transport->iscsit_get_login_rx(conn, login);
del_timer_sync(&timeout.timer);
destroy_timer_on_stack(&timeout.timer);
flush_signals(current);
conn->login_kworker = NULL;

if (rc < 0)
goto err;
Expand Down Expand Up @@ -646,7 +627,17 @@ static void iscsi_target_do_login_rx(struct work_struct *work)
if (iscsi_target_sk_check_and_clear(conn,
LOGIN_FLAGS_WRITE_ACTIVE))
goto err;

/*
* Set the login timer thread pointer to NULL to prevent the
* login process from getting stuck if the initiator
* stops sending data.
*/
rc = iscsit_set_login_timer_kworker(conn, NULL);
if (rc < 0)
goto err;
} else if (rc == 1) {
iscsit_stop_login_timer(conn);
cancel_delayed_work(&conn->login_work);
iscsi_target_nego_release(conn);
iscsi_post_login_handler(np, conn, zero_tsih);
Expand All @@ -656,6 +647,7 @@ static void iscsi_target_do_login_rx(struct work_struct *work)

err:
iscsi_target_restore_sock_callbacks(conn);
iscsit_stop_login_timer(conn);
cancel_delayed_work(&conn->login_work);
iscsi_target_login_drop(conn, login);
iscsit_deaccess_np(np, tpg, tpg_np);
Expand Down Expand Up @@ -1368,14 +1360,30 @@ int iscsi_target_start_negotiation(
* 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) {
spin_lock(&conn->login_worker_lock);

if (iscsi_target_sk_check_and_clear(conn, LOGIN_FLAGS_INITIAL_PDU))
ret = -1;
else if (!test_bit(LOGIN_FLAGS_WORKER_RUNNING, &conn->login_flags)) {
if (iscsit_set_login_timer_kworker(conn, NULL) < 0) {
/*
* The timeout has expired already.
* Schedule login_work to perform the cleanup.
*/
schedule_delayed_work(&conn->login_work, 0);
}
}

spin_unlock(&conn->login_worker_lock);
}

if (ret < 0) {
iscsi_target_restore_sock_callbacks(conn);
iscsi_remove_failed_auth_entry(conn);
}
if (ret != 0) {
iscsit_stop_login_timer(conn);
cancel_delayed_work_sync(&conn->login_work);
iscsi_target_nego_release(conn);
}
Expand Down
51 changes: 51 additions & 0 deletions drivers/target/iscsi/iscsi_target_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,57 @@ void iscsit_stop_nopin_timer(struct iscsit_conn *conn)
spin_unlock_bh(&conn->nopin_timer_lock);
}

void iscsit_login_timeout(struct timer_list *t)
{
struct iscsit_conn *conn = from_timer(conn, t, login_timer);
struct iscsi_login *login = conn->login;

pr_debug("Entering iscsi_target_login_timeout >>>>>>>>>>>>>>>>>>>\n");

spin_lock_bh(&conn->login_timer_lock);
login->login_failed = 1;

if (conn->login_kworker) {
pr_debug("Sending SIGINT to conn->login_kworker %s/%d\n",
conn->login_kworker->comm, conn->login_kworker->pid);
send_sig(SIGINT, conn->login_kworker, 1);
} else {
schedule_delayed_work(&conn->login_work, 0);
}
spin_unlock_bh(&conn->login_timer_lock);
}

void iscsit_start_login_timer(struct iscsit_conn *conn, struct task_struct *kthr)
{
pr_debug("Login timer started\n");

conn->login_kworker = kthr;
mod_timer(&conn->login_timer, jiffies + TA_LOGIN_TIMEOUT * HZ);
}

int iscsit_set_login_timer_kworker(struct iscsit_conn *conn, struct task_struct *kthr)
{
struct iscsi_login *login = conn->login;
int ret = 0;

spin_lock_bh(&conn->login_timer_lock);
if (login->login_failed) {
/* The timer has already expired */
ret = -1;
} else {
conn->login_kworker = kthr;
}
spin_unlock_bh(&conn->login_timer_lock);

return ret;
}

void iscsit_stop_login_timer(struct iscsit_conn *conn)
{
pr_debug("Login timer stopped\n");
timer_delete_sync(&conn->login_timer);
}

int iscsit_send_tx_data(
struct iscsit_cmd *cmd,
struct iscsit_conn *conn,
Expand Down
4 changes: 4 additions & 0 deletions drivers/target/iscsi/iscsi_target_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ extern void iscsit_handle_nopin_timeout(struct timer_list *t);
extern void __iscsit_start_nopin_timer(struct iscsit_conn *);
extern void iscsit_start_nopin_timer(struct iscsit_conn *);
extern void iscsit_stop_nopin_timer(struct iscsit_conn *);
extern void iscsit_login_timeout(struct timer_list *t);
extern void iscsit_start_login_timer(struct iscsit_conn *, struct task_struct *kthr);
extern void iscsit_stop_login_timer(struct iscsit_conn *);
extern int iscsit_set_login_timer_kworker(struct iscsit_conn *, struct task_struct *kthr);
extern int iscsit_send_tx_data(struct iscsit_cmd *, struct iscsit_conn *, int);
extern int iscsit_fe_sendpage_sg(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_tx_login_rsp(struct iscsit_conn *, u8, u8);
Expand Down
6 changes: 4 additions & 2 deletions include/target/iscsi/iscsi_target_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,14 @@ struct iscsit_conn {
#define LOGIN_FLAGS_READ_ACTIVE 2
#define LOGIN_FLAGS_WRITE_ACTIVE 3
#define LOGIN_FLAGS_CLOSED 4
#define LOGIN_FLAGS_WORKER_RUNNING 5
unsigned long login_flags;
struct delayed_work login_work;
struct iscsi_login *login;
struct timer_list nopin_timer;
struct timer_list nopin_response_timer;
struct timer_list transport_timer;
struct timer_list login_timer;
struct task_struct *login_kworker;
/* Spinlock used for add/deleting cmd's from conn_cmd_list */
spinlock_t cmd_lock;
Expand All @@ -576,6 +578,8 @@ struct iscsit_conn {
spinlock_t nopin_timer_lock;
spinlock_t response_queue_lock;
spinlock_t state_lock;
spinlock_t login_timer_lock;
spinlock_t login_worker_lock;
/* libcrypto RX and TX contexts for crc32c */
struct ahash_request *conn_rx_hash;
struct ahash_request *conn_tx_hash;
Expand Down Expand Up @@ -792,15 +796,13 @@ struct iscsi_np {
enum np_thread_state_table np_thread_state;
bool enabled;
atomic_t np_reset_count;
enum iscsi_timer_flags_table np_login_timer_flags;
u32 np_exports;
enum np_flags_table np_flags;
spinlock_t np_thread_lock;
struct completion np_restart_comp;
struct socket *np_socket;
struct sockaddr_storage np_sockaddr;
struct task_struct *np_thread;
struct timer_list np_login_timer;
void *np_context;
struct iscsit_transport *np_transport;
struct list_head np_list;
Expand Down

0 comments on commit 1324701

Please sign in to comment.