diff mbox series

[3/5] ksmbd: fix kernel oops from idr_remove()

Message ID 20220722030346.28534-3-linkinjeon@kernel.org (mailing list archive)
State New, archived
Headers show
Series [1/5] ksmbd: replace sessions list in connection with xarray | expand

Commit Message

Namjae Jeon July 22, 2022, 3:03 a.m. UTC
There is a report that kernel oops happen from idr_remove().

kernel: BUG: kernel NULL pointer dereference, address: 0000000000000010
kernel: RIP: 0010:idr_remove+0x1/0x20
kernel:  __ksmbd_close_fd+0xb2/0x2d0 [ksmbd]
kernel:  ksmbd_vfs_read+0x91/0x190 [ksmbd]
kernel:  ksmbd_fd_put+0x29/0x40 [ksmbd]
kernel:  smb2_read+0x210/0x390 [ksmbd]
kernel:  __process_request+0xa4/0x180 [ksmbd]
kernel:  __handle_ksmbd_work+0xf0/0x290 [ksmbd]
kernel:  handle_ksmbd_work+0x2d/0x50 [ksmbd]
kernel:  process_one_work+0x21d/0x3f0
kernel:  worker_thread+0x50/0x3d0
kernel:  rescuer_thread+0x390/0x390
kernel:  kthread+0xee/0x120
kthread_complete_and_exit+0x20/0x20
kernel:  ret_from_fork+0x22/0x30

While accessing files, If connection is disconnected, windows send
session setup request included previous session destroy. But while still
processing requests on previous session, this request destroy file
table, which mean file table idr will be freed and set to NULL.
So kernel oops happen from ft->idr in __ksmbd_close_fd().
This patch don't directly destroy session in destroy_previous_session().
It just set to KSMBD_SESS_EXITING so that connection will be
terminated after finishing the rest of requests.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/ksmbd/mgmt/user_session.c | 2 ++
 fs/ksmbd/smb2pdu.c           | 8 ++++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

Comments

Hyunchul Lee July 25, 2022, 12:41 a.m. UTC | #1
2022년 7월 22일 (금) 오후 12:04, Namjae Jeon <linkinjeon@kernel.org>님이 작성:
>
> There is a report that kernel oops happen from idr_remove().
>
> kernel: BUG: kernel NULL pointer dereference, address: 0000000000000010
> kernel: RIP: 0010:idr_remove+0x1/0x20
> kernel:  __ksmbd_close_fd+0xb2/0x2d0 [ksmbd]
> kernel:  ksmbd_vfs_read+0x91/0x190 [ksmbd]
> kernel:  ksmbd_fd_put+0x29/0x40 [ksmbd]
> kernel:  smb2_read+0x210/0x390 [ksmbd]
> kernel:  __process_request+0xa4/0x180 [ksmbd]
> kernel:  __handle_ksmbd_work+0xf0/0x290 [ksmbd]
> kernel:  handle_ksmbd_work+0x2d/0x50 [ksmbd]
> kernel:  process_one_work+0x21d/0x3f0
> kernel:  worker_thread+0x50/0x3d0
> kernel:  rescuer_thread+0x390/0x390
> kernel:  kthread+0xee/0x120
> kthread_complete_and_exit+0x20/0x20
> kernel:  ret_from_fork+0x22/0x30
>
> While accessing files, If connection is disconnected, windows send
> session setup request included previous session destroy. But while still
> processing requests on previous session, this request destroy file
> table, which mean file table idr will be freed and set to NULL.
> So kernel oops happen from ft->idr in __ksmbd_close_fd().
> This patch don't directly destroy session in destroy_previous_session().
> It just set to KSMBD_SESS_EXITING so that connection will be
> terminated after finishing the rest of requests.
>
> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>

Looks good to me.
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>

