diff mbox

[v4,net-next] ppp: Fix one deadlock issue of PPP when reentrant

Message ID 20160822123525.ycymv43cdaoykuqt@alphalink.fr
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Guillaume Nault Aug. 22, 2016, 12:35 p.m. UTC
On Mon, Aug 22, 2016 at 09:20:14AM +0800, fgao@ikuai8.com wrote:
> From: Gao Feng <fgao@ikuai8.com>
> 
> PPP channel holds one spinlock before send frame. But the skb may
> select the same PPP channel with wrong route policy. As a result,
> the skb reaches the same channel path. It tries to get the same
> spinlock which is held before. Bang, the deadlock comes out.
>
Thanks for following up on this case.
On my side, I've thought a bit more about it in the weekend and cooked
this patch.
It's experimental and requires cleanup and further testing, but it
should fix all issues I could think of (at least for PPP over L2TP).

The main idea is to use a per-cpu variable to detect recursion in
selected points of PPP and L2TP xmit path.

---
 drivers/net/ppp/ppp_generic.c | 49 ++++++++++++++++++++++++++++++++-----------
 net/l2tp/l2tp_core.c          | 28 +++++++++++++++++++++----
 2 files changed, 61 insertions(+), 16 deletions(-)

Comments

Feng Gao Aug. 22, 2016, 1:16 p.m. UTC | #1
It seems a better solution, simple and apparent.
I accept any best solution which could make kernel works well :))

Best Regards
Feng

