[PATCH] mac80211: minstrel_ht: bound debugfs stats output construction

Pengpeng Hou posted 1 patch 1 month, 4 weeks ago
net/mac80211/rc80211_minstrel_ht_debugfs.c | 263 +++++++++++++--------
1 file changed, 168 insertions(+), 95 deletions(-)
[PATCH] mac80211: minstrel_ht: bound debugfs stats output construction
Posted by Pengpeng Hou 1 month, 4 weeks ago
minstrel_ht_stats_open() and minstrel_ht_stats_csv_open() build their
entire debugfs outputs in a fixed 32 KiB heap buffer and append each row
with raw sprintf() calls.

The number of rows depends on the current-tree minstrel group/rate
layout, and the final WARN_ON() only checks the accumulated length after
the writes have already happened.

Allocate the debugfs buffer from the current maximum row count and use
bounded appends while constructing the stats and CSV outputs.

Fixes: 9208247d74bc ("mac80211: minstrel_ht: add basic support for VHT rates <= 3SS@80MHz")
Fixes: 2cae0b6a70d6 ("mac80211: add new Minstrel-HT statistic output via csv")

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 net/mac80211/rc80211_minstrel_ht_debugfs.c | 263 +++++++++++++--------
 1 file changed, 168 insertions(+), 95 deletions(-)

diff --git a/net/mac80211/rc80211_minstrel_ht_debugfs.c b/net/mac80211/rc80211_minstrel_ht_debugfs.c
index 85149c774505..91e414a799c1 100644
--- a/net/mac80211/rc80211_minstrel_ht_debugfs.c
+++ b/net/mac80211/rc80211_minstrel_ht_debugfs.c
@@ -8,14 +8,66 @@
 #include <linux/debugfs.h>
 #include <linux/ieee80211.h>
 #include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
 #include <net/mac80211.h>
 #include "rc80211_minstrel_ht.h"
 
 struct minstrel_debugfs_info {
 	size_t len;
+	size_t size;
 	char buf[];
 };
 
+#define MINSTREL_DEBUGFS_ROW_MAX	128
+
+static size_t minstrel_debugfs_remaining(struct minstrel_debugfs_info *ms,
+					 char *p)
+{
+	return ms->size - (p - ms->buf);
+}
+
+static void minstrel_dbg_append(struct minstrel_debugfs_info *ms, char **pp,
+				const char *fmt, ...)
+{
+	size_t rem;
+	va_list args;
+	int len;
+
+	rem = minstrel_debugfs_remaining(ms, *pp);
+	if (!rem)
+		return;
+
+	va_start(args, fmt);
+	len = vscnprintf(*pp, rem, fmt, args);
+	va_end(args);
+
+	*pp += len;
+}
+
+static void minstrel_dbg_putc(struct minstrel_debugfs_info *ms, char **pp,
+			      char ch)
+{
+	if (minstrel_debugfs_remaining(ms, *pp) <= 1)
+		return;
+
+	*(*pp)++ = ch;
+}
+
+static struct minstrel_debugfs_info *
+minstrel_debugfs_info_alloc(struct minstrel_ht_sta *mi)
+{
+	size_t rows = ARRAY_SIZE(mi->groups) * MCS_GROUP_RATES + 8;
+	size_t buf_size = rows * MINSTREL_DEBUGFS_ROW_MAX;
+	struct minstrel_debugfs_info *ms;
+
+	ms = kvzalloc(sizeof(*ms) + buf_size, GFP_KERNEL);
+	if (!ms)
+		return NULL;
+	ms->size = buf_size;
+	return ms;
+}
+
 static ssize_t
 minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
 {
@@ -28,7 +80,7 @@ minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppo
 static int
 minstrel_stats_release(struct inode *inode, struct file *file)
 {
-	kfree(file->private_data);
+	kvfree(file->private_data);
 	return 0;
 }
 
@@ -45,7 +97,8 @@ minstrel_ht_is_sample_rate(struct minstrel_ht_sta *mi, int idx)
 }
 
 static char *
-minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
+minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i,
+		       struct minstrel_debugfs_info *ms, char *p)
 {
 	const struct mcs_group *mg;
 	unsigned int j, tp_max, tp_avg, eprob, tx_time;
@@ -75,33 +128,42 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
 			continue;
 
 		if (gflags & IEEE80211_TX_RC_MCS) {
-			p += sprintf(p, "HT%c0  ", htmode);
-			p += sprintf(p, "%cGI  ", gimode);
-			p += sprintf(p, "%d  ", mg->streams);
+			minstrel_dbg_append(ms, &p, "HT%c0  ", htmode);
+			minstrel_dbg_append(ms, &p, "%cGI  ", gimode);
+			minstrel_dbg_append(ms, &p, "%d  ", mg->streams);
 		} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
-			p += sprintf(p, "VHT%c0 ", htmode);
-			p += sprintf(p, "%cGI ", gimode);
-			p += sprintf(p, "%d  ", mg->streams);
+			minstrel_dbg_append(ms, &p, "VHT%c0 ", htmode);
+			minstrel_dbg_append(ms, &p, "%cGI ", gimode);
+			minstrel_dbg_append(ms, &p, "%d  ", mg->streams);
 		} else if (i == MINSTREL_OFDM_GROUP) {
-			p += sprintf(p, "OFDM       ");
-			p += sprintf(p, "1 ");
+			minstrel_dbg_append(ms, &p, "OFDM       ");
+			minstrel_dbg_append(ms, &p, "1 ");
 		} else {
-			p += sprintf(p, "CCK    ");
-			p += sprintf(p, "%cP  ", j < 4 ? 'L' : 'S');
-			p += sprintf(p, "1 ");
+			minstrel_dbg_append(ms, &p, "CCK    ");
+			minstrel_dbg_append(ms, &p, "%cP  ",
+					    j < 4 ? 'L' : 'S');
+			minstrel_dbg_append(ms, &p, "1 ");
 		}
 
-		*(p++) = (idx == mi->max_tp_rate[0]) ? 'A' : ' ';
-		*(p++) = (idx == mi->max_tp_rate[1]) ? 'B' : ' ';
-		*(p++) = (idx == mi->max_tp_rate[2]) ? 'C' : ' ';
-		*(p++) = (idx == mi->max_tp_rate[3]) ? 'D' : ' ';
-		*(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
-		*(p++) = minstrel_ht_is_sample_rate(mi, idx) ? 'S' : ' ';
+		minstrel_dbg_putc(ms, &p,
+				  (idx == mi->max_tp_rate[0]) ? 'A' : ' ');
+		minstrel_dbg_putc(ms, &p,
+				  (idx == mi->max_tp_rate[1]) ? 'B' : ' ');
+		minstrel_dbg_putc(ms, &p,
+				  (idx == mi->max_tp_rate[2]) ? 'C' : ' ');
+		minstrel_dbg_putc(ms, &p,
+				  (idx == mi->max_tp_rate[3]) ? 'D' : ' ');
+		minstrel_dbg_putc(ms, &p,
+				  (idx == mi->max_prob_rate) ? 'P' : ' ');
+		minstrel_dbg_putc(ms, &p,
+				  minstrel_ht_is_sample_rate(mi, idx) ? 'S' : ' ');
 
 		if (gflags & IEEE80211_TX_RC_MCS) {
-			p += sprintf(p, "  MCS%-2u", (mg->streams - 1) * 8 + j);
+			minstrel_dbg_append(ms, &p, "  MCS%-2u",
+					    (mg->streams - 1) * 8 + j);
 		} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
-			p += sprintf(p, "  MCS%-1u/%1u", j, mg->streams);
+			minstrel_dbg_append(ms, &p, "  MCS%-1u/%1u",
+					    j, mg->streams);
 		} else {
 			int r;
 
@@ -110,32 +172,34 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
 			else
 				r = minstrel_cck_bitrates[j % 4];
 
-			p += sprintf(p, "   %2u.%1uM", r / 10, r % 10);
+			minstrel_dbg_append(ms, &p, "   %2u.%1uM", r / 10,
+					    r % 10);
 		}
 
-		p += sprintf(p, "  %3u  ", idx);
+		minstrel_dbg_append(ms, &p, "  %3u  ", idx);
 
 		/* tx_time[rate(i)] in usec */
 		duration = mg->duration[j];
 		duration <<= mg->shift;
 		tx_time = DIV_ROUND_CLOSEST(duration, 1000);
