diff mbox series

[v3,6/6] Bluetooth: Avoid recursion in hci_conn_unlink

Message ID 20230502145737.140856-7-lrh2000@pku.edu.cn (mailing list archive)
State Superseded
Headers show
Series Bluetooth: Fix potential double free caused by hci_conn_unlink | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
tedd_an/CheckPatch success CheckPatch PASS
tedd_an/GitLint success Gitlint PASS
tedd_an/SubjectPrefix success Gitlint PASS
tedd_an/IncrementalBuild success Incremental Build PASS

Commit Message

Ruihan Li May 2, 2023, 2:57 p.m. UTC
Previously, hci_conn_unlink was implemented as a recursion function. To
unlink physical connections (e.g. ACL/LE), it calls itself to unlink all
its logical channels (e.g. SCO/eSCO/ISO).

Recursion is not required. This patch refactors hci_conn_unlink into two
functions, where hci_conn_unlink_parent takes a physical connection,
checks out all its logical channels, and calls hci_conn_unlink_child for
each logical channel to unlink it.

Signed-off-by: Ruihan Li <lrh2000@pku.edu.cn>
---
 net/bluetooth/hci_conn.c | 55 +++++++++++++++++++++++-----------------
 1 file changed, 32 insertions(+), 23 deletions(-)

Comments

Luiz Augusto von Dentz May 2, 2023, 4:18 p.m. UTC | #1
Hi Ruihan,