On Mon, Aug 22, 2016 at 8:35 PM, Guillaume Nault <g.nault@alphalink.fr> wrote:
> On Mon, Aug 22, 2016 at 09:20:14AM +0800, fgao@ikuai8.com wrote:
>> From: Gao Feng <fgao@ikuai8.com>
>>
>> PPP channel holds one spinlock before send frame. But the skb may
>> select the same PPP channel with wrong route policy. As a result,
>> the skb reaches the same channel path. It tries to get the same
>> spinlock which is held before. Bang, the deadlock comes out.
>>
> Thanks for following up on this case.
> On my side, I've thought a bit more about it in the weekend and cooked
> this patch.
> It's experimental and requires cleanup and further testing, but it
> should fix all issues I could think of (at least for PPP over L2TP).
>
> The main idea is to use a per-cpu variable to detect recursion in
> selected points of PPP and L2TP xmit path.
>
> ---
>  drivers/net/ppp/ppp_generic.c | 49 ++++++++++++++++++++++++++++++++-----------
>  net/l2tp/l2tp_core.c          | 28 +++++++++++++++++++++----
>  2 files changed, 61 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
> index f226db4..c33036bf 100644
> --- a/drivers/net/ppp/ppp_generic.c
> +++ b/drivers/net/ppp/ppp_generic.c
> @@ -1354,6 +1354,8 @@ static void ppp_setup(struct net_device *dev)
>         dev->netdev_ops = &ppp_netdev_ops;
>         SET_NETDEV_DEVTYPE(dev, &ppp_type);
>
> +       dev->features |= NETIF_F_LLTX;
> +
>         dev->hard_header_len = PPP_HDRLEN;
>         dev->mtu = PPP_MRU;
>         dev->addr_len = 0;
> @@ -1367,12 +1369,7 @@ static void ppp_setup(struct net_device *dev)
>   * Transmit-side routines.
>   */
>
> -/*
> - * Called to do any work queued up on the transmit side
> - * that can now be done.
> - */
> -static void
> -ppp_xmit_process(struct ppp *ppp)
> +static void __ppp_xmit_process(struct ppp *ppp)
>  {
>         struct sk_buff *skb;
>
> @@ -1392,6 +1389,23 @@ ppp_xmit_process(struct ppp *ppp)
>         ppp_xmit_unlock(ppp);
>  }
>
> +static DEFINE_PER_CPU(int, ppp_xmit_recursion);
> +
> +/* Called to do any work queued up on the transmit side that can now be done */
> +static void ppp_xmit_process(struct ppp *ppp)
> +{
> +       if (unlikely(__this_cpu_read(ppp_xmit_recursion))) {
> +               WARN(1, "recursion detected\n");
> +               return;
> +       }
> +
> +       __this_cpu_inc(ppp_xmit_recursion);
> +       local_bh_disable();
> +       __ppp_xmit_process(ppp);
> +       local_bh_enable();
> +       __this_cpu_dec(ppp_xmit_recursion);
> +}
> +
>  static inline struct sk_buff *
>  pad_compress_skb(struct ppp *ppp, struct sk_buff *skb)
>  {
> @@ -1847,11 +1861,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
>  }
>  #endif /* CONFIG_PPP_MULTILINK */
>
> -/*
> - * Try to send data out on a channel.
> - */
> -static void
> -ppp_channel_push(struct channel *pch)
> +static void __ppp_channel_push(struct channel *pch)
>  {
>         struct sk_buff *skb;
>         struct ppp *ppp;
> @@ -1876,11 +1886,26 @@ ppp_channel_push(struct channel *pch)
>                 read_lock_bh(&pch->upl);
>                 ppp = pch->ppp;
>                 if (ppp)
> -                       ppp_xmit_process(ppp);
> +                       __ppp_xmit_process(ppp);
>                 read_unlock_bh(&pch->upl);
>         }
>  }
>
> +/* Try to send data out on a channel */
> +static void ppp_channel_push(struct channel *pch)
> +{
> +       if (unlikely(__this_cpu_read(ppp_xmit_recursion))) {
> +               WARN(1, "recursion detected\n");
> +               return;
> +       }
> +
> +       __this_cpu_inc(ppp_xmit_recursion);
> +       local_bh_disable();
> +       __ppp_channel_push(pch);
> +       local_bh_enable();
> +       __this_cpu_dec(ppp_xmit_recursion);
> +}
> +
>  /*
>   * Receive-side routines.
>   */
> diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
> index 1e40dac..bdfb1be 100644
> --- a/net/l2tp/l2tp_core.c
> +++ b/net/l2tp/l2tp_core.c
> @@ -1096,10 +1096,8 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb,
>         return 0;
>  }
>
> -/* If caller requires the skb to have a ppp header, the header must be
> - * inserted in the skb data before calling this function.
> - */
> -int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len)
> +static int __l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb,
> +                          int hdr_len)
>  {
>         int data_len = skb->len;
>         struct l2tp_tunnel *tunnel = session->tunnel;
> @@ -1178,6 +1176,28 @@ out_unlock:
>
>         return ret;
>  }
> +
> +static DEFINE_PER_CPU(int, l2tp_xmit_recursion);
> +
> +/* If caller requires the skb to have a ppp header, the header must be
> + * inserted in the skb data before calling this function.
> + */
> +int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb,
> +                 int hdr_len)
> +{
> +       int ret;
> +
> +       if (unlikely(__this_cpu_read(l2tp_xmit_recursion))) {
> +               WARN(1, "recursion detected\n");
> +               return NET_XMIT_DROP;
> +       }
> +
> +       __this_cpu_inc(l2tp_xmit_recursion);
> +       ret = __l2tp_xmit_skb(session, skb, hdr_len);
> +       __this_cpu_dec(l2tp_xmit_recursion);
> +
> +       return ret;
> +}
>  EXPORT_SYMBOL_GPL(l2tp_xmit_skb);
>
>  /*****************************************************************************
> --
> 2.9.3
Guillaume Nault Aug. 22, 2016, 2:48 p.m. UTC | #2
On Mon, Aug 22, 2016 at 09:16:16PM +0800, Feng Gao wrote:
> It seems a better solution, simple and apparent.
> I accept any best solution which could make kernel works well :))
> 
Thanks. I need to rework it a bit and do wider testing.
If everything goes fine, I should have something to submit formally
before the end of the week.
diff mbox

Patch

diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
index f226db4..c33036bf 100644
--- a/drivers/net/ppp/ppp_generic.c
+++ b/drivers/net/ppp/ppp_generic.c
@@ -1354,6 +1354,8 @@  static void ppp_setup(struct net_device *dev)
 	dev->netdev_ops = &ppp_netdev_ops;
 	SET_NETDEV_DEVTYPE(dev, &ppp_type);
 
+	dev->features |= NETIF_F_LLTX;
+
 	dev->hard_header_len = PPP_HDRLEN;
 	dev->mtu = PPP_MRU;
 	dev->addr_len = 0;
@@ -1367,12 +1369,7 @@  static void ppp_setup(struct net_device *dev)
  * Transmit-side routines.
  */
 