-		p += sprintf(p, "%6u  ", tx_time);
+		minstrel_dbg_append(ms, &p, "%6u  ", tx_time);
 
 		tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
 		tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
 		eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
 
-		p += sprintf(p, "%4u.%1u    %4u.%1u     %3u.%1u"
-				"     %3u   %3u %-3u   "
-				"%9llu   %-9llu\n",
-				tp_max / 10, tp_max % 10,
-				tp_avg / 10, tp_avg % 10,
-				eprob / 10, eprob % 10,
-				mrs->retry_count,
-				mrs->last_success,
-				mrs->last_attempts,
-				(unsigned long long)mrs->succ_hist,
-				(unsigned long long)mrs->att_hist);
+		minstrel_dbg_append(ms, &p,
+				    "%4u.%1u    %4u.%1u     %3u.%1u     ",
+				    tp_max / 10, tp_max % 10,
+				    tp_avg / 10, tp_avg % 10,
+				    eprob / 10, eprob % 10);
+		minstrel_dbg_append(ms, &p, "%3u   %3u %-3u   ",
+				    mrs->retry_count,
+				    mrs->last_success,
+				    mrs->last_attempts);
+		minstrel_dbg_append(ms, &p, "%9llu   %-9llu\n",
+				    (unsigned long long)mrs->succ_hist,
+				    (unsigned long long)mrs->att_hist);
 	}
 
 	return p;
@@ -149,35 +213,35 @@ minstrel_ht_stats_open(struct inode *inode, struct file *file)
 	unsigned int i;
 	char *p;
 
-	ms = kmalloc(32768, GFP_KERNEL);
+	ms = minstrel_debugfs_info_alloc(mi);
 	if (!ms)
 		return -ENOMEM;
 
 	file->private_data = ms;
 	p = ms->buf;
 
-	p += sprintf(p, "\n");
-	p += sprintf(p,
-		     "              best    ____________rate__________    ____statistics___    _____last____    ______sum-of________\n");
-	p += sprintf(p,
-		     "mode guard #  rate   [name   idx airtime  max_tp]  [avg(tp) avg(prob)]  [retry|suc|att]  [#success | #attempts]\n");
+	minstrel_dbg_append(ms, &p, "\n");
+	minstrel_dbg_append(ms, &p,
+			    "              best    ____________rate__________    ____statistics___    _____last____    ______sum-of________\n");
+	minstrel_dbg_append(ms, &p,
+			    "mode guard #  rate   [name   idx airtime  max_tp]  [avg(tp) avg(prob)]  [retry|suc|att]  [#success | #attempts]\n");
 
-	p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, p);
+	p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, ms, p);
 	for (i = 0; i < MINSTREL_CCK_GROUP; i++)
-		p = minstrel_ht_stats_dump(mi, i, p);
+		p = minstrel_ht_stats_dump(mi, i, ms, p);
 	for (i++; i < ARRAY_SIZE(mi->groups); i++)
-		p = minstrel_ht_stats_dump(mi, i, p);
+		p = minstrel_ht_stats_dump(mi, i, ms, p);
 
-	p += sprintf(p, "\nTotal packet count::    ideal %d      "
-			"lookaround %d\n",
-			max(0, (int) mi->total_packets - (int) mi->sample_packets),
-			mi->sample_packets);
+	minstrel_dbg_append(ms, &p, "\nTotal packet count::    ideal %d      ",
+			    max(0, (int)mi->total_packets -
+				(int)mi->sample_packets));
+	minstrel_dbg_append(ms, &p, "lookaround %d\n", mi->sample_packets);
 	if (mi->avg_ampdu_len)