On Tue, May 2, 2023 at 7:57 AM Ruihan Li <lrh2000@pku.edu.cn> wrote:
>
> Previously, hci_conn_unlink was implemented as a recursion function. To
> unlink physical connections (e.g. ACL/LE), it calls itself to unlink all
> its logical channels (e.g. SCO/eSCO/ISO).
>
> Recursion is not required. This patch refactors hci_conn_unlink into two
> functions, where hci_conn_unlink_parent takes a physical connection,
> checks out all its logical channels, and calls hci_conn_unlink_child for
> each logical channel to unlink it.
>
> Signed-off-by: Ruihan Li <lrh2000@pku.edu.cn>
> ---
>  net/bluetooth/hci_conn.c | 55 +++++++++++++++++++++++-----------------
>  1 file changed, 32 insertions(+), 23 deletions(-)
>
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index de553e062..243d68a64 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -1074,34 +1074,13 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
>         return conn;
>  }
>
> -static void hci_conn_unlink(struct hci_conn *conn)
> +static void hci_conn_unlink_parent(struct hci_conn *conn)
>  {
>         struct hci_dev *hdev = conn->hdev;
>
>         bt_dev_dbg(hdev, "hcon %p", conn);
>
> -       if (!conn->parent) {
> -               struct hci_link *link, *t;
> -
> -               list_for_each_entry_safe(link, t, &conn->link_list, list) {
> -                       struct hci_conn *child = link->conn;
> -
> -                       hci_conn_unlink(child);
> -
> -                       /* Due to race, SCO connection might be not established
> -                        * yet at this point. Delete it now, otherwise it is
> -                        * possible for it to be stuck and can't be deleted.
> -                        */
> -                       if ((child->type == SCO_LINK ||
> -                            child->type == ESCO_LINK) &&
> -                           child->handle == HCI_CONN_HANDLE_UNSET)
> -                               hci_conn_del(child);
> -               }
> -
> -               return;
> -       }
> -
> -       if (!conn->link)
> +       if (WARN_ON(!conn->link))
>                 return;
>
>         list_del_rcu(&conn->link->list);
> @@ -1115,6 +1094,36 @@ static void hci_conn_unlink(struct hci_conn *conn)
>         conn->link = NULL;
>  }
>
> +static void hci_conn_unlink_children(struct hci_conn *conn)
> +{
> +       struct hci_dev *hdev = conn->hdev;
> +       struct hci_link *link, *t;
> +
> +       bt_dev_dbg(hdev, "hcon %p", conn);
> +
> +       list_for_each_entry_safe(link, t, &conn->link_list, list) {
> +               struct hci_conn *child = link->conn;
> +
> +               hci_conn_unlink_parent(child);
> +
> +               /* Due to race, SCO connection might be not established
> +                * yet at this point. Delete it now, otherwise it is
> +                * possible for it to be stuck and can't be deleted.
> +                */
> +               if (child->type == SCO_LINK || child->type == ESCO_LINK)
> +                       if (child->handle == HCI_CONN_HANDLE_UNSET)
> +                               hci_conn_del(child);
> +       }

This is not quite right, when we are unlinking the children's hci_conn
it shall only unlink itself from the parent not everything.

> +}
> +
> +static void hci_conn_unlink(struct hci_conn *conn)
> +{
> +       if (conn->parent)
> +               hci_conn_unlink_parent(conn);
> +       else
> +               hci_conn_unlink_children(conn);
> +}
> +
>  void hci_conn_del(struct hci_conn *conn)
>  {
>         struct hci_dev *hdev = conn->hdev;
> --
> 2.40.0
>
Ruihan Li May 3, 2023, 1:15 p.m. UTC | #2
Hi Luiz,

On Tue, May 02, 2023 at 09:18:02AM -0700, Luiz Augusto von Dentz wrote:
> Hi Ruihan,
> 
> On Tue, May 2, 2023 at 7:57 AM Ruihan Li <lrh2000@pku.edu.cn> wrote:
> >
> > Previously, hci_conn_unlink was implemented as a recursion function. To
> > unlink physical connections (e.g. ACL/LE), it calls itself to unlink all
> > its logical channels (e.g. SCO/eSCO/ISO).
> >
> > Recursion is not required. This patch refactors hci_conn_unlink into two
> > functions, where hci_conn_unlink_parent takes a physical connection,
> > checks out all its logical channels, and calls hci_conn_unlink_child for
> > each logical channel to unlink it.
> >
> > Signed-off-by: Ruihan Li <lrh2000@pku.edu.cn>
> > ---
> >  net/bluetooth/hci_conn.c | 55 +++++++++++++++++++++++-----------------
> >  1 file changed, 32 insertions(+), 23 deletions(-)
> >
> > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> > index de553e062..243d68a64 100644
> > --- a/net/bluetooth/hci_conn.c
> > +++ b/net/bluetooth/hci_conn.c
> > @@ -1074,34 +1074,13 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
> >         return conn;
> >  }
> >
> > -static void hci_conn_unlink(struct hci_conn *conn)
> > +static void hci_conn_unlink_parent(struct hci_conn *conn)
> >  {
> >         struct hci_dev *hdev = conn->hdev;
> >
> >         bt_dev_dbg(hdev, "hcon %p", conn);
> >
> > -       if (!conn->parent) {
> > -               struct hci_link *link, *t;
> > -
> > -               list_for_each_entry_safe(link, t, &conn->link_list, list) {
> > -                       struct hci_conn *child = link->conn;
> > -
> > -                       hci_conn_unlink(child);
> > -
> > -                       /* Due to race, SCO connection might be not established
> > -                        * yet at this point. Delete it now, otherwise it is
> > -                        * possible for it to be stuck and can't be deleted.
> > -                        */
> > -                       if ((child->type == SCO_LINK ||
> > -                            child->type == ESCO_LINK) &&
> > -                           child->handle == HCI_CONN_HANDLE_UNSET)
> > -                               hci_conn_del(child);
> > -               }
> > -
> > -               return;
> > -       }
> > -
> > -       if (!conn->link)
> > +       if (WARN_ON(!conn->link))
> >                 return;
> >
> >         list_del_rcu(&conn->link->list);
> > @@ -1115,6 +1094,36 @@ static void hci_conn_unlink(struct hci_conn *conn)
> >         conn->link = NULL;
> >  }
> >
> > +static void hci_conn_unlink_children(struct hci_conn *conn)
> > +{
> > +       struct hci_dev *hdev = conn->hdev;
> > +       struct hci_link *link, *t;
> > +
> > +       bt_dev_dbg(hdev, "hcon %p", conn);
> > +
> > +       list_for_each_entry_safe(link, t, &conn->link_list, list) {
> > +               struct hci_conn *child = link->conn;
> > +
> > +               hci_conn_unlink_parent(child);
> > +
> > +               /* Due to race, SCO connection might be not established
> > +                * yet at this point. Delete it now, otherwise it is
> > +                * possible for it to be stuck and can't be deleted.
> > +                */
> > +               if (child->type == SCO_LINK || child->type == ESCO_LINK)
> > +                       if (child->handle == HCI_CONN_HANDLE_UNSET)
> > +                               hci_conn_del(child);
> > +       }
> 
> This is not quite right, when we are unlinking the children's hci_conn
> it shall only unlink itself from the parent not everything.

My assumption is that each hci_conn is either
 * a logical channel, which implies that it has a parent and no
   children, or
 * a physical link, which means that it has no parent and possibly some
   children.
So here, as children of physical links, they must be logical channels
and thus cannot have more children, so just unlinking them from the
parent should be ok. We can add some assertions like
	WARN_ON(!conn->parent || !conn->link)   // conn has a parent
	WARN_ON(!list_empty(&conn->link_list))  // conn has no children
in hci_conn_unlink_parent.

Do you think this assumption is wrong, or could become wrong in the
future? Otherwise, I don't think there are correctness problems.

In my opinion, separating the functions for logical channels and
physical links makes the code a bit cleaner. But it's my opinion, if you
think it actually makes the code harder to understand, I won't insist.

> 
> > +}
> > +
> > +static void hci_conn_unlink(struct hci_conn *conn)
> > +{
> > +       if (conn->parent)
> > +               hci_conn_unlink_parent(conn);
> > +       else
> > +               hci_conn_unlink_children(conn);
> > +}
> > +
> >  void hci_conn_del(struct hci_conn *conn)
> >  {
> >         struct hci_dev *hdev = conn->hdev;
> > --
> > 2.40.0
> >
> 
> 
> -- 
> Luiz Augusto von Dentz

Thanks,
Ruihan Li
diff mbox series

Patch

diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index de553e062..243d68a64 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -1074,34 +1074,13 @@  struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
 	return conn;
 }
 
