如何在 C(在 Linux 中)使用新的 counted_by 属性
counted_by 属性
counted_by
属性首次在 Clang-18 中引入,并即将在 GCC-15 中使用。它的主要功能是将一个 flexible-array 成员
与一个结构成员关联起来,这个结构成员在运行时会保存该数组中的元素数量。这对于通过 array bounds sanitizer
和 __builtin_dynamic_object_size()
内置函数进行运行时边界检查非常重要。在用户空间中,可以通过 -D_FORTIFY_SOURCE=3
启用这种额外的安全机制。因此,正确使用此属性可以增强 C 代码库中 flexible-array 成员的运行时边界检查。
以下是一个带有此属性的 flexible array 示例:
struct bounded_flex_struct {
...
size_t count;
struct foo flex_array[] __attribute__((__counted_by__(count)));
};
在这个示例中,count
是用于保存 flexible array 元素数量的结构成员,我们称之为计数器。
在 Linux 内核中,这一属性通过增强的 API(如 memcpy()
系列函数)实现边界检查,这些 API 内部调用了 __builtin_dynamic_object_size()
(CONFIG_FORTIFY_SOURCE)。此外,这一属性还通过数组边界检查器(CONFIG_UBSAN_BOUNDS)实现。
__counted_by() 宏
在内核中,我们将 counted_by
属性封装在 __counted_by()
宏中,代码如下:
#if __has_attribute(__counted_by__)
# define __counted_by(member) __attribute__((__counted_by__(member)))
#else
# define __counted_by(member)
#endif
- c8248faf3ca27 (“编译器属性:counted_by:调整名称…”)
通过这个宏,我们在过去的一年里在整个内核代码树中为 flexible-array 成员添加了注释。
diff --git a/drivers/net/ethernet/chelsio/cxgb4/sched.h b/drivers/net/ethernet/chelsio/cxgb4/sched.h
index 5f8b871d79afac..6b3c778815f09e 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/sched.h
+++ b/drivers/net/ethernet/chelsio/cxgb4/sched.h
@@ -82,7 +82,7 @@ struct sched_class {
struct sched_table { /* 每个端口调度表 */
u8 sched_size;
- struct sched_class tab[];
+ struct sched_class tab[] __counted_by(sched_size);
};
- ceba9725fb45 (“cxgb4: 为 struct sched_table 添加注释…”)
不过,并不是所有的 __counted_by()
注释都像上面的示例那样简单。
内核中的 __counted_by() 注释
要正确使用 counted_by
属性,有一些关键要求。首先,计数器必须在第一次引用 flexible-array 成员前初始化。其次,数组中的元素数量应当至少与计数器指示的数量相等。以下是一个满足这些要求的内核补丁示例:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index dac7eb77799bd1..68960ae9898713 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@-33,7 +33,7 @@ struct brcmf_fweh_queue_item {
u8 ifaddr[ETH_ALEN];
struct brcmf_event_msg_be emsg;
u32 datalen;
- u8 data[];
+ u8 data[] __counted_by(datalen);
};
/*
@@-418,17 +418,17 @@ void brcmf_fweh_process_event(struct brcmf_pub *drvr,
datalen + sizeof(*event_packet) > packet_len)
return;
- event = kzalloc(sizeof(*event) + datalen, gfp);
+ event = kzalloc(struct_size(event, data, datalen), gfp);
if (!event)
return;
+ event->datalen = datalen;
event->code = code;
event->ifidx = event_packet->msg.ifidx;
/* 使用 memcpy 以获取对齐的事件消息 */
memcpy(&event->emsg, &event_packet->msg, sizeof(event->emsg));
memcpy(event->data, data, datalen);
- event->datalen = datalen;
memcpy(event->ifaddr, event_packet->eth.h_dest, ETH_ALEN);
brcmf_fweh_queue_event(fweh, event);
- 62d19b358088 (“wifi: brcmfmac: fweh: 添加 __counted_by…“)
在上述补丁中,datalen
是 flexible-array 成员 data
的计数器。注意到将 event->datalen = datalen
的赋值操作移到调用 memcpy(event->data, data, datalen)
之前,确保在首次引用 flexible-array 前初始化计数器。否则,编译器会因试图写入大小为零的 flexible-array 而报错,因为 datalen
被之前的 kzalloc()
调用清零了。这种在调用 memcpy
后再进行赋值的模式在 Linux 内核中很常见,但在处理 counted_by
注释时应当改变。因此,我们需要小心处理这些注释,确保代码满足正确的要求。
在内核中,我们从错误中学习,并修复了一些最初我们做的错误注释。以下是一些错误修复示例:
- 6dc445c19050 (“clk: bcm: rpi: 在访问前赋值 –>num…”)
- 9368cdf90f52 (“clk: bcm: dvp: 在访问前赋值 –>num…”)
另一个常见的问题是计数器在循环内更新。请参阅以下补丁:
diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c
index 8993028709ecfb..e8f1d30a8d73c5 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@-892,10 +892,8 @@ static int wil_cfg80211_scan(struct wiphy *wiphy,
struct wil6210_priv *wil = wiphy_to_wil(wiphy);
struct wireless_dev *wdev = request->wdev;
struct wil6210_vif *vif = wdev_to_vif(wil, wdev);
- struct {
- struct wmi_start_scan_cmd cmd;
- u16 chnl[4];
- } __packed cmd;
+ DEFINE_FLEX(struct wmi_start_scan_cmd, cmd,
+ channel_list, num_channels, 4);
uint i, n;
int rc;
@@-977,9 +975,8 @@ static int wil_cfg80211_scan(struct wiphy *wiphy,
vif->scan_request = request;
mod_timer(&vif->scan_timer, jiffies + WIL6210_SCAN_TO);
- memset(&cmd, 0, sizeof(cmd));
- cmd.cmd.scan_type = WMI_ACTIVE_SCAN;
- cmd.cmd.num_channels = 0;
+ cmd->scan_type = WMI_ACTIVE_SCAN;
+ cmd->num_channels = 0;
n = min(request->n_channels, 4U);
for (i = 0; i < n; i++) {
int ch = request->channels[i]->hw_value;
@@-991,7 +988,8 @@ static int wil_cfg80211_scan(struct wiphy *wiphy,
continue;
}
/* 0-based channel indexes */
- cmd.cmd.channel_list[cmd.cmd.num_channels++].channel = ch - 1;
+ cmd->num_channels++;
+ cmd->channel_list[cmd->num_channels - 1].channel = ch - 1;
wil_dbg_misc(wil, "Scan for ch %d : %d MHz\n", ch,
request->channels[i]->center_freq);
}
...
--- a/drivers/net/wireless/ath/wil6210/wmi.h
+++ b/drivers/net/wireless/ath/wil6210/wmi.h
@@-474,7 +474,7 @@ struct wmi_start_scan_cmd {
struct {
u8 channel;
u8 reserved;
- } channel_list[];
+ } channel_list[] __counted_by(num_channels);
} __packed;
- 34c34c242a1b (“wifi: wil6210: cfg80211: 使用 __counted_by…”)
如补丁所示,问题在于当 num_channels
为零时,cmd.cmd.channel_list[cmd.cmd.num_channels++]
会导致未定义行为并可能触发编译器警告。解决方案是在访问数组之前增加 num_channels
,然后通过索引访问数组。
另一种选择是完全避免将计数器用作 flexible-array 的索引,可以通过引入辅助变量实现。以下是一个补丁片段:
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 38eb7ec86a1a65..21ebd70f3dcc97 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@-2143,7 +2143,7 @@ struct hci_cp_le_set_cig_params {
__le16 c_latency;
__le16 p_latency;
__u8 num_cis;
- struct hci_cis_params cis[];
+ struct hci_cis_params cis[] __counted_by(num_cis);
} __packed;
@@-1722,34 +1717,33 @@ static int hci_le_create_big(struct hci_conn *conn, struct bt_iso_qos *qos)
static int set_cig_params_sync(struct hci_dev *hdev, void *data)
{
...
+ u8 aux_num_cis = 0;
u8 cis_id;
...
for (cis_id = 0x00; cis_id < 0xf0 &&
- pdu.cp.num_cis < ARRAY_SIZE(pdu.cis); cis_id++) {
+ aux_num_cis < pdu->num_cis; cis_id++) {
struct hci_cis_params *cis;
conn = hci_conn_hash_lookup_cis(hdev, NULL, 0, cig_id, cis_id);
@@-1758,7 +1752,7 @@ static int set_cig_params_sync(struct hci_dev *hdev, void *data)
qos = &conn->iso_qos;
- cis = &pdu.cis[pdu.cp.num_cis++];
+ cis = &pdu->cis[aux_num_cis++];
cis->cis_id = cis_id;
cis->c_sdu = cpu_to_le16(conn->iso_qos.ucast.out.sdu);
cis->p_sdu = cpu_to_le16(conn->iso_qos.ucast.in.sdu);
@@-1769,14 +1763,14 @@ static int set_cig_params_sync(struct hci_dev *hdev, void *data)
cis->c_rtn = qos->ucast.out.rtn;
cis->p_rtn = qos->ucast.in.rtn;
}
+ pdu->num_cis = aux_num_cis;
...
- ea9e148c803b (“Bluetooth: hci_conn: 使用 __counted_by()…”)
在这个示例中,新的辅助变量 aux_num_cis
被引入以替代计数器 num_cis
进行数组访问:&pdu->cis[aux_num_cis++]
。计数器在循环之后更新:pdu->num_cis = aux_num_cis
。
两种解决方案都可以接受,根据需要选择适合的方式。
以下是最近一些错误修复,用以修正没有注意到上述细节的注释:
- PATCH “wifi: iwlwifi: mvm: 修复 _counted_by 用法在 cfg80211_wowlan_nd* 中的错误”