-		p += sprintf(p, "Average # of aggregated frames per A-MPDU: %d.%d\n",
-			MINSTREL_TRUNC(mi->avg_ampdu_len),
-			MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
+		minstrel_dbg_append(ms, &p,
+				    "Average # of aggregated frames per A-MPDU: %d.%d\n",
+				    MINSTREL_TRUNC(mi->avg_ampdu_len),
+				    MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
 	ms->len = p - ms->buf;
-	WARN_ON(ms->len + sizeof(*ms) > 32768);
 
 	return nonseekable_open(inode, file);
 }
@@ -190,7 +254,8 @@ static const struct file_operations minstrel_ht_stat_fops = {
 };
 
 static char *
-minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
+minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i,
+			   struct minstrel_debugfs_info *ms, char *p)
 {
 	const struct mcs_group *mg;
 	unsigned int j, tp_max, tp_avg, eprob, tx_time;
@@ -220,32 +285,40 @@ minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
 			continue;
 
 		if (gflags & IEEE80211_TX_RC_MCS) {
-			p += sprintf(p, "HT%c0,", htmode);
-			p += sprintf(p, "%cGI,", gimode);
-			p += sprintf(p, "%d,", mg->streams);
+			minstrel_dbg_append(ms, &p, "HT%c0,", htmode);
+			minstrel_dbg_append(ms, &p, "%cGI,", gimode);
+			minstrel_dbg_append(ms, &p, "%d,", mg->streams);
 		} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
-			p += sprintf(p, "VHT%c0,", htmode);
-			p += sprintf(p, "%cGI,", gimode);
-			p += sprintf(p, "%d,", mg->streams);
+			minstrel_dbg_append(ms, &p, "VHT%c0,", htmode);
+			minstrel_dbg_append(ms, &p, "%cGI,", gimode);
+			minstrel_dbg_append(ms, &p, "%d,", mg->streams);
 		} else if (i == MINSTREL_OFDM_GROUP) {
-			p += sprintf(p, "OFDM,,1,");
+			minstrel_dbg_append(ms, &p, "OFDM,,1,");
 		} else {
-			p += sprintf(p, "CCK,");
-			p += sprintf(p, "%cP,", j < 4 ? 'L' : 'S');
-			p += sprintf(p, "1,");
+			minstrel_dbg_append(ms, &p, "CCK,");
+			minstrel_dbg_append(ms, &p, "%cP,", j < 4 ? 'L' : 'S');
+			minstrel_dbg_append(ms, &p, "1,");
 		}
 
-		p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[0]) ? "A" : ""));
-		p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[1]) ? "B" : ""));
-		p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[2]) ? "C" : ""));
-		p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[3]) ? "D" : ""));
-		p += sprintf(p, "%s" ,((idx == mi->max_prob_rate) ? "P" : ""));
-		p += sprintf(p, "%s", (minstrel_ht_is_sample_rate(mi, idx) ? "S" : ""));
+		minstrel_dbg_append(ms, &p, "%s",
+				    (idx == mi->max_tp_rate[0]) ? "A" : "");
+		minstrel_dbg_append(ms, &p, "%s",
+				    (idx == mi->max_tp_rate[1]) ? "B" : "");
+		minstrel_dbg_append(ms, &p, "%s",
+				    (idx == mi->max_tp_rate[2]) ? "C" : "");
+		minstrel_dbg_append(ms, &p, "%s",
+				    (idx == mi->max_tp_rate[3]) ? "D" : "");
+		minstrel_dbg_append(ms, &p, "%s",
+				    (idx == mi->max_prob_rate) ? "P" : "");
+		minstrel_dbg_append(ms, &p, "%s",
+				    minstrel_ht_is_sample_rate(mi, idx) ? "S" : "");
 
 		if (gflags & IEEE80211_TX_RC_MCS) {
-			p += sprintf(p, ",MCS%-2u,", (mg->streams - 1) * 8 + j);
+			minstrel_dbg_append(ms, &p, ",MCS%-2u,",
+					    (mg->streams - 1) * 8 + j);
 		} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
-			p += sprintf(p, ",MCS%-1u/%1u,", j, mg->streams);
+			minstrel_dbg_append(ms, &p, ",MCS%-1u/%1u,",
+					    j, mg->streams);
 		} else {
 			int r;
 
@@ -254,36 +327,37 @@ minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
 			else
 				r = minstrel_cck_bitrates[j % 4];
 
-			p += sprintf(p, ",%2u.%1uM,", r / 10, r % 10);
+			minstrel_dbg_append(ms, &p, ",%2u.%1uM,",
+					    r / 10, r % 10);
 		}
 
