Skip to contentSkip to conversation

net: ieee802154: 6lowpan: fix frag reassembly

PATCH In review Alexander Aring 3 participants 5 messages Apr 20, 2018 – Apr 25, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 net: ieee802154: 6lowpan: fix frag reassembly

This patch initialize stack variables which are used in frag_lowpan_compare_key to zero. In my case there are padding bytes in the structures ieee802154_addr as well in frag_lowpan_compare_key. Otherwise the key variable contains random bytes. The result is that a compare of two keys by memcmp works incorrect.

Fixes 648700f76b03 ("inet: frags: use rhashtables for reassembly units")

---
So far I see it's a case of 32 alignment in frag_v4_compare_key and frag_v6_compare_key and I am not sure about if this works on all arch correctly.

 net/ieee802154/6lowpan/6lowpan_i.h  |  4 ++--
 net/ieee802154/6lowpan/reassembly.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)
net/ieee802154/6lowpan/6lowpan_i.h +2 -2
index b8d95cb71c25..44a7e16bf3b5 100644
@@ -20,8 +20,8 @@ typedef unsigned __bitwise lowpan_rx_result;
 struct frag_lowpan_compare_key {
 	u16 tag;
 	u16 d_size;
-	const struct ieee802154_addr src;
-	const struct ieee802154_addr dst;
+	struct ieee802154_addr src;
+	struct ieee802154_addr dst;
 };
 
 /* Equivalent of ipv4 struct ipq
net/ieee802154/6lowpan/reassembly.c +7 -7
index 1790b65944b3..2cc224106b69 100644
@@ -75,14 +75,14 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb,
 {
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	struct frag_lowpan_compare_key key = {
-		.tag = cb->d_tag,
-		.d_size = cb->d_size,
-		.src = *src,
-		.dst = *dst,
-	};
+	struct frag_lowpan_compare_key key = {};
 	struct inet_frag_queue *q;
 
+	key.tag = cb->d_tag;
+	key.d_size = cb->d_size;
+	key.src = *src;
+	key.dst = *dst;
+
 	q = inet_frag_find(&ieee802154_lowpan->frags, &key);
 	if (!q)
 		return NULL;
@@ -372,7 +372,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 	struct lowpan_frag_queue *fq;
 	struct net *net = dev_net(skb->dev);
 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = {};
 	int err;
 
 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
Signature · 1 line
2.11.0
#1 lore raw

Hello.

Quoted 9 lines · Alexander Aring

This patch initialize stack variables which are used in frag_lowpan_compare_key to zero. In my case there are padding bytes in the structures ieee802154_addr as well in frag_lowpan_compare_key. Otherwise the key variable contains random bytes. The result is that a compare of two keys by memcmp works incorrect.

Fixes 648700f76b03 ("inet: frags: use rhashtables for reassembly units")

Thanks for having another attempt on this. I dropped the __packed patch you sent earlier.

Giving this some more testing with SSH and large ping packets and it is as stable again as before.

Eric, you did the original change so let me know if you have a problem with this.

Quoted 50 lines

---
So far I see it's a case of 32 alignment in frag_v4_compare_key and frag_v6_compare_key and I am not sure about if this works on all arch correctly.

 net/ieee802154/6lowpan/6lowpan_i.h  |  4 ++--
 net/ieee802154/6lowpan/reassembly.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)
net/ieee802154/6lowpan/6lowpan_i.h +2 -2
index b8d95cb71c25..44a7e16bf3b5 100644
@@ -20,8 +20,8 @@ typedef unsigned __bitwise lowpan_rx_result;
 struct frag_lowpan_compare_key {
 	u16 tag;
 	u16 d_size;
-	const struct ieee802154_addr src;
-	const struct ieee802154_addr dst;
+	struct ieee802154_addr src;
+	struct ieee802154_addr dst;
 };
 
 /* Equivalent of ipv4 struct ipq
net/ieee802154/6lowpan/reassembly.c +6 -6
index 1790b65944b3..2cc224106b69 100644
@@ -75,14 +75,14 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb,
 {
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	struct frag_lowpan_compare_key key = {
-		.tag = cb->d_tag,
-		.d_size = cb->d_size,
-		.src = *src,
-		.dst = *dst,
-	};
+	struct frag_lowpan_compare_key key = {};
 	struct inet_frag_queue *q;
 
+	key.tag = cb->d_tag;
+	key.d_size = cb->d_size;
+	key.src = *src;
+	key.dst = *dst;
+
 	q = inet_frag_find(&ieee802154_lowpan->frags, &key);
 	if (!q)
 		return NULL;

net/ieee802154/6lowpan/reassembly.c int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) · line 378

Quoted 8 lines
net/ieee802154/6lowpan/reassembly.c +1 -1
@@ -372,7 +372,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 	struct lowpan_frag_queue *fq;
 	struct net *net = dev_net(skb->dev);
 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = {};
 	int err;
 
 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)

regards
Stefan Schmidt

#2 lore raw
In reply to Alexander Aring

Hello.

Quoted 59 lines · Alexander Aring

This patch initialize stack variables which are used in frag_lowpan_compare_key to zero. In my case there are padding bytes in the structures ieee802154_addr as well in frag_lowpan_compare_key. Otherwise the key variable contains random bytes. The result is that a compare of two keys by memcmp works incorrect.

Fixes 648700f76b03 ("inet: frags: use rhashtables for reassembly units")

---
So far I see it's a case of 32 alignment in frag_v4_compare_key and frag_v6_compare_key and I am not sure about if this works on all arch correctly.

 net/ieee802154/6lowpan/6lowpan_i.h  |  4 ++--
 net/ieee802154/6lowpan/reassembly.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)
net/ieee802154/6lowpan/6lowpan_i.h +2 -2
index b8d95cb71c25..44a7e16bf3b5 100644
@@ -20,8 +20,8 @@ typedef unsigned __bitwise lowpan_rx_result;
 struct frag_lowpan_compare_key {
 	u16 tag;
 	u16 d_size;
-	const struct ieee802154_addr src;
-	const struct ieee802154_addr dst;
+	struct ieee802154_addr src;
+	struct ieee802154_addr dst;
 };
 
 /* Equivalent of ipv4 struct ipq
net/ieee802154/6lowpan/reassembly.c +6 -6
index 1790b65944b3..2cc224106b69 100644
@@ -75,14 +75,14 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb,
 {
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	struct frag_lowpan_compare_key key = {
-		.tag = cb->d_tag,
-		.d_size = cb->d_size,
-		.src = *src,
-		.dst = *dst,
-	};
+	struct frag_lowpan_compare_key key = {};
 	struct inet_frag_queue *q;
 
+	key.tag = cb->d_tag;
+	key.d_size = cb->d_size;
+	key.src = *src;
+	key.dst = *dst;
+
 	q = inet_frag_find(&ieee802154_lowpan->frags, &key);
 	if (!q)
 		return NULL;

net/ieee802154/6lowpan/reassembly.c int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) · line 378

Quoted 8 lines
net/ieee802154/6lowpan/reassembly.c +1 -1
@@ -372,7 +372,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 	struct lowpan_frag_queue *fq;
 	struct net *net = dev_net(skb->dev);
 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = {};
 	int err;
 
 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)

This patch has been applied to the wpan tree and will be part of the next pull request to net. Thanks!

regards
Stefan Schmidt

#3 lore raw
In reply to Alexander Aring
Quoted 60 lines · Alexander Aring

This patch initialize stack variables which are used in frag_lowpan_compare_key to zero. In my case there are padding bytes in the structures ieee802154_addr as well in frag_lowpan_compare_key. Otherwise the key variable contains random bytes. The result is that a compare of two keys by memcmp works incorrect.

Fixes 648700f76b03 ("inet: frags: use rhashtables for reassembly units")

---
So far I see it's a case of 32 alignment in frag_v4_compare_key and frag_v6_compare_key and I am not sure about if this works on all arch correctly.

 net/ieee802154/6lowpan/6lowpan_i.h  |  4 ++--
 net/ieee802154/6lowpan/reassembly.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)
net/ieee802154/6lowpan/6lowpan_i.h +2 -2
index b8d95cb71c25..44a7e16bf3b5 100644
@@ -20,8 +20,8 @@ typedef unsigned __bitwise lowpan_rx_result;
 struct frag_lowpan_compare_key {
 	u16 tag;
 	u16 d_size;
-	const struct ieee802154_addr src;
-	const struct ieee802154_addr dst;
+	struct ieee802154_addr src;
+	struct ieee802154_addr dst;
 };
 
 /* Equivalent of ipv4 struct ipq
net/ieee802154/6lowpan/reassembly.c +6 -6
index 1790b65944b3..2cc224106b69 100644
@@ -75,14 +75,14 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb,
 {
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	struct frag_lowpan_compare_key key = {
-		.tag = cb->d_tag,
-		.d_size = cb->d_size,
-		.src = *src,
-		.dst = *dst,
-	};
+	struct frag_lowpan_compare_key key = {};
 	struct inet_frag_queue *q;
 
+	key.tag = cb->d_tag;
+	key.d_size = cb->d_size;
+	key.src = *src;
+	key.dst = *dst;
+
 	q = inet_frag_find(&ieee802154_lowpan->frags, &key);
 	if (!q)
 		return NULL;

net/ieee802154/6lowpan/reassembly.c int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) · line 378

Quoted 8 lines
net/ieee802154/6lowpan/reassembly.c +1 -1
@@ -372,7 +372,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 	struct lowpan_frag_queue *fq;
 	struct net *net = dev_net(skb->dev);
 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = {};
 	int err;
 
 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)

Hi Alexander.

Thanks for working on this !

It looks like only the last chunk was really needed to fix this bug, right ?

Compiler should really init everything, and not leave garbage bytes in :

	struct frag_lowpan_compare_key key = {
		.tag = cb->d_tag,
		.d_size = cb->d_size,
		.src = *src,
		.dst = *dst,
	};
#4 lore raw

Hi,

Quoted 69 lines · Eric Dumazet
Quoted 66 lines · Alexander Aring

This patch initialize stack variables which are used in frag_lowpan_compare_key to zero. In my case there are padding bytes in the structures ieee802154_addr as well in frag_lowpan_compare_key. Otherwise the key variable contains random bytes. The result is that a compare of two keys by memcmp works incorrect.

Fixes 648700f76b03 ("inet: frags: use rhashtables for reassembly units")

---
So far I see it's a case of 32 alignment in frag_v4_compare_key and frag_v6_compare_key and I am not sure about if this works on all arch correctly.

 net/ieee802154/6lowpan/6lowpan_i.h  |  4 ++--
 net/ieee802154/6lowpan/reassembly.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)
net/ieee802154/6lowpan/6lowpan_i.h +2 -2
index b8d95cb71c25..44a7e16bf3b5 100644
@@ -20,8 +20,8 @@ typedef unsigned __bitwise lowpan_rx_result;
 struct frag_lowpan_compare_key {
 	u16 tag;
 	u16 d_size;
-	const struct ieee802154_addr src;
-	const struct ieee802154_addr dst;
+	struct ieee802154_addr src;
+	struct ieee802154_addr dst;
 };
 
 /* Equivalent of ipv4 struct ipq
net/ieee802154/6lowpan/reassembly.c +7 -7
index 1790b65944b3..2cc224106b69 100644
@@ -75,14 +75,14 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb,
 {
 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
 		net_ieee802154_lowpan(net);
-	struct frag_lowpan_compare_key key = {
-		.tag = cb->d_tag,
-		.d_size = cb->d_size,
-		.src = *src,
-		.dst = *dst,
-	};
+	struct frag_lowpan_compare_key key = {};
 	struct inet_frag_queue *q;
 
+	key.tag = cb->d_tag;
+	key.d_size = cb->d_size;
+	key.src = *src;
+	key.dst = *dst;
+
 	q = inet_frag_find(&ieee802154_lowpan->frags, &key);
 	if (!q)
 		return NULL;
@@ -372,7 +372,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
 	struct lowpan_frag_queue *fq;
 	struct net *net = dev_net(skb->dev);
 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = {};
 	int err;
 
Quoted 9 lines

net/ieee802154/6lowpan/reassembly.c int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) · line 378

Quoted 2 lines
net/ieee802154/6lowpan/reassembly.c +0 -0
 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)

Hi Alexander.

Thanks for working on this !

It looks like only the last chunk was really needed to fix this bug, right ?

Yes, the last chunk is needed because:

.src = *src,

but it will not fix everything, see below. The reason why I was not sure is that I get packet drops with many fragments and _virtual_ hardware which doesn't should have any drops because I don't use something to make drops.

Quoted 8 lines

Compiler should really init everything, and not leave garbage bytes in :

	struct frag_lowpan_compare_key key = {
		.tag = cb->d_tag,
		.d_size = cb->d_size,
		.src = *src,
		.dst = *dst,
	};

I talked privately with Stefan and we both use (but I have the same issue with my host compiler 6.3.0 debian stretch):

gcc version 7.3.1

in my case I use a x86_64 qemu emulation.

my test is:

$ ping fe80::38c7:dc15:6089:4cc8%lowpan0 -s 1024 -c 10000 -i 0.025

which ends in about ~10 fragments (I don't remember the exact value, we have ~80 bytes payload per frame only)

and I get back:

10000 packets transmitted, 6192 received, 38% packet loss, time 326431ms

ping implementation is:

ping utility, iputils-s20161105

I did the following changes:

---
@@ -590,8 +593,30 @@ static int lowpan_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *ptr)

 {
        const struct frag_lowpan_compare_key *key = arg->key;
        const struct inet_frag_queue *fq = ptr;

+ const struct frag_lowpan_compare_key *key2 = (const struct frag_lowpan_compare_key *)&fq->key;
+ int rc, rc2;
+
+ rc2 = !!memcmp(&fq->key, key, sizeof(*key));
+
+ if (sizeof(*key) > sizeof(fq->key))
+ BUG();
+
+ rc = ((key2->tag == key->tag) &&

+             (key2->d_size == key->d_size) &&
+             ieee802154_addr_equal(&key2->src, &key->src) &&
+             ieee802154_addr_equal(&key2->dst, &key->dst));

+
+ if (rc) {
+ if (rc2 != 0) {
+ pr_info("XXXXXXXXXXXXXXXXXXXX BINGO %d\n", rc2);
+ print_hex_dump(KERN_INFO, "key data: ", DUMP_PREFIX_ADDRESS,
+ 16, 1, key, sizeof(*key), true);
+ print_hex_dump(KERN_INFO, "key2 data: ", DUMP_PREFIX_ADDRESS,
+ 16, 1, key2, sizeof(*key2), true);
+ }
+ }
---

This change will check between memcmp() and fields compare and report a BINGO inclusive hexdump() when they report different results which they should not.

While debugging that I saw that fq->key is a union which doesn't has the "frag_lowpan_compare_key" inside. Which is still fine because we are not bigger than frag_v6_compare_key. I did some BUG() if this occurs. Just as side note.

Please also note I _only_ get this code triggered when I am lucky and the hashfn report the same hash for some mismatch keys (I saw this is already the case and seems enough to debug).

I get this on my dmesg output:

---
6LoWPAN: XXXXXXXXXXXXXXXXXXXX BINGO 1
key data: 000000004c4f1290: 55 0b 30 04 ff ff ff ff 02 00 ef be 00 00 00 00 U.0............. key data: 00000000f28c6d16: cd 0b 00 00 00 00 00 00 03 00 ef be 00 00 00 00 ................
key data: 00000000c3564bd8: c8 4c 89 60 15 dc c7 3a .L.`...:
key2 data: 000000000469f467: 55 0b 30 04 02 88 ff ff 02 00 ef be 00 00 00 00 U.0............. key2 data: 00000000c3bc7aca: cd 0b 00 00 00 00 00 00 03 00 ef be 00 00 00 00 ................
key2 data: 00000000d2a3a530: c8 4c 89 60 15 dc c7 3a .L.`...:
---

pahole reports about frag_lowpan_compare_key the following (we should move the padding "bits" to the end. Now I know how to deal with pahole):

---
struct frag_lowpan_compare_key {

    u16                        tag;                  /*     0     2 */
    u16                        d_size;               /*     2     2 */
    /* XXX 4 bytes hole, try to pack */

    struct ieee802154_addr     src;                  /*     8    16 */
    struct ieee802154_addr     dst;                  /*    24    16 */

    /* size: 40, cachelines: 1, members: 4 */
    /* sum members: 36, holes: 1, sum holes: 4 */
    /* last cacheline: 40 bytes */
};