> ---
>  fs/ksmbd/mgmt/user_session.c | 2 ++
>  fs/ksmbd/smb2pdu.c           | 8 ++++++--
>  2 files changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ksmbd/mgmt/user_session.c b/fs/ksmbd/mgmt/user_session.c
> index 25e9ba3b7550..b9acb6770b03 100644
> --- a/fs/ksmbd/mgmt/user_session.c
> +++ b/fs/ksmbd/mgmt/user_session.c
> @@ -239,6 +239,8 @@ struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
>         sess = ksmbd_session_lookup(conn, id);
>         if (!sess && conn->binding)
>                 sess = ksmbd_session_lookup_slowpath(id);
> +       if (sess && sess->state != SMB2_SESSION_VALID)
> +               sess = NULL;
>         return sess;
>  }
>
> diff --git a/fs/ksmbd/smb2pdu.c b/fs/ksmbd/smb2pdu.c
> index 5a0328a070dc..ae5677a66cb2 100644
> --- a/fs/ksmbd/smb2pdu.c
> +++ b/fs/ksmbd/smb2pdu.c
> @@ -593,6 +593,7 @@ static void destroy_previous_session(struct ksmbd_conn *conn,
>  {
>         struct ksmbd_session *prev_sess = ksmbd_session_lookup_slowpath(id);
>         struct ksmbd_user *prev_user;
> +       struct channel *chann;
>
>         if (!prev_sess)
>                 return;
> @@ -608,8 +609,11 @@ static void destroy_previous_session(struct ksmbd_conn *conn,
>         }
>
>         put_session(prev_sess);
> -       xa_erase(&conn->sessions, prev_sess->id);
> -       ksmbd_session_destroy(prev_sess);
> +       prev_sess->state = SMB2_SESSION_EXPIRED;
> +       write_lock(&prev_sess->chann_lock);
> +       list_for_each_entry(chann, &prev_sess->ksmbd_chann_list, chann_list)
> +               chann->conn->status = KSMBD_SESS_EXITING;
> +       write_unlock(&prev_sess->chann_lock);
>  }
>
>  /**
> --
> 2.25.1
>
diff mbox series

Patch

diff --git a/fs/ksmbd/mgmt/user_session.c b/fs/ksmbd/mgmt/user_session.c
index 25e9ba3b7550..b9acb6770b03 100644
--- a/fs/ksmbd/mgmt/user_session.c
+++ b/fs/ksmbd/mgmt/user_session.c
@@ -239,6 +239,8 @@  struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
 	sess = ksmbd_session_lookup(conn, id);
 	if (!sess && conn->binding)
 		sess = ksmbd_session_lookup_slowpath(id);
+	if (sess && sess->state != SMB2_SESSION_VALID)
+		sess = NULL;
 	return sess;
 }
 
diff --git a/fs/ksmbd/smb2pdu.c b/fs/ksmbd/smb2pdu.c
index 5a0328a070dc..ae5677a66cb2 100644
--- a/fs/ksmbd/smb2pdu.c
+++ b/fs/ksmbd/smb2pdu.c
@@ -593,6 +593,7 @@  static void destroy_previous_session(struct ksmbd_conn *conn,
 {
 	struct ksmbd_session *prev_sess = ksmbd_session_lookup_slowpath(id);
 	struct ksmbd_user *prev_user;
+	struct channel *chann;
 
 	if (!prev_sess)
 		return;
@@ -608,8 +609,11 @@  static void destroy_previous_session(struct ksmbd_conn *conn,
 	}
 
 	put_session(prev_sess);
-	xa_erase(&conn->sessions, prev_sess->id);
-	ksmbd_session_destroy(prev_sess);
+	prev_sess->state = SMB2_SESSION_EXPIRED;
+	write_lock(&prev_sess->chann_lock);
+	list_for_each_entry(chann, &prev_sess->ksmbd_chann_list, chann_list)
+		chann->conn->status = KSMBD_SESS_EXITING;
+	write_unlock(&prev_sess->chann_lock);
 }
 
 /**