diff mbox series

[V3] target: fix a race condition between login_work and the login thread

Message ID 20221115125242.102344-1-mlombard@redhat.com (mailing list archive)
State Superseded
Headers show
Series [V3] target: fix a race condition between login_work and the login thread | expand

Commit Message

Maurizio Lombardi Nov. 15, 2022, 12:52 p.m. UTC
In case a malicious initiator sends some random data immediately after a
login PDU; the iscsi_target_sk_data_ready() callback will
schedule the login_work and, at the same time,
the negotiation may end without clearing the LOGIN_FLAGS_INITIAL_PDU flag
(because no additional PDU exchanges are required to complete the login).

The login has been completed but the login_work function
will find the LOGIN_FLAGS_INITIAL_PDU flag set and will
never stop from rescheduling itself;
at this point, if the initiator drops the connection, the iscsit_conn
structure will be freed, login_work will dereference a released
socket structure and the kernel crashes.

BUG: kernel NULL pointer dereference, address: 0000000000000230
PF: supervisor write access in kernel mode
PF: error_code(0x0002) - not-present page
Workqueue: events iscsi_target_do_login_rx [iscsi_target_mod]
RIP: 0010:_raw_read_lock_bh+0x15/0x30
Call trace:
 iscsi_target_do_login_rx+0x75/0x3f0 [iscsi_target_mod]
 process_one_work+0x1e8/0x3c0

Fix this bug by forcing login_work to stop after the login has been
completed and the socket callbacks have been restored.

Add a comment to clearify the return values of iscsi_target_do_login()

v3: cancel_delayed_work_sync() should be called by
    iscsi_target_start_negotiation(), because the latter is only executed
    in login_thread context

V2: remove an unnecessary call to cancel_delayed_work();
    fix a potential race condition in iscsi_start_negotiation() and
    in iscsi_target_do_login_rx()'s error paths

Signed-off-by: Maurizio Lombardi <mlombard@redhat.com>
---
 drivers/target/iscsi/iscsi_target_nego.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

Comments

Maurizio Lombardi Nov. 15, 2022, 1:58 p.m. UTC | #1
Ășt 15. 11. 2022 v 13:56 odesĂ­latel Maurizio Lombardi
<mlombard@redhat.com> napsal:
>
>
> +/**
> + * RETURN VALUE:
> + *
> + *  1 = Login successful
> + * -1 = Login failed
> + *  0 = More PDU exchanges required
> + */
>  static int iscsi_target_do_login(struct iscsit_conn *conn, struct iscsi_login *login)
>  {
>         int pdu_count = 0;
> @@ -1363,12 +1370,13 @@ int iscsi_target_start_negotiation(
>                 ret = -1;
>
>         if (ret < 0) {
> -               cancel_delayed_work_sync(&conn->login_work);
>                 iscsi_target_restore_sock_callbacks(conn);
>                 iscsi_remove_failed_auth_entry(conn);
>         }
> -       if (ret != 0)
> +       if (ret != 0) {
> +               cancel_delayed_work_sync(&conn->login_work);
>                 iscsi_target_nego_release(conn);
> +       }
>
>         return ret;
>  }
>

If ret == 1 the login was successful, the socket callbacks have been
restored by iscsi_target_do_login()
so we just have to call  cancel_delayed_work_sync() against login_work
to fix the race condition.

if ret == -1 the login was unsuccessful, I moved the
cancel_delayed_work_sync() call from the (ret < 0) block
to the (ret != 0) to close a potential race condition (we must restore
the socket callbacks first).

if ret == 0 then iscsi_target_do_login() clears
LOGIN_FLAGS_INITIAL_PDU and passes the control to login_work.

With this version of the patch, nothing has been changed in the
login_work context so it should work exactly as before.
iscsi_target_start_negotiation() is executed only in the login_thread context.


Maurizio
diff mbox series

Patch

diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c
index f2919319ad38..fab92a460623 100644
--- a/drivers/target/iscsi/iscsi_target_nego.c
+++ b/drivers/target/iscsi/iscsi_target_nego.c
@@ -1018,6 +1018,13 @@  static int iscsi_target_handle_csg_one(struct iscsit_conn *conn, struct iscsi_lo
 	return 0;
 }
 
+/**
+ * RETURN VALUE:
+ *
+ *  1 = Login successful
+ * -1 = Login failed
+ *  0 = More PDU exchanges required
+ */
 static int iscsi_target_do_login(struct iscsit_conn *conn, struct iscsi_login *login)
 {
 	int pdu_count = 0;
@@ -1363,12 +1370,13 @@  int iscsi_target_start_negotiation(
 		ret = -1;
 
 	if (ret < 0) {
-		cancel_delayed_work_sync(&conn->login_work);
 		iscsi_target_restore_sock_callbacks(conn);
 		iscsi_remove_failed_auth_entry(conn);
 	}
-	if (ret != 0)
+	if (ret != 0) {
+		cancel_delayed_work_sync(&conn->login_work);
 		iscsi_target_nego_release(conn);
+	}
 
 	return ret;
 }