---

As you can see starting at offset 4 size 4 I have a 4 bytes hole. Compare to the hexdump: (I marked the bytes with ^^)

---
key data: 000000004c4f1290: 55 0b 30 04 ff ff ff ff 02 00 ef be 00 00 00 00 U.0.............

                                        ^^ ^^ ^^ ^^
key data: 00000000f28c6d16: cd 0b 00 00 00 00 00 00 03 00 ef be 00 00 00 00  ................
key data: 00000000c3564bd8: c8 4c 89 60 15 dc c7 3a                          .L.`...:
key2 data: 000000000469f467: 55 0b 30 04 02 88 ff ff 02 00 ef be 00 00 00 00  U.0.............
                                         ^^ ^^ ^^ ^^

key2 data: 00000000c3bc7aca: cd 0b 00 00 00 00 00 00 03 00 ef be 00 00 00 00 ................
key2 data: 00000000d2a3a530: c8 4c 89 60 15 dc c7 3a .L.`...:
---

So the padding bits are different. The compare via memcmp didn't reported 0, but compare by fields it reports that it's equal. That's when my BINGO hits.

I received a lot of BINGO messages, but remember I had only luck that the hashfn reports the same hash for it which seems to confirm there is something not working with the padding bits.

I added afterwards this:

---
+ print_hex_dump(KERN_INFO, "key data before inet_frag_find: ", DUMP_PREFIX_ADDRESS,
+ 16, 1, &key, sizeof(key), true);
---