-		p += sprintf(p, "%u,", idx);
+		minstrel_dbg_append(ms, &p, "%u,", idx);
 
 		duration = mg->duration[j];
 		duration <<= mg->shift;
 		tx_time = DIV_ROUND_CLOSEST(duration, 1000);
-		p += sprintf(p, "%u,", tx_time);
+		minstrel_dbg_append(ms, &p, "%u,", tx_time);
 
 		tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
 		tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
 		eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
 
-		p += sprintf(p, "%u.%u,%u.%u,%u.%u,%u,%u,"
-				"%u,%llu,%llu,",
-				tp_max / 10, tp_max % 10,
-				tp_avg / 10, tp_avg % 10,
-				eprob / 10, eprob % 10,
-				mrs->retry_count,
-				mrs->last_success,
-				mrs->last_attempts,
-				(unsigned long long)mrs->succ_hist,
-				(unsigned long long)mrs->att_hist);
-		p += sprintf(p, "%d,%d,%d.%d\n",
-				max(0, (int) mi->total_packets -
-				(int) mi->sample_packets),
-				mi->sample_packets,
-				MINSTREL_TRUNC(mi->avg_ampdu_len),
-				MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
+		minstrel_dbg_append(ms, &p, "%u.%u,%u.%u,%u.%u,%u,%u,%u,",
+				    tp_max / 10, tp_max % 10,
+				    tp_avg / 10, tp_avg % 10,
+				    eprob / 10, eprob % 10,
+				    mrs->retry_count,
+				    mrs->last_success,
+				    mrs->last_attempts);
+		minstrel_dbg_append(ms, &p, "%llu,%llu,",
+				    (unsigned long long)mrs->succ_hist,
+				    (unsigned long long)mrs->att_hist);
+		minstrel_dbg_append(ms, &p, "%d,%d,%d.%d\n",
+				    max(0, (int)mi->total_packets -
+					(int)mi->sample_packets),
+				    mi->sample_packets,
+				    MINSTREL_TRUNC(mi->avg_ampdu_len),
+				    MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
 	}
 
 	return p;
@@ -297,7 +371,7 @@ minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
 	unsigned int i;
 	char *p;
 
-	ms = kmalloc(32768, GFP_KERNEL);
+	ms = minstrel_debugfs_info_alloc(mi);
 	if (!ms)
 		return -ENOMEM;
 
@@ -305,14 +379,13 @@ minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
 
 	p = ms->buf;
 
-	p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, p);
+	p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, ms, p);
 	for (i = 0; i < MINSTREL_CCK_GROUP; i++)
-		p = minstrel_ht_stats_csv_dump(mi, i, p);
+		p = minstrel_ht_stats_csv_dump(mi, i, ms, p);
 	for (i++; i < ARRAY_SIZE(mi->groups); i++)
-		p = minstrel_ht_stats_csv_dump(mi, i, p);
+		p = minstrel_ht_stats_csv_dump(mi, i, ms, p);
 
 	ms->len = p - ms->buf;
-	WARN_ON(ms->len + sizeof(*ms) > 32768);
 
 	return nonseekable_open(inode, file);
 }
-- 
2.50.1 (Apple Git-155)
Re: [PATCH] mac80211: minstrel_ht: bound debugfs stats output construction
Posted by Pengpeng Hou 1 month, 3 weeks ago
Hi Johannes,

Thanks for reviewing it.

I rechecked the current minstrel group/rate bounds and agree that the
overflow claim does not hold statically. I'll drop this patch.

Thanks,
Pengpeng
Re: [PATCH] mac80211: minstrel_ht: bound debugfs stats output construction
Posted by Johannes Berg 1 month, 4 weeks ago
On Fri, 2026-04-17 at 15:56 +0800, Pengpeng Hou wrote:
> minstrel_ht_stats_open() and minstrel_ht_stats_csv_open() build their
> entire debugfs outputs in a fixed 32 KiB heap buffer and append each row
> with raw sprintf() calls.
> 
> The number of rows depends on the current-tree minstrel group/rate
> layout, and the final WARN_ON() only checks the accumulated length after
> the writes have already happened.

Which statically cannot happen, so no.

Please review your changes for actually being relevant *before* posting
them, otherwise I'm just going to start rejecting *all* your patches
without looking.

johannes