-/*
- * Called to do any work queued up on the transmit side
- * that can now be done.
- */
-static void
-ppp_xmit_process(struct ppp *ppp)
+static void __ppp_xmit_process(struct ppp *ppp)
 {
 	struct sk_buff *skb;
 
@@ -1392,6 +1389,23 @@  ppp_xmit_process(struct ppp *ppp)
 	ppp_xmit_unlock(ppp);
 }
 
+static DEFINE_PER_CPU(int, ppp_xmit_recursion);
+
+/* Called to do any work queued up on the transmit side that can now be done */
+static void ppp_xmit_process(struct ppp *ppp)
+{
+	if (unlikely(__this_cpu_read(ppp_xmit_recursion))) {
+		WARN(1, "recursion detected\n");
+		return;
+	}
+
+	__this_cpu_inc(ppp_xmit_recursion);
+	local_bh_disable();
+	__ppp_xmit_process(ppp);
+	local_bh_enable();
+	__this_cpu_dec(ppp_xmit_recursion);
+}
+
 static inline struct sk_buff *
 pad_compress_skb(struct ppp *ppp, struct sk_buff *skb)
 {
@@ -1847,11 +1861,7 @@  static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 }
 #endif /* CONFIG_PPP_MULTILINK */
 
-/*
- * Try to send data out on a channel.
- */
-static void
-ppp_channel_push(struct channel *pch)
+static void __ppp_channel_push(struct channel *pch)
 {
 	struct sk_buff *skb;
 	struct ppp *ppp;
@@ -1876,11 +1886,26 @@  ppp_channel_push(struct channel *pch)
 		read_lock_bh(&pch->upl);
 		ppp = pch->ppp;
 		if (ppp)
-			ppp_xmit_process(ppp);
+			__ppp_xmit_process(ppp);
 		read_unlock_bh(&pch->upl);
 	}
 }
 
+/* Try to send data out on a channel */
+static void ppp_channel_push(struct channel *pch)
+{
+	if (unlikely(__this_cpu_read(ppp_xmit_recursion))) {
+		WARN(1, "recursion detected\n");
+		return;
+	}
+
+	__this_cpu_inc(ppp_xmit_recursion);
+	local_bh_disable();
+	__ppp_channel_push(pch);
+	local_bh_enable();
+	__this_cpu_dec(ppp_xmit_recursion);
+}
+
 /*
  * Receive-side routines.
  */
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
index 1e40dac..bdfb1be 100644
--- a/net/l2tp/l2tp_core.c
+++ b/net/l2tp/l2tp_core.c
@@ -1096,10 +1096,8 @@  static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb,
 	return 0;
 }
 
-/* If caller requires the skb to have a ppp header, the header must be
- * inserted in the skb data before calling this function.
- */
-int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len)
+static int __l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb,
+			   int hdr_len)
 {
 	int data_len = skb->len;
 	struct l2tp_tunnel *tunnel = session->tunnel;
@@ -1178,6 +1176,28 @@  out_unlock:
 
 	return ret;
 }
+
+static DEFINE_PER_CPU(int, l2tp_xmit_recursion);
+
+/* If caller requires the skb to have a ppp header, the header must be
+ * inserted in the skb data before calling this function.
+ */
+int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb,
+		  int hdr_len)
+{
+	int ret;
+
+	if (unlikely(__this_cpu_read(l2tp_xmit_recursion))) {
+		WARN(1, "recursion detected\n");
+		return NET_XMIT_DROP;
+	}
+
+	__this_cpu_inc(l2tp_xmit_recursion);
+	ret = __l2tp_xmit_skb(session, skb, hdr_len);
+	__this_cpu_dec(l2tp_xmit_recursion);
+
+	return ret;
+}
 EXPORT_SYMBOL_GPL(l2tp_xmit_skb);
 
 /*****************************************************************************