right before calling inet_frag_find and check for the padding bits.
I did the same command again but with a -s 100, which should end in two fragments:

---
key data before inet_frag_find: 0000000056195e0f: 06 00 94 00 00 00 00 00 03 00 ef be 00 00 00 00 ................

                                                              ^^ ^^ ^^ ^^ 
key data before inet_frag_find: 00000000bc3eefd4: 57 bc 05 52 e9 ea 9b be 02 00 ef be 00 00 00 00  W..R............
key data before inet_frag_find: 00000000a1c4ed4e: cd 0b 00 00 00 00 00 00                          ........
key data before inet_frag_find: 0000000056195e0f: 06 00 94 00 02 88 ff ff 03 00 ef be 00 00 00 00  ................
                                                              ^^ ^^ ^^ ^^

key data before inet_frag_find: 00000000bc3eefd4: 57 bc 05 52 e9 ea 9b be 02 00 ef be 00 00 00 00 W..R............ key data before inet_frag_find: 00000000a1c4ed4e: cd 0b 00 00 00 00 00 00
---

It seems at the first fragment the stack will be zero and then for the next fragment there are some random bits inside the padding bits. Maybe this behavior is compiler related... above the first fragment seems to have ff..ff inside the padding bits, that's so far what fq->key tells me and so far I understood is the first fragment.
(I only suppose here that the fragments will arrive synchronized order).

Also I get nothing reassembled anymore and the random padding bits are for all followed frags always the same. 02 88 ff ff, I think it doesn't matter...

----------

Everything works fine when I init frag_lowpan_compare_key with zero by doing = {} and assign the values afterwards.

Ping reports:

10000 packets transmitted, 10000 received, 0% packet loss, time 1108587ms

This is what I assume for virtual hardware... but I guess I am only lucky, that gcc handles = {} as memset(&foo, 0, sizeof(..)) somehow.

That's all what I can tell right now...

What should I do now? I am ready to debug this more...

- Alex