--- /dev/null
+The gen_stats facility will add a header for the toplevel nlattr of type
+TCA_STATS2 that contains all stats added by qdisc callbacks. A reference
+to this header is stored in the gnet_dump struct, and when all the
+per-qdisc callbacks have finished adding their stats, the length of the
+containing header will be adjusted to the right value.
+
+However, on architectures that need padding (i.e., that don't set
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS), the padding nlattr is added
+before the stats, which means that the stored pointer will point to the
+padding, and so when the header is fixed up, the result is just a very
+big padding nlattr. Because most qdiscs also supply the legacy TCA_STATS
+struct, this problem has been mostly invisible, but we exposed it with
+the netlink attribute-based statistics in CAKE.
+
+Fix the issue by fixing up the stored pointer if it points to a padding
+nlattr.
+
+Tested-by: Pete Heist <pete@heistp.net>
+Tested-by: Kevin Darbyshire-Bryant <kevin@darbyshire-bryant.me.uk>
+Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
+---
+ net/core/gen_stats.c | 16 ++++++++++++++--
+ 1 file changed, 14 insertions(+), 2 deletions(-)
+
+--- a/net/core/gen_stats.c
++++ b/net/core/gen_stats.c
+@@ -77,8 +77,20 @@ gnet_stats_start_copy_compat(struct sk_b
+ d->lock = lock;
+ spin_lock_bh(lock);
+ }
+- if (d->tail)
+- return gnet_stats_copy(d, type, NULL, 0, padattr);
++ if (d->tail) {
++ int ret = gnet_stats_copy(d, type, NULL, 0, padattr);
++
++ /* The initial attribute added in gnet_stats_copy() may be
++ * preceded by a padding attribute, in which case d->tail will
++ * end up pointing at the padding instead of the real attribute.
++ * Fix this so gnet_stats_finish_copy() adjusts the length of
++ * the right attribute.
++ */
++ if (ret == 0 && d->tail->nla_type == padattr)
++ d->tail = (struct nlattr *)((char *)d->tail +
++ NLA_ALIGN(d->tail->nla_len));
++ return ret;
++ }
+
+ return 0;
+ }
--- /dev/null
+The gen_stats facility will add a header for the toplevel nlattr of type
+TCA_STATS2 that contains all stats added by qdisc callbacks. A reference
+to this header is stored in the gnet_dump struct, and when all the
+per-qdisc callbacks have finished adding their stats, the length of the
+containing header will be adjusted to the right value.
+
+However, on architectures that need padding (i.e., that don't set
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS), the padding nlattr is added
+before the stats, which means that the stored pointer will point to the
+padding, and so when the header is fixed up, the result is just a very
+big padding nlattr. Because most qdiscs also supply the legacy TCA_STATS
+struct, this problem has been mostly invisible, but we exposed it with
+the netlink attribute-based statistics in CAKE.
+
+Fix the issue by fixing up the stored pointer if it points to a padding
+nlattr.
+
+Tested-by: Pete Heist <pete@heistp.net>
+Tested-by: Kevin Darbyshire-Bryant <kevin@darbyshire-bryant.me.uk>
+Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
+---
+ net/core/gen_stats.c | 16 ++++++++++++++--
+ 1 file changed, 14 insertions(+), 2 deletions(-)
+
+--- a/net/core/gen_stats.c
++++ b/net/core/gen_stats.c
+@@ -77,8 +77,20 @@ gnet_stats_start_copy_compat(struct sk_b
+ d->lock = lock;
+ spin_lock_bh(lock);
+ }
+- if (d->tail)
+- return gnet_stats_copy(d, type, NULL, 0, padattr);
++ if (d->tail) {
++ int ret = gnet_stats_copy(d, type, NULL, 0, padattr);
++
++ /* The initial attribute added in gnet_stats_copy() may be
++ * preceded by a padding attribute, in which case d->tail will
++ * end up pointing at the padding instead of the real attribute.
++ * Fix this so gnet_stats_finish_copy() adjusts the length of
++ * the right attribute.
++ */
++ if (ret == 0 && d->tail->nla_type == padattr)
++ d->tail = (struct nlattr *)((char *)d->tail +
++ NLA_ALIGN(d->tail->nla_len));
++ return ret;
++ }
+
+ return 0;
+ }