-static void hci_conn_unlink(struct hci_conn *conn)
+static void hci_conn_unlink_parent(struct hci_conn *conn)
 {
 	struct hci_dev *hdev = conn->hdev;
 
 	bt_dev_dbg(hdev, "hcon %p", conn);
 
-	if (!conn->parent) {
-		struct hci_link *link, *t;
-
-		list_for_each_entry_safe(link, t, &conn->link_list, list) {
-			struct hci_conn *child = link->conn;
-
-			hci_conn_unlink(child);
-
-			/* Due to race, SCO connection might be not established
-			 * yet at this point. Delete it now, otherwise it is
-			 * possible for it to be stuck and can't be deleted.
-			 */
-			if ((child->type == SCO_LINK ||
-			     child->type == ESCO_LINK) &&
-			    child->handle == HCI_CONN_HANDLE_UNSET)
-				hci_conn_del(child);
-		}
-
-		return;
-	}
-
-	if (!conn->link)
+	if (WARN_ON(!conn->link))
 		return;
 
 	list_del_rcu(&conn->link->list);
@@ -1115,6 +1094,36 @@  static void hci_conn_unlink(struct hci_conn *conn)
 	conn->link = NULL;
 }
 
+static void hci_conn_unlink_children(struct hci_conn *conn)
+{
+	struct hci_dev *hdev = conn->hdev;
+	struct hci_link *link, *t;
+
+	bt_dev_dbg(hdev, "hcon %p", conn);
+
+	list_for_each_entry_safe(link, t, &conn->link_list, list) {
+		struct hci_conn *child = link->conn;
+
+		hci_conn_unlink_parent(child);
+
+		/* Due to race, SCO connection might be not established
+		 * yet at this point. Delete it now, otherwise it is
+		 * possible for it to be stuck and can't be deleted.
+		 */
+		if (child->type == SCO_LINK || child->type == ESCO_LINK)
+			if (child->handle == HCI_CONN_HANDLE_UNSET)
+				hci_conn_del(child);
+	}
+}
+
+static void hci_conn_unlink(struct hci_conn *conn)
+{
+	if (conn->parent)
+		hci_conn_unlink_parent(conn);
+	else
+		hci_conn_unlink_children(conn);
+}
+
 void hci_conn_del(struct hci_conn *conn)
 {
 	struct hci_dev *hdev = conn->hdev;