Merge Official Source

Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
This commit is contained in:
Tianling Shen
2026-07-22 11:10:30 +08:00
174 changed files with 7043 additions and 302 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-6.6 = .143
LINUX_KERNEL_HASH-6.6.143 = dace1f8dc9c0dbf5df14f47e3229cd62c298e83049681731ef229f2ba7592932
LINUX_VERSION-6.6 = .144
LINUX_KERNEL_HASH-6.6.144 = ec19b8307b495078cdc03749e63cb3802ac23cf6122c82eb9719feeb4557ecc3
+2 -2
View File
@@ -1,14 +1,14 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=wireless-regdb
PKG_VERSION:=2026.03.18
PKG_VERSION:=2026.05.30
PKG_RELEASE:=1
PKG_LICENSE:=ISC
PKG_LICENSE_FILES:=LICENSE
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=@KERNEL/software/network/wireless-regdb/
PKG_HASH:=5fc0000475d8c5368ccc5222827c16aef98b1eb6a69c9b5a3e7b7e98528945ac
PKG_HASH:=8a27bfc081bafed8c24dd70fab0d96f098e5a0bfcd08d3da672595f225ab8993
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
@@ -1,6 +1,6 @@
--- a/db.txt
+++ b/db.txt
@@ -460,9 +460,8 @@ country CL: DFS-JP
@@ -469,9 +469,8 @@ country CL: DFS-JP
# https://www.miit.gov.cn/cms_files/filemanager/1226211233/attach/20236/d1dc19424d5a4cfe90d631adeee8dd58.pdf
# Note: The transmit power for 5250-5350MHz bands can be raised by 3dBm when TPC is implemented
country CN: DFS-FCC
@@ -12,7 +12,7 @@
(5725 - 5850 @ 80), (33)
# 60 GHz band channels 1,4: 28dBm, channels 2,3: 44dBm
# ref: http://www.miit.gov.cn/n11293472/n11505629/n11506593/n11960250/n11960606/n11960700/n12330791.files/n12330790.pdf
@@ -2040,14 +2039,12 @@ country US: DFS-FCC
@@ -2081,14 +2080,12 @@ country US: DFS-FCC
(920 - 928 @ 8), (30)
(2400 - 2472 @ 40), (30)
# 5.15 ~ 5.25 GHz: 30 dBm for master mode, 23 dBm for clients
+4 -4
View File
@@ -10,15 +10,15 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=mac80211
PKG_VERSION:=6.12.61
PKG_VERSION:=6.12.96
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0-only
PKG_LICENSE_FILES:=COPYING
PKG_SOURCE_URL:=http://mirror2.openwrt.org/sources/
PKG_HASH:=9db2f836dba7f38ad68f8798720ad4360bce6a3557bde02b88b3a4f068c77118
PKG_SOURCE_URL:=https://github.com/openwrt/backports/releases/download/backports-v$(PKG_VERSION)
PKG_HASH:=816882e80cd6eb7f10d2851f634ca7964eb47a62f64eff4bf6f92ffdf8593650
PKG_SOURCE:=backports-$(PKG_VERSION).tar.xz
PKG_SOURCE:=backports-$(PKG_VERSION).tar.zst
PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(if $(BUILD_VARIANT),$(PKG_NAME)-$(BUILD_VARIANT)/)backports-$(PKG_VERSION)
PKG_BUILD_PARALLEL:=1
@@ -14,7 +14,7 @@ Signed-off-by: Sven Eckelmann <sven@open-mesh.com>
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -3577,6 +3577,16 @@ int ath10k_core_register(struct ath10k *
@@ -3576,6 +3576,16 @@ int ath10k_core_register(struct ath10k *
queue_work(ar->workqueue, &ar->register_work);
@@ -1,6 +1,6 @@
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -9932,6 +9932,21 @@ static int ath10k_mac_init_rd(struct ath
@@ -9950,6 +9950,21 @@ static int ath10k_mac_init_rd(struct ath
return 0;
}
@@ -22,7 +22,7 @@
int ath10k_mac_register(struct ath10k *ar)
{
static const u32 cipher_suites[] = {
@@ -10294,6 +10309,12 @@ int ath10k_mac_register(struct ath10k *a
@@ -10312,6 +10327,12 @@ int ath10k_mac_register(struct ath10k *a
ar->hw->weight_multiplier = ATH10K_AIRTIME_WEIGHT_MULTIPLIER;
@@ -40,7 +40,7 @@ Signed-off-by: Mathias Kresin <dev@kresin.me>
if (ret)
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -10310,7 +10310,7 @@ int ath10k_mac_register(struct ath10k *a
@@ -10328,7 +10328,7 @@ int ath10k_mac_register(struct ath10k *a
ar->hw->weight_multiplier = ATH10K_AIRTIME_WEIGHT_MULTIPLIER;
#ifdef CPTCFG_MAC80211_LEDS
@@ -28,8 +28,8 @@ Forwarded: no
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -1030,6 +1030,40 @@ static inline int ath10k_vdev_setup_sync
return ar->last_wmi_vdev_start_status;
@@ -1051,6 +1051,40 @@ static inline int ath10k_vdev_delete_syn
return 0;
}
+static u32 ath10k_get_max_antenna_gain(struct ath10k *ar,
@@ -69,7 +69,7 @@ Forwarded: no
static int ath10k_monitor_vdev_start(struct ath10k *ar, int vdev_id)
{
struct cfg80211_chan_def *chandef = NULL;
@@ -1062,7 +1096,8 @@ static int ath10k_monitor_vdev_start(str
@@ -1083,7 +1117,8 @@ static int ath10k_monitor_vdev_start(str
arg.channel.min_power = 0;
arg.channel.max_power = channel->max_power * 2;
arg.channel.max_reg_power = channel->max_reg_power * 2;
@@ -79,7 +79,7 @@ Forwarded: no
reinit_completion(&ar->vdev_setup_done);
reinit_completion(&ar->vdev_delete_done);
@@ -1508,7 +1543,8 @@ static int ath10k_vdev_start_restart(str
@@ -1529,7 +1564,8 @@ static int ath10k_vdev_start_restart(str
arg.channel.min_power = 0;
arg.channel.max_power = chandef->chan->max_power * 2;
arg.channel.max_reg_power = chandef->chan->max_reg_power * 2;
@@ -89,7 +89,7 @@ Forwarded: no
if (arvif->vdev_type == WMI_VDEV_TYPE_AP) {
arg.ssid = arvif->u.ap.ssid;
@@ -3439,7 +3475,8 @@ static int ath10k_update_channel_list(st
@@ -3460,7 +3496,8 @@ static int ath10k_update_channel_list(st
ch->min_power = 0;
ch->max_power = channel->max_power * 2;
ch->max_reg_power = channel->max_reg_power * 2;
@@ -18,7 +18,7 @@ Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -9,6 +9,7 @@
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/of.h>
@@ -26,7 +26,7 @@ Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
#include <linux/property.h>
#include <linux/dmi.h>
#include <linux/ctype.h>
@@ -3449,6 +3450,8 @@ static int ath10k_core_probe_fw(struct a
@@ -3448,6 +3449,8 @@ static int ath10k_core_probe_fw(struct a
device_get_mac_address(ar->dev, ar->mac_addr);
@@ -140,7 +140,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
int ath11k_mhi_register(struct ath11k_pci *ar_pci);
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -374,13 +374,20 @@ static void ath11k_pci_sw_reset(struct a
@@ -392,13 +392,20 @@ static void ath11k_pci_sw_reset(struct a
static void ath11k_pci_init_qmi_ce_config(struct ath11k_base *ab)
{
struct ath11k_qmi_ce_cfg *cfg = &ab->qmi.ce_cfg;
@@ -31,7 +31,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
{
.hw_rev = ATH11K_HW_IPQ8074,
.name = "ipq8074 hw2.0",
@@ -2239,7 +2239,8 @@ static void ath11k_core_reset(struct wor
@@ -2267,7 +2267,8 @@ static void ath11k_core_reset(struct wor
static int ath11k_init_hw_params(struct ath11k_base *ab)
{
const struct ath11k_hw_params *hw_params = NULL;
@@ -41,7 +41,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
for (i = 0; i < ARRAY_SIZE(ath11k_hw_params); i++) {
hw_params = &ath11k_hw_params[i];
@@ -2255,7 +2256,31 @@ static int ath11k_init_hw_params(struct
@@ -2283,7 +2284,31 @@ static int ath11k_init_hw_params(struct
ab->hw_params = *hw_params;
@@ -291,9 +291,9 @@ Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
int pdev_idx;
struct ath11k *ar;
enum wmi_vdev_type vdev_type;
@@ -929,24 +772,14 @@ int ath11k_reg_handle_chan_list(struct a
(char *)reg_info->alpha2, 2))
goto retfail;
@@ -932,24 +775,14 @@ int ath11k_reg_handle_chan_list(struct a
return 0;
}
- /* Intersect new rules with default regd if a new country setting was
- * requested, i.e a default regd was already set during initialization
@@ -13,7 +13,7 @@ Signed-off-by: Paweł Owoc <frut3k7@gmail.com>
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -2682,7 +2682,7 @@ int ath11k_wmi_send_wmm_update_cmd_tlv(s
@@ -2688,7 +2688,7 @@ int ath11k_wmi_send_wmm_update_cmd_tlv(s
FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
cmd->vdev_id = vdev_id;
@@ -18,7 +18,7 @@
goto end;
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -2009,7 +2009,7 @@ ath5k_beacon_send(struct ath5k_hw *ah)
@@ -2010,7 +2010,7 @@ ath5k_beacon_send(struct ath5k_hw *ah)
}
if ((ah->opmode == NL80211_IFTYPE_AP && ah->num_ap_vifs +
@@ -27,7 +27,7 @@
ah->opmode == NL80211_IFTYPE_MESH_POINT) {
u64 tsf = ath5k_hw_get_tsf64(ah);
u32 tsftu = TSF_TO_TU(tsf);
@@ -2095,7 +2095,7 @@ ath5k_beacon_update_timers(struct ath5k_
@@ -2096,7 +2096,7 @@ ath5k_beacon_update_timers(struct ath5k_
intval = ah->bintval & AR5K_BEACON_PERIOD;
if (ah->opmode == NL80211_IFTYPE_AP && ah->num_ap_vifs
@@ -36,7 +36,7 @@
intval /= ATH_BCBUF; /* staggered multi-bss beacons */
if (intval < 15)
ATH5K_WARN(ah, "intval %u is too low, min 15\n",
@@ -2561,6 +2561,7 @@ static const struct ieee80211_iface_limi
@@ -2562,6 +2562,7 @@ static const struct ieee80211_iface_limi
BIT(NL80211_IFTYPE_MESH_POINT) |
#endif
BIT(NL80211_IFTYPE_AP) },
@@ -1,6 +1,6 @@
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -6637,6 +6637,7 @@ static void hwsim_virtio_rx_done(struct
@@ -6636,6 +6636,7 @@ static void hwsim_virtio_rx_done(struct
static int init_vqs(struct virtio_device *vdev)
{
@@ -8,7 +8,7 @@
struct virtqueue_info vqs_info[HWSIM_NUM_VQS] = {
[HWSIM_VQ_TX] = { "tx", hwsim_virtio_tx_done },
[HWSIM_VQ_RX] = { "rx", hwsim_virtio_rx_done },
@@ -6644,6 +6645,19 @@ static int init_vqs(struct virtio_device
@@ -6643,6 +6644,19 @@ static int init_vqs(struct virtio_device
return virtio_find_vqs(vdev, HWSIM_NUM_VQS,
hwsim_vqs, vqs_info, NULL);
@@ -17,8 +17,8 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- a/backport-include/net/genetlink.h
+++ b/backport-include/net/genetlink.h
@@ -172,4 +172,15 @@ static inline int genlmsg_parse(const st
}
@@ -192,4 +192,15 @@ int backport_genlmsg_multicast_allns(con
#define genlmsg_multicast_allns LINUX_BACKPORT(genlmsg_multicast_allns)
#endif /* LINUX_VERSION_IS_LESS(5,2,0) */
+#if LINUX_VERSION_IN_RANGE(5,15,0,5,15,169) || \
@@ -284,7 +284,7 @@ Link: https://patch.msgid.link/dae7994f-3491-40de-b537-ebf68df084bb@gmail.com
.query_rx_desc = rtw8821c_query_rx_desc,
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -1980,13 +1980,13 @@ static const struct rtw_pwr_seq_cmd tran
@@ -1981,13 +1981,13 @@ static const struct rtw_pwr_seq_cmd tran
RTW_PWR_CMD_END, 0, 0},
};
@@ -300,7 +300,7 @@ Link: https://patch.msgid.link/dae7994f-3491-40de-b537-ebf68df084bb@gmail.com
trans_act_to_cardemu_8822b,
trans_cardemu_to_carddis_8822b,
NULL
@@ -2158,7 +2158,7 @@ static const struct rtw_rqpn rqpn_table_
@@ -2159,7 +2159,7 @@ static const struct rtw_rqpn rqpn_table_
RTW_DMA_MAPPING_EXTRA, RTW_DMA_MAPPING_HIGH},
};
@@ -309,7 +309,7 @@ Link: https://patch.msgid.link/dae7994f-3491-40de-b537-ebf68df084bb@gmail.com
.prio[RTW_DMA_MAPPING_EXTRA] = {
.rsvd = REG_FIFOPAGE_INFO_4, .avail = REG_FIFOPAGE_INFO_4 + 2,
},
@@ -2174,7 +2174,7 @@ static struct rtw_prioq_addrs prioq_addr
@@ -2175,7 +2175,7 @@ static struct rtw_prioq_addrs prioq_addr
.wsize = true,
};
@@ -318,7 +318,7 @@ Link: https://patch.msgid.link/dae7994f-3491-40de-b537-ebf68df084bb@gmail.com
.phy_set_param = rtw8822b_phy_set_param,
.read_efuse = rtw8822b_read_efuse,
.query_rx_desc = rtw8822b_query_rx_desc,
@@ -2523,7 +2523,7 @@ static const struct rtw_reg_domain coex_
@@ -2524,7 +2524,7 @@ static const struct rtw_reg_domain coex_
{0xc50, MASKBYTE0, RTW_REG_DOMAIN_MAC8},
};
@@ -304,7 +304,7 @@ Link: https://patch.msgid.link/913f1747-38fc-4409-85a4-57bb9cee506b@gmail.com
static void
rtw8822b_set_tx_power_index_by_rate(struct rtw_dev *rtwdev, u8 path,
u8 rs, u32 *phy_pwr_idx)
@@ -2177,7 +2136,7 @@ static const struct rtw_prioq_addrs prio
@@ -2178,7 +2137,7 @@ static const struct rtw_prioq_addrs prio
static const struct rtw_chip_ops rtw8822b_ops = {
.phy_set_param = rtw8822b_phy_set_param,
.read_efuse = rtw8822b_read_efuse,
@@ -535,7 +535,7 @@ Link: https://patch.msgid.link/913f1747-38fc-4409-85a4-57bb9cee506b@gmail.com
struct rtw_rx_pkt_stat *pkt_stat);
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -983,8 +983,7 @@ static void rtw_sdio_rxfifo_recv(struct
@@ -985,8 +985,7 @@ static void rtw_sdio_rxfifo_recv(struct
while (true) {
rx_desc = skb->data;
@@ -547,7 +547,7 @@ Link: https://patch.msgid.link/913f1747-38fc-4409-85a4-57bb9cee506b@gmail.com
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -625,8 +625,8 @@ static void rtw_usb_rx_handler(struct wo
@@ -634,8 +634,8 @@ static void rtw_usb_rx_handler(struct wo
do {
rx_desc = skb->data;
@@ -21,7 +21,7 @@ Link: https://patch.msgid.link/8becd851-8760-4480-8e8c-c4869ce72507@gmail.com
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1907,6 +1907,9 @@ static int rtw_dump_hw_feature(struct rt
@@ -1927,6 +1927,9 @@ static int rtw_dump_hw_feature(struct rt
u8 bw;
int i;
@@ -73,7 +73,7 @@ Link: https://patch.msgid.link/8becd851-8760-4480-8e8c-c4869ce72507@gmail.com
.lps_deep_mode_supported = BIT(LPS_DEEP_MODE_LCLK),
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -2511,6 +2511,7 @@ const struct rtw_chip_info rtw8822b_hw_s
@@ -2512,6 +2512,7 @@ const struct rtw_chip_info rtw8822b_hw_s
.page_size = TX_PAGE_SIZE,
.dig_min = 0x1c,
.usb_tx_agg_desc_num = 3,
@@ -155,7 +155,7 @@ Link: https://patch.msgid.link/c3e73c3a-fb2f-4013-9f06-d5274211e282@gmail.com
.lps_deep_mode_supported = BIT(LPS_DEEP_MODE_LCLK),
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -2512,6 +2512,7 @@ const struct rtw_chip_info rtw8822b_hw_s
@@ -2513,6 +2513,7 @@ const struct rtw_chip_info rtw8822b_hw_s
.dig_min = 0x1c,
.usb_tx_agg_desc_num = 3,
.hw_feature_report = true,
@@ -252,7 +252,7 @@ Link: https://patch.msgid.link/98ab839f-9100-44ae-9551-9af743a4aa3a@gmail.com
.query_phy_status = query_phy_status,
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -2134,6 +2134,8 @@ static const struct rtw_prioq_addrs prio
@@ -2135,6 +2135,8 @@ static const struct rtw_prioq_addrs prio
};
static const struct rtw_chip_ops rtw8822b_ops = {
@@ -101,7 +101,7 @@ Link: https://patch.msgid.link/2b3e3e6f-541b-4a3b-8ca3-65b267e6a95a@gmail.com
.lps_deep_mode_supported = BIT(LPS_DEEP_MODE_LCLK),
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -2515,6 +2515,7 @@ const struct rtw_chip_info rtw8822b_hw_s
@@ -2516,6 +2516,7 @@ const struct rtw_chip_info rtw8822b_hw_s
.usb_tx_agg_desc_num = 3,
.hw_feature_report = true,
.c2h_ra_report_size = 7,
@@ -121,7 +121,7 @@ Link: https://patch.msgid.link/2b3e3e6f-541b-4a3b-8ca3-65b267e6a95a@gmail.com
.ht_supported = true,
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -866,7 +866,7 @@ static void rtw_sdio_tx_skb_prepare(stru
@@ -868,7 +868,7 @@ static void rtw_sdio_tx_skb_prepare(stru
pkt_info->qsel = rtw_sdio_get_tx_qsel(rtwdev, skb, queue);
@@ -174,7 +174,7 @@ Link: https://patch.msgid.link/2b3e3e6f-541b-4a3b-8ca3-65b267e6a95a@gmail.com
void rtw_tx_rsvd_page_pkt_info_update(struct rtw_dev *rtwdev,
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -512,7 +512,7 @@ static int rtw_usb_write_data(struct rtw
@@ -519,7 +519,7 @@ static int rtw_usb_write_data(struct rtw
skb_put_data(skb, buf, size);
skb_push(skb, chip->tx_pkt_desc_sz);
memset(skb->data, 0, chip->tx_pkt_desc_sz);
@@ -183,7 +183,7 @@ Link: https://patch.msgid.link/2b3e3e6f-541b-4a3b-8ca3-65b267e6a95a@gmail.com
rtw_tx_fill_txdesc_checksum(rtwdev, pkt_info, skb->data);
ret = rtw_usb_write_port(rtwdev, qsel, skb,
@@ -579,7 +579,7 @@ static int rtw_usb_tx_write(struct rtw_d
@@ -588,7 +588,7 @@ static int rtw_usb_tx_write(struct rtw_d
pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz);
memset(pkt_desc, 0, chip->tx_pkt_desc_sz);
ep = qsel_to_ep(rtwusb, pkt_info->qsel);
@@ -199,7 +199,7 @@ Link: https://patch.msgid.link/904d0ab1-c046-40cd-a3a3-d4fdcf663c9d@gmail.com
.bfer_mu_max_num = 1,
--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c
@@ -2074,12 +2074,6 @@ static const struct rtw_intf_phy_para_ta
@@ -2075,12 +2075,6 @@ static const struct rtw_intf_phy_para_ta
.n_gen2_para = ARRAY_SIZE(pcie_gen2_param_8822b),
};
@@ -212,7 +212,7 @@ Link: https://patch.msgid.link/904d0ab1-c046-40cd-a3a3-d4fdcf663c9d@gmail.com
static const struct rtw_hw_reg rtw8822b_dig[] = {
[0] = { .addr = 0xc50, .mask = 0x7f },
[1] = { .addr = 0xe50, .mask = 0x7f },
@@ -2434,7 +2428,7 @@ static const u8 rtw8822b_pwrtrk_2g_cck_a
@@ -2435,7 +2429,7 @@ static const u8 rtw8822b_pwrtrk_2g_cck_a
10, 11, 11, 12, 12, 13, 13, 14, 14, 15
};
@@ -221,7 +221,7 @@ Link: https://patch.msgid.link/904d0ab1-c046-40cd-a3a3-d4fdcf663c9d@gmail.com
.pwrtrk_5gb_n[RTW_PWR_TRK_5G_1] = rtw8822b_pwrtrk_5gb_n[RTW_PWR_TRK_5G_1],
.pwrtrk_5gb_n[RTW_PWR_TRK_5G_2] = rtw8822b_pwrtrk_5gb_n[RTW_PWR_TRK_5G_2],
.pwrtrk_5gb_n[RTW_PWR_TRK_5G_3] = rtw8822b_pwrtrk_5gb_n[RTW_PWR_TRK_5G_3],
@@ -2457,6 +2451,12 @@ static const struct rtw_pwr_track_tbl rt
@@ -2458,6 +2452,12 @@ static const struct rtw_pwr_track_tbl rt
.pwrtrk_2g_ccka_p = rtw8822b_pwrtrk_2g_cck_a_p,
};
@@ -234,7 +234,7 @@ Link: https://patch.msgid.link/904d0ab1-c046-40cd-a3a3-d4fdcf663c9d@gmail.com
static const struct rtw_reg_domain coex_info_hw_regs_8822b[] = {
{0xcb0, MASKDWORD, RTW_REG_DOMAIN_MAC32},
{0xcb4, MASKDWORD, RTW_REG_DOMAIN_MAC32},
@@ -2537,7 +2537,6 @@ const struct rtw_chip_info rtw8822b_hw_s
@@ -2538,7 +2538,6 @@ const struct rtw_chip_info rtw8822b_hw_s
.rf_tbl = {&rtw8822b_rf_a_tbl, &rtw8822b_rf_b_tbl},
.rfe_defs = rtw8822b_rfe_defs,
.rfe_defs_size = ARRAY_SIZE(rtw8822b_rfe_defs),
@@ -17,7 +17,7 @@ Link: https://patch.msgid.link/e443f5d9-4b53-4f64-985c-64313ec80bef@gmail.com
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -532,6 +532,7 @@ static int rtw_usb_write_data_rsvd_page(
@@ -541,6 +541,7 @@ static int rtw_usb_write_data_rsvd_page(
pkt_info.tx_pkt_size = size;
pkt_info.qsel = TX_DESC_QSEL_BEACON;
pkt_info.offset = chip->tx_pkt_desc_sz;
@@ -12,7 +12,7 @@ Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -985,6 +985,32 @@ static void rtw_usb_intf_deinit(struct r
@@ -993,6 +993,32 @@ static void rtw_usb_intf_deinit(struct r
usb_set_intfdata(intf, NULL);
}
@@ -45,7 +45,7 @@ Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
static int rtw_usb_switch_mode_new(struct rtw_dev *rtwdev)
{
enum usb_device_speed cur_speed;
@@ -1038,7 +1064,8 @@ static int rtw_usb_switch_mode(struct rt
@@ -1046,7 +1072,8 @@ static int rtw_usb_switch_mode(struct rt
{
u8 id = rtwdev->chip->id;
@@ -55,7 +55,7 @@ Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
return 0;
if (!rtwdev->efuse.usb_mode_switch) {
@@ -1053,7 +1080,10 @@ static int rtw_usb_switch_mode(struct rt
@@ -1061,7 +1088,10 @@ static int rtw_usb_switch_mode(struct rt
return 0;
}
@@ -19,7 +19,7 @@ Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -843,6 +843,32 @@ static void rtw_usb_dynamic_rx_agg_v1(st
@@ -852,6 +852,32 @@ static void rtw_usb_dynamic_rx_agg_v1(st
rtw_write16(rtwdev, REG_RXDMA_AGG_PG_TH, val16);
}
@@ -52,7 +52,7 @@ Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
static void rtw_usb_dynamic_rx_agg(struct rtw_dev *rtwdev, bool enable)
{
switch (rtwdev->chip->id) {
@@ -851,6 +877,10 @@ static void rtw_usb_dynamic_rx_agg(struc
@@ -860,6 +886,10 @@ static void rtw_usb_dynamic_rx_agg(struc
case RTW_CHIP_TYPE_8821C:
rtw_usb_dynamic_rx_agg_v1(rtwdev, enable);
break;
@@ -9,7 +9,7 @@ Used for AP+STA support in OpenWrt - preserve AP mode keys across STA reconnect
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1663,12 +1663,6 @@ static int ieee80211_stop_ap(struct wiph
@@ -1653,12 +1653,6 @@ static int ieee80211_stop_ap(struct wiph
__sta_info_flush(sdata, true, link_id);
@@ -8,7 +8,7 @@ Subject: [PATCH] mac80211: allow scans in access point mode (for site survey)
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2877,6 +2877,8 @@ static int ieee80211_scan(struct wiphy *
@@ -2867,6 +2867,8 @@ static int ieee80211_scan(struct wiphy *
*/
fallthrough;
case NL80211_IFTYPE_AP:
@@ -16,7 +16,7 @@ and we should ignore this.
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -678,21 +678,6 @@ int wiphy_verify_iface_combinations(stru
@@ -674,21 +674,6 @@ int wiphy_verify_iface_combinations(stru
c->limits[j].max > 1))
return -EINVAL;
@@ -18,7 +18,7 @@ Signed-off-by: David Bauer <mail@david-bauer.net>
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2446,6 +2446,13 @@ static void sta_stats_decode_rate(struct
@@ -2447,6 +2447,13 @@ static void sta_stats_decode_rate(struct
sband = local->hw.wiphy->bands[band];
@@ -23,7 +23,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -4082,7 +4082,7 @@ struct ieee80211_txq *ieee80211_next_txq
@@ -4088,7 +4088,7 @@ struct ieee80211_txq *ieee80211_next_txq
if (deficit < 0)
sta->airtime[txqi->txq.ac].deficit +=
@@ -32,7 +32,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (deficit < 0 || !aql_check) {
list_move_tail(&txqi->schedule_order,
@@ -4227,7 +4227,8 @@ bool ieee80211_txq_may_transmit(struct i
@@ -4233,7 +4233,8 @@ bool ieee80211_txq_may_transmit(struct i
}
sta = container_of(iter->txq.sta, struct sta_info, sta);
if (ieee80211_sta_deficit(sta, ac) < 0)
@@ -42,7 +42,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
list_move_tail(&iter->schedule_order, &local->active_txqs[ac]);
}
@@ -4235,7 +4236,7 @@ bool ieee80211_txq_may_transmit(struct i
@@ -4241,7 +4242,7 @@ bool ieee80211_txq_may_transmit(struct i
if (sta->airtime[ac].deficit >= 0)
goto out;
@@ -95,7 +95,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
spin_lock_init(&local->active_txq_lock[i]);
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2360,13 +2360,28 @@ EXPORT_SYMBOL(ieee80211_sta_recalc_aggre
@@ -2361,13 +2361,28 @@ EXPORT_SYMBOL(ieee80211_sta_recalc_aggre
void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
struct sta_info *sta, u8 ac,
@@ -127,7 +127,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
atomic_add(tx_airtime,
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2554,7 +2554,7 @@ static u16 ieee80211_store_ack_skb(struc
@@ -2560,7 +2560,7 @@ static u16 ieee80211_store_ack_skb(struc
spin_lock_irqsave(&local->ack_status_lock, flags);
id = idr_alloc(&local->ack_status_frames, ack_skb,
@@ -136,7 +136,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
spin_unlock_irqrestore(&local->ack_status_lock, flags);
if (id >= 0) {
@@ -3983,20 +3983,20 @@ begin:
@@ -3989,20 +3989,20 @@ begin:
encap_out:
info->control.vif = vif;
@@ -167,7 +167,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
}
return skb;
@@ -4048,6 +4048,7 @@ struct ieee80211_txq *ieee80211_next_txq
@@ -4054,6 +4054,7 @@ struct ieee80211_txq *ieee80211_next_txq
struct ieee80211_txq *ret = NULL;
struct txq_info *txqi = NULL, *head = NULL;
bool found_eligible_txq = false;
@@ -175,7 +175,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
spin_lock_bh(&local->active_txq_lock[ac]);
@@ -4071,26 +4072,26 @@ struct ieee80211_txq *ieee80211_next_txq
@@ -4077,26 +4078,26 @@ struct ieee80211_txq *ieee80211_next_txq
if (!head)
head = txqi;
@@ -214,7 +214,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (txqi->schedule_round == local->schedule_round[ac])
goto out;
@@ -4157,7 +4158,8 @@ bool ieee80211_txq_airtime_check(struct
@@ -4163,7 +4164,8 @@ bool ieee80211_txq_airtime_check(struct
return true;
if (!txq->sta)
@@ -224,7 +224,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (unlikely(txq->tid == IEEE80211_NUM_TIDS))
return true;
@@ -4206,15 +4208,15 @@ bool ieee80211_txq_may_transmit(struct i
@@ -4212,15 +4214,15 @@ bool ieee80211_txq_may_transmit(struct i
spin_lock_bh(&local->active_txq_lock[ac]);
@@ -204,7 +204,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
nlmsg_free(msg);
@@ -9192,6 +9231,9 @@ static bool cfg80211_off_channel_oper_al
@@ -9195,6 +9234,9 @@ static bool cfg80211_off_channel_oper_al
lockdep_assert_wiphy(wdev->wiphy);
@@ -214,7 +214,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (!cfg80211_beaconing_iface_active(wdev))
return true;
@@ -9404,7 +9446,8 @@ static int nl80211_trigger_scan(struct s
@@ -9407,7 +9449,8 @@ static int nl80211_trigger_scan(struct s
}
/* ignore disabled channels */
@@ -224,7 +224,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
continue;
request->channels[i] = chan;
@@ -9424,7 +9467,8 @@ static int nl80211_trigger_scan(struct s
@@ -9427,7 +9470,8 @@ static int nl80211_trigger_scan(struct s
chan = &wiphy->bands[band]->channels[j];
@@ -246,7 +246,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
continue;
for (i = 0; i < rdev_req->n_channels; i++) {
@@ -3513,9 +3514,12 @@ int cfg80211_wext_siwscan(struct net_dev
@@ -3516,9 +3517,12 @@ int cfg80211_wext_siwscan(struct net_dev
continue;
for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
@@ -263,7 +263,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
/* If we have a wireless request structure and the
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -2970,3 +2970,32 @@ bool cfg80211_radio_chandef_valid(const
@@ -2951,3 +2951,32 @@ bool cfg80211_radio_chandef_valid(const
return true;
}
EXPORT_SYMBOL(cfg80211_radio_chandef_valid);
@@ -10,7 +10,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -1175,14 +1175,14 @@ int ieee80211_request_ibss_scan(struct i
@@ -1180,14 +1180,14 @@ int ieee80211_request_ibss_scan(struct i
unsigned int n_channels)
{
struct ieee80211_local *local = sdata->local;
@@ -27,7 +27,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
/* fill internal scan request */
if (!channels) {
@@ -1199,7 +1199,9 @@ int ieee80211_request_ibss_scan(struct i
@@ -1204,7 +1204,9 @@ int ieee80211_request_ibss_scan(struct i
&local->hw.wiphy->bands[band]->channels[i];
if (tmp_ch->flags & (IEEE80211_CHAN_NO_IR |
@@ -38,7 +38,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
continue;
local->int_scan_req->channels[n_ch] = tmp_ch;
@@ -1208,21 +1210,23 @@ int ieee80211_request_ibss_scan(struct i
@@ -1213,21 +1215,23 @@ int ieee80211_request_ibss_scan(struct i
}
if (WARN_ON_ONCE(n_ch == 0))
@@ -66,7 +66,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
local->int_scan_req->n_channels = n_ch;
}
@@ -1232,9 +1236,7 @@ int ieee80211_request_ibss_scan(struct i
@@ -1237,9 +1241,7 @@ int ieee80211_request_ibss_scan(struct i
memcpy(local->int_scan_req->ssids[0].ssid, ssid, IEEE80211_MAX_SSID_LEN);
local->int_scan_req->ssids[0].ssid_len = ssid_len;
@@ -9,7 +9,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1169,7 +1169,7 @@ ieee80211_replace_chanctx(struct ieee802
@@ -1171,7 +1171,7 @@ ieee80211_replace_chanctx(struct ieee802
static bool
ieee80211_find_available_radio(struct ieee80211_local *local,
const struct ieee80211_chan_req *chanreq,
@@ -18,7 +18,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
{
struct wiphy *wiphy = local->hw.wiphy;
const struct wiphy_radio *radio;
@@ -1180,6 +1180,9 @@ ieee80211_find_available_radio(struct ie
@@ -1182,6 +1182,9 @@ ieee80211_find_available_radio(struct ie
return true;
for (i = 0; i < wiphy->n_radio; i++) {
@@ -28,7 +28,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
radio = &wiphy->radio[i];
if (!cfg80211_radio_chandef_valid(radio, &chanreq->oper))
continue;
@@ -1213,7 +1216,9 @@ int ieee80211_link_reserve_chanctx(struc
@@ -1215,7 +1218,9 @@ int ieee80211_link_reserve_chanctx(struc
new_ctx = ieee80211_find_reservation_chanctx(local, chanreq, mode);
if (!new_ctx) {
if (ieee80211_can_create_new_chanctx(local, -1) &&
@@ -39,7 +39,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
new_ctx = ieee80211_new_chanctx(local, chanreq, mode,
false, radio_idx);
else
@@ -1884,7 +1889,9 @@ int _ieee80211_link_use_channel(struct i
@@ -1886,7 +1891,9 @@ int _ieee80211_link_use_channel(struct i
/* Note: context is now reserved */
if (ctx)
reserved = true;
@@ -88,7 +88,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
bool cfg80211_any_usable_channels(struct wiphy *wiphy,
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -518,6 +518,7 @@ static inline unsigned int elapsed_jiffi
@@ -519,6 +519,7 @@ static inline unsigned int elapsed_jiffi
}
int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev,
@@ -95,7 +95,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (ret)
return ret;
done:
@@ -3064,7 +3072,8 @@ static int ieee80211_set_tx_power(struct
@@ -3054,7 +3062,8 @@ static int ieee80211_set_tx_power(struct
if (wdev) {
sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
@@ -105,7 +105,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF))
return -EOPNOTSUPP;
@@ -3112,7 +3121,8 @@ static int ieee80211_set_tx_power(struct
@@ -3102,7 +3111,8 @@ static int ieee80211_set_tx_power(struct
}
list_for_each_entry(sdata, &local->interfaces, list) {
@@ -115,7 +115,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
has_monitor = true;
continue;
}
@@ -3122,7 +3132,8 @@ static int ieee80211_set_tx_power(struct
@@ -3112,7 +3122,8 @@ static int ieee80211_set_tx_power(struct
sdata->vif.bss_conf.txpower_type = txp_type;
}
list_for_each_entry(sdata, &local->interfaces, list) {
@@ -125,7 +125,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
continue;
ieee80211_recalc_txpower(sdata, update_txp_type);
}
@@ -4317,7 +4328,8 @@ static int ieee80211_cfg_get_channel(str
@@ -4307,7 +4318,8 @@ static int ieee80211_cfg_get_channel(str
if (chanctx_conf) {
*chandef = link->conf->chanreq.oper;
ret = 0;
@@ -156,7 +156,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
WARN_ON_ONCE(1);
@@ -956,6 +959,10 @@ void ieee80211_recalc_smps_chanctx(struc
@@ -958,6 +961,10 @@ void ieee80211_recalc_smps_chanctx(struc
if (!link->sdata->u.mgd.associated)
continue;
break;
@@ -167,7 +167,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
@@ -968,6 +975,11 @@ void ieee80211_recalc_smps_chanctx(struc
@@ -970,6 +977,11 @@ void ieee80211_recalc_smps_chanctx(struc
if (rcu_access_pointer(link->conf->chanctx_conf) != &chanctx->conf)
continue;
@@ -181,7 +181,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
WARN_ONCE(1, "Invalid SMPS mode %d\n",
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -465,6 +465,7 @@ static const char *hw_flag_names[] = {
@@ -461,6 +461,7 @@ static const char *hw_flag_names[] = {
FLAG(SUPPORTS_DYNAMIC_PS),
FLAG(MFP_CAPABLE),
FLAG(WANT_MONITOR_VIF),
@@ -217,7 +217,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
}
/* Regardless of eth_mac_addr() return we still want to add the
@@ -722,9 +727,11 @@ static void ieee80211_do_stop(struct iee
@@ -726,9 +731,11 @@ static void ieee80211_do_stop(struct iee
ieee80211_recalc_idle(local);
ieee80211_recalc_offload(local);
@@ -230,7 +230,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
fallthrough;
default:
if (!going_down)
@@ -1157,7 +1164,8 @@ int ieee80211_add_virtual_monitor(struct
@@ -1161,7 +1168,8 @@ int ieee80211_add_virtual_monitor(struct
ASSERT_RTNL();
lockdep_assert_wiphy(local->hw.wiphy);
@@ -240,7 +240,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
return 0;
sdata = kzalloc(sizeof(*sdata) + local->hw.vif_data_size, GFP_KERNEL);
@@ -1219,6 +1227,9 @@ void ieee80211_del_virtual_monitor(struc
@@ -1223,6 +1231,9 @@ void ieee80211_del_virtual_monitor(struc
{
struct ieee80211_sub_if_data *sdata;
@@ -250,7 +250,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
ASSERT_RTNL();
lockdep_assert_wiphy(local->hw.wiphy);
@@ -1362,7 +1373,8 @@ int ieee80211_do_open(struct wireless_de
@@ -1366,7 +1377,8 @@ int ieee80211_do_open(struct wireless_de
break;
}
@@ -284,7 +284,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
vif = &sdata->vif;
break;
}
@@ -3953,7 +3954,8 @@ begin:
@@ -3959,7 +3960,8 @@ begin:
switch (tx.sdata->vif.type) {
case NL80211_IFTYPE_MONITOR:
@@ -22,7 +22,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
fif_probe_req;
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1120,6 +1120,8 @@ void ieee80211_adjust_monitor_flags(stru
@@ -1124,6 +1124,8 @@ void ieee80211_adjust_monitor_flags(stru
ADJUST(CONTROL, control);
ADJUST(CONTROL, pspoll);
ADJUST(OTHER_BSS, other_bss);
@@ -52,7 +52,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
}
/* Regardless of eth_mac_addr() return we still want to add the
@@ -1353,6 +1348,8 @@ int ieee80211_do_open(struct wireless_de
@@ -1357,6 +1352,8 @@ int ieee80211_do_open(struct wireless_de
}
}
@@ -45,7 +45,7 @@ Signed-off-by: Johannes Berg <johannes.berg@intel.com>
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
}
@@ -7893,6 +7908,29 @@ void ieee80211_sta_work(struct ieee80211
@@ -7897,6 +7912,29 @@ void ieee80211_sta_work(struct ieee80211
}
}
@@ -45,7 +45,7 @@ Signed-off-by: Johannes Berg <johannes.berg@intel.com>
if (!ieee80211_sdata_running(sdata))
return;
@@ -7951,36 +7945,73 @@ static void ieee80211_sta_bcn_mon_timer(
@@ -7955,36 +7949,73 @@ static void ieee80211_sta_bcn_mon_timer(
&sdata->u.mgd.beacon_connection_loss_work);
}
+1 -1
View File
@@ -8,7 +8,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=odhcp6c
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=$(PROJECT_GIT)/project/odhcp6c.git
@@ -0,0 +1,37 @@
From 3d812c338eb93d897717627ebb1b7beb01646e6e Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:29:19 +0200
Subject: dhcpv6: fix out-of-bounds end pointer when parsing IA in Advertise
dhcpv6_handle_advert passes 'odata + olen + sizeof(*ia_hdr)' as the end
pointer to dhcpv6_parse_ia. odata points to the start of the IA
option's data (just past the 4-byte option header) and olen is the
data length, so the valid sub-option range ends at 'odata + olen'.
The extra '+ sizeof(*ia_hdr)' (16 bytes) extends the range 16 bytes
past the IA option boundary, allowing dhcpv6_parse_ia's sub-option
iterator to read into adjacent option bytes (or past the receive
buffer when the IA is the last option) and parse them as bogus IA
sub-options, polluting STATE_IA_NA / STATE_IA_PD with garbage.
The sibling call site in dhcpv6_handle_reply already passes the
correct 'odata + olen'. Match that here.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit b6f0c70f5fc26632d8a6c748efec5aa335aa4fde)
---
src/dhcpv6.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -959,7 +959,7 @@ static int dhcpv6_handle_advert(enum dhc
(otype == DHCPV6_OPT_IA_NA && na_mode != IA_MODE_NONE)) &&
olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
- dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
+ dhcpv6_parse_ia(ia_hdr, odata + olen);
}
if (otype == DHCPV6_OPT_SERVERID && olen <= 130) {
@@ -0,0 +1,49 @@
From ee3b760cb42935f4b6578c0481f75b793eadd0f7 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:31:09 +0200
Subject: script: avoid kill(0) when SIGCHLD races script_call
script_call() does
if (running) {
...
kill(running, SIGTERM);
}
where 'running' is a volatile pid_t cleared to 0 by the SIGCHLD
handler when the previous script child exits. The 'if (running)'
gate and the 'kill(running, ...)' that follows it are independent
reads of the volatile variable. If SIGCHLD fires for the previous
child between these two reads, the handler clears 'running' to 0
and kill() is called with pid == 0, which delivers SIGTERM to every
process in the same process group as odhcp6c, including odhcp6c
itself (and any peer/parent that shares the pgrp).
Snapshot the pid into a local before checking it and using it, so the
kill() target is the value we actually tested.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 2521bf732b9f643ce36c5ac3b314b17c034a0f50)
---
src/script.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
--- a/src/script.c
+++ b/src/script.c
@@ -402,10 +402,11 @@ void script_call(const char *status, int
time_t now = odhcp6c_get_milli_time() / 1000;
bool running_script = false;
- if (running) {
+ pid_t prev = running;
+ if (prev > 0) {
time_t diff = now - started;
- kill(running, SIGTERM);
+ kill(prev, SIGTERM);
if (diff > delay)
delay -= diff;
@@ -0,0 +1,52 @@
From 240ca48bb8f783177b86a21425c5b1b5bab2b7ae Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:15:36 +0200
Subject: odhcp6c: propagate allocation failure from insert_state
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
odhcp6c_insert_state always returned 0, even when odhcp6c_resize_state
failed (either because the state would exceed the 1024-byte cap or
because realloc() returned NULL). The callers act on the return value:
* config_add_requested_options() logs and aborts when nonzero, but
silently accepts the lost option when zero — so a failed insert
becomes "succeeded but not actually added".
* dhcpv6_add_server_cand() frees the candidate's IA_NA / IA_PD only
when nonzero, so an insert failure both loses the candidate and
leaks its IA buffers.
Return -1 when odhcp6c_resize_state() returns NULL, matching the
documented contract of the function and the behaviour of
odhcp6c_add_state().
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 49d9c0112bb8cd9760cc13495caa5009b61cf2ad)
---
src/odhcp6c.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -735,12 +735,13 @@ int odhcp6c_insert_state(enum odhcp6c_st
uint8_t *n = odhcp6c_resize_state(state, len);
- if (n) {
- uint8_t *sdata = state_data[state];
+ if (!n)
+ return -1;
- memmove(sdata + offset + len, sdata + offset, len_after);
- memcpy(sdata + offset, data, len);
- }
+ uint8_t *sdata = state_data[state];
+
+ memmove(sdata + offset + len, sdata + offset, len_after);
+ memcpy(sdata + offset, data, len);
return 0;
}
@@ -0,0 +1,36 @@
From 5495a8b3f20df00866946645b01869598d3ddd74 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:32:40 +0200
Subject: dhcpv6: ensure hostname buffer is NUL-terminated for dn_comp
POSIX permits gethostname() to leave the destination buffer
non-NUL-terminated when the system hostname is at least as long as the
buffer (the trailing NUL is dropped during truncation). Linux glibc
exhibits this behaviour. The buffer is passed directly to dn_comp(),
which scans for the terminator and would otherwise read past
fqdn_buf[].
Pass one fewer byte to gethostname() and explicitly NUL-terminate the
last byte so dn_comp() always sees a properly bounded C string.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 4bd976fd60c0e2baf00c92991d30507ece2b9075)
---
src/dhcpv6.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -341,7 +341,8 @@ static void dhcpv6_send(enum dhcpv6_msg
{
// Build FQDN
char fqdn_buf[256];
- gethostname(fqdn_buf, sizeof(fqdn_buf));
+ gethostname(fqdn_buf, sizeof(fqdn_buf) - 1);
+ fqdn_buf[sizeof(fqdn_buf) - 1] = '\0';
struct {
uint16_t type;
uint16_t len;
@@ -0,0 +1,121 @@
From 43f9c5e03c6bf89ef93834c8de57fe6985f88b64 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:19:39 +0200
Subject: script: handle allocation failures in env helpers
ipv6_to_env(), fqdn_to_env(), bin_to_env(), entry_to_env(),
search_to_env(), int_to_env() and the inline PASSTHRU=... assembler
all called realloc(NULL, ...) / malloc() without checking the result,
then immediately wrote into the (possibly NULL) buffer with memcpy(),
snprintf() or script_hexlify(). Under memory pressure this dereferences
NULL inside the forked script-helper child and crashes it before
putenv() / execv(), so the state script does not run for that
state-change.
Check each allocation and skip the corresponding environment variable
when it fails. While at it, replace the cosmetic realloc(NULL, ...) idiom
with plain malloc() to make intent clearer.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 01130f80338a250f94ae0df786306c11662c88fb)
---
src/script.c | 42 ++++++++++++++++++++++++++++++++----------
1 file changed, 32 insertions(+), 10 deletions(-)
--- a/src/script.c
+++ b/src/script.c
@@ -98,7 +98,10 @@ static void ipv6_to_env(const char *name
const struct in6_addr *addr, size_t cnt)
{
size_t buf_len = strlen(name);
- char *buf = realloc(NULL, cnt * INET6_ADDRSTRLEN + buf_len + 2);
+ char *buf = malloc(cnt * INET6_ADDRSTRLEN + buf_len + 2);
+
+ if (!buf)
+ return;
memcpy(buf, name, buf_len);
buf[buf_len++] = '=';
@@ -121,7 +124,10 @@ static void fqdn_to_env(const char *name
size_t buf_len = strlen(name);
size_t buf_size = len + buf_len + 2;
const uint8_t *fqdn_end = fqdn + len;
- char *buf = realloc(NULL, len + buf_len + 2);
+ char *buf = malloc(buf_size);
+
+ if (!buf)
+ return;
memcpy(buf, name, buf_len);
buf[buf_len++] = '=';
@@ -148,9 +154,12 @@ static void bin_to_env(uint8_t *opts, si
uint16_t otype, olen;
dhcpv6_for_each_option(opts, oend, otype, olen, odata) {
- char *buf = realloc(NULL, 14 + (olen * 2));
+ char *buf = malloc(14 + (olen * 2));
size_t buf_len = 0;
+ if (!buf)
+ continue;
+
snprintf(buf, 14, "OPTION_%hu=", otype);
buf_len += strlen(buf);
@@ -173,7 +182,10 @@ static void entry_to_env(const char *nam
// Worst case: ENTRY_PREFIX with iaid != 1 and exclusion
const size_t max_entry_len = (INET6_ADDRSTRLEN-1 + 5 + 22 + 15 + 10 +
INET6_ADDRSTRLEN-1 + 11 + 1);
- char *buf = realloc(NULL, buf_len + 2 + (len / sizeof(*e)) * max_entry_len);
+ char *buf = malloc(buf_len + 2 + (len / sizeof(*e)) * max_entry_len);
+
+ if (!buf)
+ return;
memcpy(buf, name, buf_len);
buf[buf_len++] = '=';
@@ -238,8 +250,13 @@ static void entry_to_env(const char *nam
static void search_to_env(const char *name, const uint8_t *start, size_t len)
{
size_t buf_len = strlen(name);
- char *buf = realloc(NULL, buf_len + 2 + len);
- char *c = mempcpy(buf, name, buf_len);
+ char *buf = malloc(buf_len + 2 + len);
+ char *c;
+
+ if (!buf)
+ return;
+
+ c = mempcpy(buf, name, buf_len);
*c++ = '=';
for (struct odhcp6c_entry *e = (struct odhcp6c_entry*)start;
@@ -262,7 +279,10 @@ static void search_to_env(const char *na
static void int_to_env(const char *name, int value)
{
size_t len = 13 + strlen(name);
- char *buf = realloc(NULL, len);
+ char *buf = malloc(len);
+
+ if (!buf)
+ return;
snprintf(buf, len, "%s=%d", name, value);
putenv(buf);
@@ -495,9 +515,11 @@ void script_call(const char *status, int
int_to_env("RA_RETRANSMIT", ra_get_retransmit());
char *buf = malloc(10 + passthru_len * 2);
- strncpy(buf, "PASSTHRU=", 10);
- script_hexlify(&buf[9], passthru, passthru_len);
- putenv(buf);
+ if (buf) {
+ strncpy(buf, "PASSTHRU=", 10);
+ script_hexlify(&buf[9], passthru, passthru_len);
+ putenv(buf);
+ }
execv(argv[0], argv);
_exit(128);
@@ -0,0 +1,52 @@
From 2b2aeabbb9550d9617878f32b6ed1ed2a38e33ad Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:20:53 +0200
Subject: odhcp6c: refuse to follow symlinks when writing pidfile
odhcp6c is generally run as root and writes its pidfile with
fopen(pidfile, "w"), which calls open(O_WRONLY | O_CREAT | O_TRUNC)
underneath. open() follows symlinks by default, so if an attacker (or
a broken init script) can place a symlink at the pidfile path before
odhcp6c starts, the symlink target gets truncated and overwritten with
a single line containing the daemon's PID. With /var/run sometimes
shared between services this gives a primitive for trashing arbitrary
root-owned files, plus the file is created with the symlink target's
ownership instead of root:root.
Use open() with O_NOFOLLOW | O_CLOEXEC explicitly and fdopen() the
result; the file is still truncated and replaced when it is a regular
file, but a symlink in the path causes open() to fail with ELOOP and
we simply skip pidfile creation rather than clobbering the target.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 0a19052dc9fb73530adeff9b61edb3a35f15bc2a)
---
src/odhcp6c.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -454,10 +454,17 @@ int main(_unused int argc, char* const a
pidfile = (char*)buf;
}
- FILE *fp = fopen(pidfile, "w");
- if (fp) {
- fprintf(fp, "%i\n", getpid());
- fclose(fp);
+ int pidfd = open(pidfile,
+ O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
+ 0644);
+ if (pidfd >= 0) {
+ FILE *fp = fdopen(pidfd, "w");
+ if (fp) {
+ fprintf(fp, "%i\n", getpid());
+ fclose(fp);
+ } else {
+ close(pidfd);
+ }
}
}
@@ -0,0 +1,54 @@
From 760b53c7400c2b60cf48723509fc123843ad07f4 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:21:37 +0200
Subject: odhcp6c: skip malformed /proc/net/if_inet6 entries
The validation loop in odhcp6c_addr_in_scope() walked addr_buf
checking each character with isxdigit() / !isupper(), but discarded
the result: 'i' was a local that went out of scope at the end of the
loop, and execution fell straight into the decode loop regardless. If
the kernel ever produced unexpected characters (or someone bind-mounts
a non-procfs over /proc/net/if_inet6), index(hex, c) would return NULL
and the subsequent 'NULL - hex' subtraction is undefined behaviour;
the address bytes were then derived from arbitrary pointer arithmetic.
Track the validation outcome explicitly, also reject inputs whose
length isn't 32 hex characters (one IPv6 address worth), and continue
to the next /proc line on any failure so the decode loop only sees
well-formed inputs.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit d6c2fbdc255c143afc4d2c670f602c9cc9308869)
---
src/odhcp6c.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -940,13 +940,20 @@ bool odhcp6c_addr_in_scope(const struct
(flags & (IFA_F_DADFAILED | IFA_F_TENTATIVE | IFA_F_DEPRECATED)))
continue;
- for (i = 0; i < strlen(addr_buf); i++) {
- if (!isxdigit(addr_buf[i]) || isupper(addr_buf[i]))
- break;
+ size_t addr_len = strlen(addr_buf);
+ bool valid = (addr_len == 2 * sizeof(inet6_addr.s6_addr));
+
+ for (i = 0; valid && i < addr_len; i++) {
+ if (!isxdigit((unsigned char)addr_buf[i]) ||
+ isupper((unsigned char)addr_buf[i]))
+ valid = false;
}
+ if (!valid)
+ continue;
+
memset(&inet6_addr, 0, sizeof(inet6_addr));
- for (i = 0; i < (strlen(addr_buf) / 2); i++) {
+ for (i = 0; i < (addr_len / 2); i++) {
unsigned char byte;
static const char hex[] = "0123456789abcdef";
byte = ((index(hex, addr_buf[i * 2]) - hex) << 4) |
@@ -0,0 +1,50 @@
From a04d72ac6c809cc9f189b635839ddbb9a48e44a3 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:34:04 +0200
Subject: script: handle fork() failure when launching state script
If fork() returns -1 (e.g. under EAGAIN / ENOMEM), neither the parent
nor the child branch runs. Control falls through to the bottom of
script_call() while 'running' still holds the previous (just-killed)
child's pid and 'started' still holds the previous start time. The
next call sees running != 0, fires kill() at a process that no longer
exists, and accumulates a misleading 'diff = now - started' against
the older start time. No diagnostic is logged either.
Log the failure, clear 'running' so subsequent calls do not chase a
ghost pid, and return without going through the (parent-only)
post-fork bookkeeping.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit c938c168cbef5642cab42389d96c340e40f4f8ff)
[use syslog() instead of the error() log helper, which is not present in 24.10]
---
src/script.c | 7 +++++++
1 file changed, 7 insertions(+)
--- a/src/script.c
+++ b/src/script.c
@@ -14,6 +14,7 @@
*/
#include <stdio.h>
+#include <errno.h>
#include <netdb.h>
#include <resolv.h>
#include <stdlib.h>
@@ -441,6 +442,12 @@ void script_call(const char *status, int
pid_t pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ERR, "Failed to fork script handler: %s", strerror(errno));
+ running = 0;
+ return;
+ }
+
if (pid > 0) {
running = pid;
started = now;
@@ -0,0 +1,49 @@
From 49f251805dd5bce1964d4d6549ff1178ae8df2a5 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 30 May 2026 23:17:47 +0200
Subject: odhcp6c: bound the address length when parsing the -P argument
The -P <[pfx/]len> handler copied everything before the '/' into the
fixed 134-byte stack buffer "buf" with
strncpy((char *)buf, optarg, optpos - optarg);
buf[optpos - optarg] = '\0';
without checking that the address portion fits. An argument whose
address part is 134 bytes or longer overflows buf on the stack (the
trailing NUL store is out of bounds as well).
Reject any address portion that does not fit in buf before copying.
inet_pton() would reject such an over-long string anyway, so no valid
invocation changes behaviour.
Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit e9a9e9d45f38bee5d761de0421ca52163efa28d7)
---
src/odhcp6c.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -245,8 +245,17 @@ int main(_unused int argc, char* const a
optpos = strchr(optarg, '/');
if (optpos) {
- strncpy((char *)buf, optarg, optpos - optarg);
- buf[optpos - optarg] = '\0';
+ size_t addr_len = optpos - optarg;
+
+ /* Leave room for the terminating NUL; reject anything
+ * that cannot be a valid IPv6 literal instead of
+ * overflowing buf. */
+ if (addr_len >= sizeof(buf)) {
+ syslog(LOG_ERR, "invalid argument: '%s'", optarg);
+ return 1;
+ }
+ strncpy((char *)buf, optarg, addr_len);
+ buf[addr_len] = '\0';
if (inet_pton(AF_INET6, (char *)buf, &prefix.addr) <= 0) {
syslog(LOG_ERR, "invalid argument: '%s'", optarg);
return 1;
@@ -0,0 +1,43 @@
From 7d71729e4da366d1c19c205487e4decf6d646dde Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:36:10 +0200
Subject: dhcpv6: require known SERVERID when validating Reconfigure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
dhcpv6_response_is_valid() previously set serverid_ok = true whenever
STATE_SERVER_ID was empty, on the assumption that an empty stored
server-id only happens for Solicit/Advertise where the client has not
yet bound to a server. The same code path is taken for Reconfigure
messages (req_msg_type == DHCPV6_MSG_UNKNOWN), so a Reconfigure
message that arrived while STATE_SERVER_ID was somehow empty (state
corruption, an early Reconfigure delivered before binding completes,
or a server-side bug) would be accepted without server identification.
RFC 8415 §18.2.11 requires the SERVERID in a Reconfigure to match the
server the client received the lease from. Only set serverid_ok = true
on an empty stored server-id when we are not validating a Reconfigure;
for DHCPV6_MSG_UNKNOWN we leave it false so the response is dropped.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 0a4e51db30d5a13175cdda7830a5fcb882e88659)
[the message-type parameter is named 'type' in 24.10, not 'req_msg_type']
---
src/dhcpv6.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -819,7 +819,7 @@ static bool dhcpv6_response_is_valid(con
if (server_id_len)
serverid_ok = (olen + 4U == server_id_len) && !memcmp(
&odata[-4], server_id, server_id_len);
- else
+ else if (type != DHCPV6_MSG_UNKNOWN)
serverid_ok = true;
} else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
sizeof(struct dhcpv6_auth_reconfigure)) {
@@ -0,0 +1,49 @@
From 1374cbad07c63db19bba0f371c1a2f36167381dd Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:46:55 +0200
Subject: odhcp6c: do not treat DHCPv6 option type 0 as end-of-list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
dhcpv6_for_each_option was structured as a chain of '&&' expressions
where each link both assigned a loop variable (otype, odata, olen)
and gated iteration on the assigned value being truthy. The middle
link, '((otype) = _o[0] << 8 | _o[1])', made iteration stop on
otype == 0.
DHCPv6 has no end-of-list sentinel; option code 0 is just reserved
(RFC 8415 §21, IANA registry). A server (broken or hostile) that emits
an option with code 0 anywhere in the response would silently truncate
the client's view of the option list, masking everything that follows
— including STATUS, IA_NA / IA_PD, AUTH, etc.
Switch the assignments inside the for-loop condition to use the comma
operator so all three (otype, odata, olen) are unconditionally
written, and gate iteration purely on the bounds check
'odata + olen <= end'. Callers see the same variables, in the same
order, just without the spurious type-0 truncation.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 9177f236c2d74144a8a2cbdc7da0fcf2f582bc2c)
---
src/odhcp6c.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
--- a/src/odhcp6c.h
+++ b/src/odhcp6c.h
@@ -267,8 +267,10 @@ struct dhcpv6_s46_rule {
#define dhcpv6_for_each_option(start, end, otype, olen, odata)\
for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (uint8_t*)(end) &&\
- ((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) &&\
- ((olen) = _o[2] << 8 | _o[3]) + (odata) <= (uint8_t*)(end); \
+ ((otype) = _o[0] << 8 | _o[1],\
+ (odata) = (void*)&_o[4],\
+ (olen) = _o[2] << 8 | _o[3],\
+ (odata) + (olen) <= (uint8_t*)(end)); \
_o += 4 + (_o[2] << 8 | _o[3]))
@@ -0,0 +1,91 @@
From 80c68194417ab73490fb99a8589d9c02d7fc5ede Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:38:11 +0200
Subject: dhcpv6: enforce monotonic replay counter on Reconfigure RKAP
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RFC 8415 §20.4.3 requires the client to track the replay-detection
field of an authenticated Reconfigure message and to reject any
incoming Reconfigure whose replay value does not exceed the highest
one already accepted. Without this check, a captured Reconfigure can
be replayed at will: the HMAC is still valid because the message
itself has not changed, and the client will happily re-trigger
Renew / Rebind / Information-Request cycles on demand.
Add a per-binding (reconf_replay, reconf_replay_seen) tuple,
initialised in dhcpv6_promote_server_cand() alongside reconf_key so a
fresh binding starts with a fresh window. Compare the wire replay
field (network byte order, decoded with be64toh()) against the stored
value before running the HMAC verification; only commit the value on
a verified HMAC so a forged message cannot move the counter forward.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcp6c/pull/160
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit df27a49c98d89cb945767763a47bfafe196165c8)
[24.10 has no per-candidate reconf_key and no auth_protocol selector: the replay
state is global alongside the existing global reconf_key, and is reset in
dhcpv6_promote_server_cand() when a new server binding is established]
---
src/dhcpv6.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -15,6 +15,7 @@
#include <time.h>
#include <fcntl.h>
+#include <endian.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
@@ -111,6 +112,8 @@ static struct in6_addr server_addr = IN6
// Reconfigure key
static uint8_t reconf_key[16];
+static uint64_t reconf_replay;
+static bool reconf_replay_seen;
// client options
static unsigned int client_options = 0;
@@ -827,6 +830,16 @@ static bool dhcpv6_response_is_valid(con
if (r->protocol != 3 || r->algorithm != 1 || r->reconf_type != 2)
continue;
+ /* RFC 8415 §20.4.3: the replay-detection field must be
+ * monotonically increasing per (client, server-id) pair.
+ * Drop any Reconfigure whose replay value does not exceed
+ * the highest one we have already accepted. */
+ uint64_t replay;
+ memcpy(&replay, &r->replay, sizeof(replay));
+ replay = be64toh(replay);
+ if (reconf_replay_seen && replay <= reconf_replay)
+ continue;
+
md5_ctx_t md5;
uint8_t serverhash[16], secretbytes[64];
uint32_t hash[4];
@@ -855,6 +868,10 @@ static bool dhcpv6_response_is_valid(con
md5_end(hash, &md5);
rcauth_ok = !memcmp(hash, serverhash, sizeof(hash));
+ if (rcauth_ok) {
+ reconf_replay = replay;
+ reconf_replay_seen = true;
+ }
} else if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
rcmsg = odata[0];
} else if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)) {
@@ -1680,6 +1697,8 @@ int dhcpv6_promote_server_cand(void)
odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
odhcp6c_add_state(STATE_SERVER_ID, cand->duid, cand->duid_len);
accept_reconfig = cand->wants_reconfigure;
+ reconf_replay = 0;
+ reconf_replay_seen = false;
if (cand->ia_na_len) {
odhcp6c_add_state(STATE_IA_NA, cand->ia_na, cand->ia_na_len);
@@ -0,0 +1,120 @@
From 35f1c268c8ffb2bfe2c7f4fa0415215ea532570b Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 23:47:42 +0200
Subject: odhcp6c: fix handling of RFC6603 Prefix Exclude Option
Several bugs in the encoding and (more importantly) decoding of
DHCPV6_OPT_PD_EXCLUDE lead to the option being ignored in some received
messages, or the excluded subnet id being mangled to an incorrect value.
Fix both encoding and decoding, being more explicit and slightly more
verbose for clarity.
Signed-off-by: Mark H. Spatz <mark.h.spatz@gmail.com>
Link: https://github.com/openwrt/odhcp6c/pull/151
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 07d324ee7222c0e15b9975281f18236fdccc11bd)
[24.10 has no dedicated exclusion_length field (dcb53c4 is not backported), so the
port keeps reusing entry.priority / e[].priority for the exclusion length. The
upstream 'slen > 2' -> 'slen >= 2' guard change is a no-op here: 24.10 already
skips on 'slen < 2'. The added info() PD_EXCLUDE trace is dropped, as the log
helpers from bfd7597 are not backported. The computed option length is unchanged
(excl_opt_len == old ex_len), so the ia_pd buffer sizing is unaffected.]
---
src/dhcpv6.c | 47 +++++++++++++++++++++++++++--------------------
1 file changed, 27 insertions(+), 20 deletions(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -437,13 +437,16 @@ static void dhcpv6_send(enum dhcpv6_msg
if (e[j].iaid != iaid)
continue;
- uint8_t ex_len = 0;
- if (e[j].priority > 0)
- ex_len = ((e[j].priority - e[j].length - 1) / 8) + 6;
+ uint8_t excl_subnet_id_nbits, excl_subnet_id_nbytes, excl_opt_len = 0;
+ if (e[j].priority > 0) {
+ excl_subnet_id_nbits = e[j].priority - e[j].length;
+ excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
+ excl_opt_len = excl_subnet_id_nbytes + 4 + 1;
+ }
struct dhcpv6_ia_prefix p = {
.type = htons(DHCPV6_OPT_IA_PREFIX),
- .len = htons(sizeof(p) - 4U + ex_len),
+ .len = htons(sizeof(p) - 4U + excl_opt_len),
.prefix = e[j].length,
.addr = e[j].target
};
@@ -456,20 +459,22 @@ static void dhcpv6_send(enum dhcpv6_msg
memcpy(ia_pd + ia_pd_len, &p, sizeof(p));
ia_pd_len += sizeof(p);
- if (ex_len) {
+ if (excl_opt_len) {
ia_pd[ia_pd_len++] = 0;
ia_pd[ia_pd_len++] = DHCPV6_OPT_PD_EXCLUDE;
ia_pd[ia_pd_len++] = 0;
- ia_pd[ia_pd_len++] = ex_len - 4;
+ ia_pd[ia_pd_len++] = excl_opt_len - 4;
ia_pd[ia_pd_len++] = e[j].priority;
- uint32_t excl = ntohl(e[j].router.s6_addr32[1]);
- excl >>= (64 - e[j].priority);
- excl <<= 8 - ((e[j].priority - e[j].length) % 8);
-
- for (size_t i = ex_len - 5; i > 0; --i, excl >>= 8)
- ia_pd[ia_pd_len + i] = excl & 0xff;
- ia_pd_len += ex_len - 5;
+ uint32_t excluded_bits = ntohl(e[j].router.s6_addr32[1]);
+ excluded_bits >>= (64 - e[j].priority); /* Right align subnet ID bits */
+ excluded_bits <<= (32 - excl_subnet_id_nbits); /* Left align subnet ID bits */
+
+ /* Copy subnet ID bits into the option MSB first */
+ for (size_t k = 0; k < excl_subnet_id_nbytes; ++k) {
+ ia_pd[ia_pd_len++] = excluded_bits >> 24;
+ excluded_bits <<= 8;
+ }
}
hdr->len = htons(ntohs(hdr->len) + ntohs(p.len) + 4U);
@@ -1408,7 +1413,9 @@ static unsigned int dhcpv6_parse_ia(void
if (stype != DHCPV6_OPT_PD_EXCLUDE || slen < 2)
continue;
+ /* RFC 6603 §4.2 Prefix Exclude option */
uint8_t elen = sdata[0];
+ uint8_t *excl_subnet_id = &sdata[1];
if (elen > 64)
elen = 64;
@@ -1417,19 +1424,19 @@ static unsigned int dhcpv6_parse_ia(void
continue;
}
- uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
- if (slen <= bytes) {
+ uint8_t excl_subnet_id_nbits = elen - entry.length;
+ uint8_t excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
+ if ((excl_subnet_id + excl_subnet_id_nbytes) > (sdata + slen)) {
ok = false;
continue;
}
uint32_t exclude = 0;
- do {
- exclude = exclude << 8 | sdata[bytes];
- } while (--bytes);
-
- exclude >>= 8 - ((elen - entry.length) % 8);
- exclude <<= 64 - elen;
+ /* Copy subnet ID bits out of the option MSB first */
+ for (size_t k = 0; k < excl_subnet_id_nbytes; k++)
+ exclude = (exclude << 8) | excl_subnet_id[k];
+ exclude >>= (8 * excl_subnet_id_nbytes) - excl_subnet_id_nbits; /* Right align subnet ID bits */
+ exclude <<= (64 - elen); /* Shift subnet ID bits into the low-order bits of the prefix */
// Abusing router & priority fields for exclusion
entry.router = entry.target;
+1 -1
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=dropbear
PKG_VERSION:=2024.86
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2
PKG_SOURCE_URL:= \
@@ -0,0 +1,25 @@
From 91f4832c0a90fc2c2236465e2937dfbfea03c86d Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Wed, 8 Apr 2026 20:51:47 +0800
Subject: [PATCH 1/6] Fix incorrect index closing listeners
This could read beyond socks[] and close an arbitrary descriptor.
The codepath runs when creating TCP listeners for forwarding (client or
server).
(cherry picked from commit 067fd3846c425d1998c7c94d3feafe1f26b514eb)
---
src/listener.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/src/listener.c
+++ b/src/listener.c
@@ -95,7 +95,7 @@ struct Listener* new_listener(const int
if (ses.listensize > MAX_LISTENERS) {
TRACE(("leave newlistener: too many already"))
for (j = 0; j < nsocks; j++) {
- close(socks[i]);
+ close(socks[j]);
}
return NULL;
}
@@ -0,0 +1,37 @@
From a89c4ce57a0e2d9c06c1ec08f1ac0ea1e0133c0c Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Sat, 2 May 2026 08:17:25 +0800
Subject: [PATCH 2/6] scp: clear setuid/setgid bits on received files
From openssh-portable 487e8ac146f7d6616f65c125d5edb210519b833a
Tracked as CVE-2026-35385
upstream: when downloading files as root in legacy (-O) mode and
without the -p (preserve modes) flag set, clear setuid/setgid bits from
downloaded files as one might expect.
AFAIK this bug dates back to the original Berkeley rcp program.
Reported by Christos Papakonstantinou of Cantina and Spearbit.
OpenBSD-Commit-ID: 49e902fca8dd933a92a9b547ab31f63e86729fa1
(cherry picked from commit 443ccc1fd1b9a4e3747f6b5cd644b7af49064308)
---
src/scp.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
--- a/src/scp.c
+++ b/src/scp.c
@@ -850,8 +850,10 @@ sink(int argc, char **argv, const char *
setimes = targisdir = 0;
mask = umask(0);
- if (!pflag)
+ if (!pflag) {
+ mask |= 07000;
(void) umask(mask);
+ }
if (argc != 1) {
run_err("ambiguous target");
exit(1);
@@ -0,0 +1,39 @@
From d05b4fc2f7130630b8715d2b7244f090a3e679a5 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Sat, 2 May 2026 08:43:26 +0800
Subject: [PATCH 3/6] scp: Disallow -r to an existing directory
Checks of received paths don't catch all cases in -r recursive mode, so
disallow -r when the destination directory exists. This mitigates
CVE-2019-6111, where unexpected files would be accepted from a malicious
scp server.
Alternatives like rsync could be used in that case.
The new error message may be misleading if parent path components of the
destination are also missing, though the command would have failed
regardless.
(cherry picked from commit 61bd0e3e6573277de17359763aa5827a189c8d7f)
---
src/scp.c | 9 +++++++++
1 file changed, 9 insertions(+)
--- a/src/scp.c
+++ b/src/scp.c
@@ -584,6 +584,15 @@ tolocal(int argc, char **argv)
if (*suser == '\0')
suser = NULL;
}
+
+ char *dest = *(argv + argc - 1);
+ if (!(access(dest, F_OK) == 0 && errno == ENOENT)) {
+ /* paths sent from the server in recursive mode aren't adequately
+ * checked that they match the requested files, so disallow this */
+ fprintf(stderr, "-r destination \"%s\" must not already exist\n", dest);
+ exit(1);
+ }
+
host = cleanhostname(host);
len = strlen(src) + CMDNEEDS + 20;
bp = xmalloc(len);
@@ -0,0 +1,360 @@
From 7f15dd66d850a0a785b31c5e6ca1947e431fe4f9 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Mon, 20 Apr 2026 20:57:11 +0800
Subject: [PATCH 4/6] scp: more CVE-2019-6111 fix
Cherrypicked from OpenSSH portable
commit 3d896c157c722bc47adca51a58dca859225b5874
djm Sun Feb 10 11:15:52 2019 +0000
upstream: when checking that filenames sent by the server side
match what the client requested, be prepared to handle shell-style brace
alternations, e.g. "{foo,bar}".
"looks good to me" millert@ + in snaps for the last week courtesy
deraadt@
(cherry picked from commit 143291baca6ebb3407a0c25c85bd53bf041a6ab7)
---
src/scp.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 269 insertions(+), 12 deletions(-)
--- a/src/scp.c
+++ b/src/scp.c
@@ -74,7 +74,6 @@
*/
#include "includes.h"
-/*RCSID("$OpenBSD: scp.c,v 1.130 2006/01/31 10:35:43 djm Exp $");*/
#include <fnmatch.h>
@@ -609,6 +608,253 @@ tolocal(int argc, char **argv)
}
}
+/* Appends a string to an array; returns 0 on success, -1 on alloc failure */
+static int
+append(char *cp, char ***ap, size_t *np)
+{
+ char **tmp;
+
+ if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL)
+ return -1;
+ tmp[(*np)] = cp;
+ (*np)++;
+ *ap = tmp;
+ return 0;
+}
+
+/*
+ * Finds the start and end of the first brace pair in the pattern.
+ * returns 0 on success or -1 for invalid patterns.
+ */
+static int
+find_brace(const char *pattern, int *startp, int *endp)
+{
+ int i;
+ int in_bracket, brace_level;
+
+ *startp = *endp = -1;
+ in_bracket = brace_level = 0;
+ for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) {
+ switch (pattern[i]) {
+ case '\\':
+ /* skip next character */
+ if (pattern[i + 1] != '\0')
+ i++;
+ break;
+ case '[':
+ in_bracket = 1;
+ break;
+ case ']':
+ in_bracket = 0;
+ break;
+ case '{':
+ if (in_bracket)
+ break;
+ if (pattern[i + 1] == '}') {
+ /* Protect a single {}, for find(1), like csh */
+ i++; /* skip */
+ break;
+ }
+ if (*startp == -1)
+ *startp = i;
+ brace_level++;
+ break;
+ case '}':
+ if (in_bracket)
+ break;
+ if (*startp < 0) {
+ /* Unbalanced brace */
+ return -1;
+ }
+ if (--brace_level <= 0)
+ *endp = i;
+ break;
+ }
+ }
+ /* unbalanced brackets/braces */
+ if (*endp < 0 && (*startp >= 0 || in_bracket))
+ return -1;
+ return 0;
+}
+
+/*
+ * Assembles and records a successfully-expanded pattern, returns -1 on
+ * alloc failure.
+ */
+static int
+emit_expansion(const char *pattern, int brace_start, int brace_end,
+ int sel_start, int sel_end, char ***patternsp, size_t *npatternsp)
+{
+ char *cp;
+ int o = 0, tail_len = strlen(pattern + brace_end + 1);
+
+ if ((cp = malloc(brace_start + (sel_end - sel_start) +
+ tail_len + 1)) == NULL)
+ return -1;
+
+ /* Pattern before initial brace */
+ if (brace_start > 0) {
+ memcpy(cp, pattern, brace_start);
+ o = brace_start;
+ }
+ /* Current braced selection */
+ if (sel_end - sel_start > 0) {
+ memcpy(cp + o, pattern + sel_start,
+ sel_end - sel_start);
+ o += sel_end - sel_start;
+ }
+ /* Remainder of pattern after closing brace */
+ if (tail_len > 0) {
+ memcpy(cp + o, pattern + brace_end + 1, tail_len);
+ o += tail_len;
+ }
+ cp[o] = '\0';
+ if (append(cp, patternsp, npatternsp) != 0) {
+ free(cp);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Expand the first encountered brace in pattern, appending the expanded
+ * patterns it yielded to the *patternsp array.
+ *
+ * Returns 0 on success or -1 on allocation failure.
+ *
+ * Signals whether expansion was performed via *expanded and whether
+ * pattern was invalid via *invalid.
+ */
+static int
+brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp,
+ int *expanded, int *invalid)
+{
+ int i;
+ int in_bracket, brace_start, brace_end, brace_level;
+ int sel_start, sel_end;
+
+ *invalid = *expanded = 0;
+
+ if (find_brace(pattern, &brace_start, &brace_end) != 0) {
+ *invalid = 1;
+ return 0;
+ } else if (brace_start == -1)
+ return 0;
+
+ in_bracket = brace_level = 0;
+ for (i = sel_start = brace_start + 1; i < brace_end; i++) {
+ switch (pattern[i]) {
+ case '{':
+ if (in_bracket)
+ break;
+ brace_level++;
+ break;
+ case '}':
+ if (in_bracket)
+ break;
+ brace_level--;
+ break;
+ case '[':
+ in_bracket = 1;
+ break;
+ case ']':
+ in_bracket = 0;
+ break;
+ case '\\':
+ if (i < brace_end - 1)
+ i++; /* skip */
+ break;
+ }
+ if (pattern[i] == ',' || i == brace_end - 1) {
+ if (in_bracket || brace_level > 0)
+ continue;
+ /* End of a selection, emit an expanded pattern */
+
+ /* Adjust end index for last selection */
+ sel_end = (i == brace_end - 1) ? brace_end : i;
+ if (emit_expansion(pattern, brace_start, brace_end,
+ sel_start, sel_end, patternsp, npatternsp) != 0)
+ return -1;
+ /* move on to the next selection */
+ sel_start = i + 1;
+ continue;
+ }
+ }
+ if (in_bracket || brace_level > 0) {
+ *invalid = 1;
+ return 0;
+ }
+ /* success */
+ *expanded = 1;
+ return 0;
+}
+
+/* Expand braces from pattern. Returns 0 on success, -1 on failure */
+static int
+brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp)
+{
+ char *cp, *cp2, **active = NULL, **done = NULL;
+ size_t i, nactive = 0, ndone = 0;
+ int ret = -1, invalid = 0, expanded = 0;
+
+ *patternsp = NULL;
+ *npatternsp = 0;
+
+ /* Start the worklist with the original pattern */
+ if ((cp = strdup(pattern)) == NULL)
+ return -1;
+ if (append(cp, &active, &nactive) != 0) {
+ free(cp);
+ return -1;
+ }
+ while (nactive > 0) {
+ cp = active[nactive - 1];
+ nactive--;
+ if (brace_expand_one(cp, &active, &nactive,
+ &expanded, &invalid) == -1) {
+ free(cp);
+ goto fail;
+ }
+ if (invalid)
+ fatal("%s: invalid brace pattern \"%s\"", __func__, cp);
+ if (expanded) {
+ /*
+ * Current entry expanded to new entries on the
+ * active list; discard the progenitor pattern.
+ */
+ free(cp);
+ continue;
+ }
+ /*
+ * Pattern did not expand; append the finename component to
+ * the completed list
+ */
+ if ((cp2 = strrchr(cp, '/')) != NULL)
+ *cp2++ = '\0';
+ else
+ cp2 = cp;
+ if (append(xstrdup(cp2), &done, &ndone) != 0) {
+ free(cp);
+ goto fail;
+ }
+ free(cp);
+ }
+ /* success */
+ *patternsp = done;
+ *npatternsp = ndone;
+ done = NULL;
+ ndone = 0;
+ ret = 0;
+ fail:
+ for (i = 0; i < nactive; i++)
+ free(active[i]);
+ free(active);
+ for (i = 0; i < ndone; i++)
+ free(done[i]);
+ free(done);
+ return ret;
+}
+
void
source(int argc, char **argv)
{
@@ -850,7 +1096,8 @@ sink(int argc, char **argv, const char *
off_t size, statbytes;
int setimes, targisdir, wrerrno = 0;
char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
- char *src_copy = NULL, *restrict_pattern = NULL;
+ char **patterns = NULL;
+ size_t n, npatterns = 0;
struct timeval tv[2];
#define atime tv[0]
@@ -879,16 +1126,13 @@ sink(int argc, char **argv, const char *
* Prepare to try to restrict incoming filenames to match
* the requested destination file glob.
*/
- if ((src_copy = strdup(src)) == NULL)
- fatal("strdup failed");
- if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) {
- *restrict_pattern++ = '\0';
- }
+ if (brace_expand(src, &patterns, &npatterns) != 0)
+ fatal("%s: could not expand pattern", __func__);
}
for (first = 1;; first = 0) {
cp = buf;
if (atomicio(read, remin, cp, 1) != 1)
- return;
+ goto done;
if (*cp++ == '\n')
SCREWUP("unexpected <newline>");
do {
@@ -911,7 +1155,7 @@ sink(int argc, char **argv, const char *
}
if (buf[0] == 'E') {
(void) atomicio(vwrite, remout, "", 1);
- return;
+ goto done;
}
if (ch == '\n')
*--cp = 0;
@@ -967,9 +1211,14 @@ sink(int argc, char **argv, const char *
run_err("error: unexpected filename: %s", cp);
exit(1);
}
- if (restrict_pattern != NULL &&
- fnmatch(restrict_pattern, cp, 0) != 0)
- SCREWUP("filename does not match request");
+ if (npatterns > 0) {
+ for (n = 0; n < npatterns; n++) {
+ if (fnmatch(patterns[n], cp, 0) == 0)
+ break;
+ }
+ if (n >= npatterns)
+ SCREWUP("filename does not match request");
+ }
if (targisdir) {
static char *namebuf = NULL;
static size_t cursize = 0;
@@ -1134,7 +1383,15 @@ bad: run_err("%s: %s", np, strerror(er
break;
}
}
+done:
+ for (n = 0; n < npatterns; n++)
+ free(patterns[n]);
+ free(patterns);
+ return;
screwup:
+ for (n = 0; n < npatterns; n++)
+ free(patterns[n]);
+ free(patterns);
run_err("protocol error: %s", why);
exit(1);
}
@@ -0,0 +1,38 @@
From ff819590e3d945320faae956343d0a57e3525a02 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Fri, 17 Apr 2026 21:24:08 +0800
Subject: [PATCH 5/6] server: open authorized_keys files nonblocking
This avoids getting stuck with special files.
(cherry picked from commit 8b579571b5584847caf72345b9073b3b2a0467a7)
---
src/svr-authpubkey.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
--- a/src/svr-authpubkey.c
+++ b/src/svr-authpubkey.c
@@ -462,6 +462,7 @@ static int checkpubkey(const char* keyal
if (checkpubkeyperms() == DROPBEAR_FAILURE) {
TRACE(("bad authorized_keys permissions, or file doesn't exist"))
} else {
+ int fd;
/* we don't need to check pw and pw_dir for validity, since
* its been done in checkpubkeyperms. */
len = strlen(ses.authstate.pw_dir);
@@ -471,7 +472,14 @@ static int checkpubkey(const char* keyal
snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
ses.authstate.pw_dir);
- authfile = fopen(filename, "r");
+ fd = open(filename, O_RDONLY | O_NONBLOCK);
+ if (fd >= 0) {
+ authfile = fdopen(fd, "r");
+ if (!authfile) {
+ /* fdopen could fail with ENOMEM */
+ m_close(fd);
+ }
+ }
if (!authfile) {
TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno)))
}
@@ -0,0 +1,531 @@
From d48f79c851110eb673d9738a69a15696ee972767 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Thu, 26 Mar 2026 23:03:46 +0800
Subject: [PATCH 6/6] Rework pubkey_options struct
Pubkey info is now included in the restriction struct. A temporary
pubkey_options is filled then set in the global session after auth.
(cherry picked from commit c60db6408178aaca0a8aacd017ac344a2cfb30c8)
---
fuzz/fuzzer-pubkey.c | 7 +---
src/auth.h | 28 ++++++++-----
src/fuzz.h | 2 +-
src/svr-authpubkey.c | 78 ++++++++++++++++++++----------------
src/svr-authpubkeyoptions.c | 80 +++++++++++++++++++------------------
src/svr-chansession.c | 5 ++-
src/svr-session.c | 4 +-
src/sysoptions.h | 4 ++
8 files changed, 114 insertions(+), 94 deletions(-)
--- a/fuzz/fuzzer-pubkey.c
+++ b/fuzz/fuzzer-pubkey.c
@@ -31,15 +31,10 @@ int LLVMFuzzerTestOneInput(const uint8_t
dropbear_exit("fuzzer imagined a bogus algorithm");
}
- int ret = fuzz_checkpubkey_line(line, 5, "/home/me/authorized_keys",
+ fuzz_checkpubkey_line(line, 5, "/home/me/authorized_keys",
algoname, algolen,
keyblob->data, keyblob->len);
- if (ret == DROPBEAR_SUCCESS) {
- /* fuzz_checkpubkey_line() should have cleaned up for failure */
- svr_pubkey_options_cleanup();
- }
-
buf_free(line);
buf_free(keyblob);
m_free(algoname);
--- a/src/auth.h
+++ b/src/auth.h
@@ -44,6 +44,7 @@ void svr_switch_user(void);
void svr_raise_gid_utmp(void);
void svr_restore_gid(void);
+struct PubKeyOptions;
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
int svr_pubkey_allows_agentfwd(void);
int svr_pubkey_allows_tcpfwd(void);
@@ -51,8 +52,9 @@ int svr_pubkey_allows_x11fwd(void);
int svr_pubkey_allows_pty(void);
int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port);
void svr_pubkey_set_forced_command(struct ChanSess *chansess);
-void svr_pubkey_options_cleanup(void);
-int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename);
+void svr_pubkey_options_cleanup(struct PubKeyOptions *pubkey_options);
+struct PubKeyOptions*
+svr_parse_pubkey_options(buffer *options_buf, int line_num, const char* filename);
#else
/* no option : success */
#define svr_pubkey_allows_agentfwd() 1
@@ -62,9 +64,14 @@ int svr_add_pubkey_options(buffer *optio
static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port)
{ (void)host; (void)port; return 1; }
-static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { }
-static inline void svr_pubkey_options_cleanup(void) { }
-#define svr_add_pubkey_options(x,y,z) DROPBEAR_SUCCESS
+static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) {
+ (void)chansess;
+}
+static inline void svr_pubkey_options_cleanup(struct PubKeyOptions *pubkey_options) {
+ (void)pubkey_options;
+}
+struct PubKeyOptions*
+svr_parse_pubkey_options(buffer *options_buf, int line_num, const char* filename);
#endif
/* Client functions */
@@ -132,14 +139,10 @@ struct AuthState {
char *pw_shell;
char *pw_name;
char *pw_passwd;
-#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
struct PubKeyOptions* pubkey_options;
- char *pubkey_info;
-#endif
};
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
-struct PubKeyOptions;
struct PubKeyOptions {
/* Flags */
int no_port_forwarding_flag;
@@ -155,6 +158,13 @@ struct PubKeyOptions {
int no_touch_required_flag;
int verify_required_flag;
#endif
+
+ /* String from the end of authorized_keys entry, for SSH_PUBKEYINFO
+ environment variable. May be omitted (NULL) if
+ options contain non-shell-safe characters.
+ This is not involved in pubkey option restrictions, but is
+ parsed and kept similarly so is stored here. */
+ char * info_env;
};
struct PermitTCPFwdEntry {
--- a/src/fuzz.h
+++ b/src/fuzz.h
@@ -30,7 +30,7 @@ const void* fuzz_get_algo(const algo_typ
// fuzzer functions that intrude into general code
void fuzz_kex_fakealgos(void);
-int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
+void fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
const char* algo, unsigned int algolen,
const unsigned char* keyblob, unsigned int keybloblen);
extern const char * const * fuzz_signkey_names;
--- a/src/svr-authpubkey.c
+++ b/src/svr-authpubkey.c
@@ -72,7 +72,8 @@
#define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
- const unsigned char* keyblob, unsigned int keybloblen);
+ const unsigned char* keyblob, unsigned int keybloblen,
+ struct PubKeyOptions **ret_options);
static int checkpubkeyperms(void);
static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
const unsigned char* keyblob, unsigned int keybloblen);
@@ -95,7 +96,8 @@ void svr_auth_pubkey(int valid_user) {
char* fp = NULL;
enum signature_type sigtype;
enum signkey_type keytype;
- int auth_failure = 1;
+ int auth_failure = 1;
+ struct PubKeyOptions *pubkey_options = NULL;
TRACE(("enter pubkeyauth"))
@@ -148,8 +150,8 @@ void svr_auth_pubkey(int valid_user) {
.pos = 0,
.size = 0
};
- int ret = svr_add_pubkey_options(&temp_buf, 0, "N/A");
- if (ret == DROPBEAR_FAILURE) {
+ pubkey_options = svr_parse_pubkey_options(&temp_buf, 0, "plugin");
+ if (pubkey_options == NULL) {
/* Fail immediately as the plugin provided wrong options */
send_msg_userauth_failure(0, 0);
goto out;
@@ -160,7 +162,8 @@ void svr_auth_pubkey(int valid_user) {
#endif
/* check if the key is valid */
if (auth_failure) {
- auth_failure = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen) == DROPBEAR_FAILURE;
+ int status = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen, &pubkey_options);
+ auth_failure = (status != DROPBEAR_SUCCESS);
}
if (auth_failure) {
@@ -185,13 +188,13 @@ void svr_auth_pubkey(int valid_user) {
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
key->sk_flags_mask = SSH_SK_USER_PRESENCE_REQD;
- if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->no_touch_required_flag) {
+ if (pubkey_options->no_touch_required_flag) {
key->sk_flags_mask &= ~SSH_SK_USER_PRESENCE_REQD;
}
- if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->verify_required_flag) {
+ if (pubkey_options->verify_required_flag) {
key->sk_flags_mask |= SSH_SK_USER_VERIFICATION_REQD;
}
-#endif
+#endif /* DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 */
/* create the data which has been signed - this a string containing
* session_id, concatenated with the payload packet up to the signature */
@@ -212,6 +215,10 @@ void svr_auth_pubkey(int valid_user) {
/* ... and finally verify the signature */
fp = sign_key_fingerprint(keyblob, keybloblen);
if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) {
+ if (ses.authstate.pubkey_options == NULL) {
+ ses.authstate.pubkey_options = pubkey_options;
+ pubkey_options = NULL;
+ }
if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PUBKEY)) {
/* successful pubkey authentication, but extra auth required */
dropbear_log(LOG_NOTICE,
@@ -256,9 +263,9 @@ out:
sign_key_free(key);
key = NULL;
}
- /* Retain pubkey options only if auth succeeded */
- if (!ses.authstate.authdone) {
- svr_pubkey_options_cleanup();
+ if (pubkey_options) {
+ svr_pubkey_options_cleanup(pubkey_options);
+ pubkey_options = NULL;
}
TRACE(("leave pubkeyauth"))
}
@@ -281,17 +288,22 @@ static void send_msg_userauth_pk_ok(cons
}
-/* Content for SSH_PUBKEYINFO is optionally returned malloced in ret_info (will be
- freed if already set */
+/* Key options are optionally returned in ret_options.
+ Should be passed with *ret_options = NULL, will only be populated
+ on success return. */
static int checkpubkey_line(buffer* line, int line_num, const char* filename,
const char* algo, unsigned int algolen,
const unsigned char* keyblob, unsigned int keybloblen,
- char ** ret_info) {
+ struct PubKeyOptions ** ret_options) {
buffer *options_buf = NULL;
char *info_str = NULL;
unsigned int pos, len, infopos, infolen;
int ret = DROPBEAR_FAILURE;
+ if (ret_options) {
+ *ret_options = NULL;
+ }
+
if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) {
TRACE(("checkpubkey_line: bad line length %d", line->len))
goto out;
@@ -393,7 +405,7 @@ static int checkpubkey_line(buffer* line
if (infolen > 0) {
info_str = m_malloc(infolen + 1);
buf_setpos(line, infopos);
- strncpy(info_str, buf_getptr(line, infolen), infolen);
+ strncpy(info_str, buf_getptr(line, infolen), infolen);
}
/* truncate to base64 data length */
@@ -405,20 +417,19 @@ static int checkpubkey_line(buffer* line
ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL);
/* free pubkey_info if it is filled */
- if (ret_info && *ret_info) {
- m_free(*ret_info);
- *ret_info = NULL;
- }
-
if (ret == DROPBEAR_SUCCESS) {
- if (options_buf) {
- ret = svr_add_pubkey_options(options_buf, line_num, filename);
- }
- if (ret_info) {
+#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
+ if (ret_options) {
+ *ret_options = svr_parse_pubkey_options(options_buf, line_num, filename);
+ if (*ret_options == NULL) {
+ ret = DROPBEAR_FAILURE;
+ goto out;
+ }
/* take the (optional) public key information */
- *ret_info = info_str;
+ (*ret_options)->info_env = info_str;
info_str = NULL;
}
+#endif
}
out:
@@ -436,7 +447,8 @@ out:
* acceptable key for authentication */
/* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
- const unsigned char* keyblob, unsigned int keybloblen) {
+ const unsigned char* keyblob, unsigned int keybloblen,
+ struct PubKeyOptions **ret_options) {
FILE * authfile = NULL;
char * filename = NULL;
@@ -509,7 +521,7 @@ static int checkpubkey(const char* keyal
line_num++;
ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen,
- keyblob, keybloblen, &ses.authstate.pubkey_info);
+ keyblob, keybloblen, ret_options);
if (ret == DROPBEAR_SUCCESS) {
break;
}
@@ -622,10 +634,12 @@ static int checkfileperm(char * filename
}
#if DROPBEAR_FUZZ
-int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
+void fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
const char* algo, unsigned int algolen,
const unsigned char* keyblob, unsigned int keybloblen) {
- return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, NULL);
+ struct PubKeyOptions *options = NULL;
+ checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, &options);
+ svr_pubkey_options_cleanup(options);
}
#endif
--- a/src/svr-authpubkeyoptions.c
+++ b/src/svr-authpubkeyoptions.c
@@ -132,25 +132,21 @@ void svr_pubkey_set_forced_command(struc
}
/* Free potential public key options */
-void svr_pubkey_options_cleanup() {
- if (ses.authstate.pubkey_options) {
- if (ses.authstate.pubkey_options->forced_command) {
- m_free(ses.authstate.pubkey_options->forced_command);
- }
- if (ses.authstate.pubkey_options->permit_open_destinations) {
- m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first;
+void svr_pubkey_options_cleanup(struct PubKeyOptions *pubkey_options) {
+ if (pubkey_options) {
+ m_free(pubkey_options->forced_command);
+ if (pubkey_options->permit_open_destinations) {
+ m_list_elem *iter = pubkey_options->permit_open_destinations->first;
while (iter) {
struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter);
m_free(entry->host);
m_free(entry);
- iter = ses.authstate.pubkey_options->permit_open_destinations->first;
+ iter = pubkey_options->permit_open_destinations->first;
}
- m_free(ses.authstate.pubkey_options->permit_open_destinations);
+ m_free(pubkey_options->permit_open_destinations);
}
- m_free(ses.authstate.pubkey_options);
- }
- if (ses.authstate.pubkey_info) {
- m_free(ses.authstate.pubkey_info);
+ m_free(pubkey_options->info_env);
+ m_free(pubkey_options);
}
}
@@ -168,58 +164,65 @@ static int match_option(buffer *options_
return DROPBEAR_FAILURE;
}
-/* Parse pubkey options and set ses.authstate.pubkey_options accordingly.
- * Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
-int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename) {
- int ret = DROPBEAR_FAILURE;
+/* Parse pubkey options and return a malloced struct.
+ * options_buf may be NULL, treated as empty.
+ * Returns NULL on failure */
+struct PubKeyOptions*
+svr_parse_pubkey_options(buffer *options_buf, int line_num, const char* filename) {
+ struct PubKeyOptions *pubkey_options = NULL;
TRACE(("enter addpubkeyoptions"))
- ses.authstate.pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions ));
+ pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions ));
+
+ if (!options_buf) {
+ TRACE(("leave addpubkeyoptions, null options"))
+ return pubkey_options;
+ }
buf_setpos(options_buf, 0);
while (options_buf->pos < options_buf->len) {
if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) {
dropbear_log(LOG_WARNING, "Port forwarding disabled.");
- ses.authstate.pubkey_options->no_port_forwarding_flag = 1;
+ pubkey_options->no_port_forwarding_flag = 1;
goto next_option;
}
if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) {
#if DROPBEAR_SVR_AGENTFWD
dropbear_log(LOG_WARNING, "Agent forwarding disabled.");
- ses.authstate.pubkey_options->no_agent_forwarding_flag = 1;
+ pubkey_options->no_agent_forwarding_flag = 1;
#endif
goto next_option;
}
if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) {
#if DROPBEAR_X11FWD
dropbear_log(LOG_WARNING, "X11 forwarding disabled.");
- ses.authstate.pubkey_options->no_x11_forwarding_flag = 1;
+ pubkey_options->no_x11_forwarding_flag = 1;
#endif
goto next_option;
}
if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) {
dropbear_log(LOG_WARNING, "Pty allocation disabled.");
- ses.authstate.pubkey_options->no_pty_flag = 1;
+ pubkey_options->no_pty_flag = 1;
goto next_option;
}
if (match_option(options_buf, "restrict") == DROPBEAR_SUCCESS) {
dropbear_log(LOG_WARNING, "Restrict option set");
- ses.authstate.pubkey_options->no_port_forwarding_flag = 1;
+ pubkey_options->no_port_forwarding_flag = 1;
#if DROPBEAR_SVR_AGENTFWD
- ses.authstate.pubkey_options->no_agent_forwarding_flag = 1;
+ pubkey_options->no_agent_forwarding_flag = 1;
#endif
#if DROPBEAR_X11FWD
- ses.authstate.pubkey_options->no_x11_forwarding_flag = 1;
+ pubkey_options->no_x11_forwarding_flag = 1;
#endif
- ses.authstate.pubkey_options->no_pty_flag = 1;
+ pubkey_options->no_pty_flag = 1;
goto next_option;
}
if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) {
int escaped = 0;
const unsigned char* command_start = buf_getptr(options_buf, 0);
- if (ses.authstate.pubkey_options->forced_command) {
+ if (pubkey_options->forced_command) {
/* multiple command= options */
goto bad_option;
}
@@ -228,10 +231,10 @@ int svr_add_pubkey_options(buffer *optio
const char c = buf_getbyte(options_buf);
if (!escaped && c == '"') {
const int command_len = buf_getptr(options_buf, 0) - command_start;
- ses.authstate.pubkey_options->forced_command = m_malloc(command_len);
- memcpy(ses.authstate.pubkey_options->forced_command,
+ pubkey_options->forced_command = m_malloc(command_len);
+ memcpy(pubkey_options->forced_command,
command_start, command_len-1);
- ses.authstate.pubkey_options->forced_command[command_len-1] = '\0';
+ pubkey_options->forced_command[command_len-1] = '\0';
goto next_option;
}
escaped = (!escaped && c == '\\');
@@ -244,8 +247,8 @@ int svr_add_pubkey_options(buffer *optio
int valid_option = 0;
const unsigned char* permitopen_start = buf_getptr(options_buf, 0);
- if (!ses.authstate.pubkey_options->permit_open_destinations) {
- ses.authstate.pubkey_options->permit_open_destinations = list_new();
+ if (!pubkey_options->permit_open_destinations) {
+ pubkey_options->permit_open_destinations = list_new();
}
while (options_buf->pos < options_buf->len) {
@@ -257,7 +260,7 @@ int svr_add_pubkey_options(buffer *optio
struct PermitTCPFwdEntry *entry =
(struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry));
- list_append(ses.authstate.pubkey_options->permit_open_destinations, entry);
+ list_append(pubkey_options->permit_open_destinations, entry);
spec = m_malloc(permitopen_len);
memcpy(spec, permitopen_start, permitopen_len - 1);
spec[permitopen_len - 1] = '\0';
@@ -291,14 +294,14 @@ int svr_add_pubkey_options(buffer *optio
if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) {
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key.");
- ses.authstate.pubkey_options->no_touch_required_flag = 1;
+ pubkey_options->no_touch_required_flag = 1;
#endif
goto next_option;
}
if (match_option(options_buf, "verify-required") == DROPBEAR_SUCCESS) {
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
dropbear_log(LOG_WARNING, "User verification required for U2F/FIDO key.");
- ses.authstate.pubkey_options->verify_required_flag = 1;
+ pubkey_options->verify_required_flag = 1;
#endif
goto next_option;
}
@@ -315,17 +318,16 @@ next_option:
/* Process the next option. */
}
/* parsed all options with no problem */
- ret = DROPBEAR_SUCCESS;
goto end;
bad_option:
- ret = DROPBEAR_FAILURE;
- svr_pubkey_options_cleanup();
+ svr_pubkey_options_cleanup(pubkey_options);
+ pubkey_options = NULL;
dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num);
end:
TRACE(("leave addpubkeyoptions"))
- return ret;
+ return pubkey_options;
}
#endif
--- a/src/svr-chansession.c
+++ b/src/svr-chansession.c
@@ -1026,8 +1026,9 @@ static void execchild(const void *user_d
addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
}
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
- if (ses.authstate.pubkey_info != NULL) {
- addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info);
+ if (ses.authstate.pubkey_options
+ && ses.authstate.pubkey_options->info_env) {
+ addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_options->info_env);
}
#endif
--- a/src/svr-session.c
+++ b/src/svr-session.c
@@ -84,8 +84,8 @@ static const struct ChanType *svr_chanty
static void
svr_session_cleanup(void) {
- /* free potential public key options */
- svr_pubkey_options_cleanup();
+ svr_pubkey_options_cleanup(ses.authstate.pubkey_options);
+ ses.authstate.pubkey_options = NULL;
m_free(svr_ses.addrstring);
m_free(svr_ses.remotehost);
--- a/src/sysoptions.h
+++ b/src/sysoptions.h
@@ -310,6 +310,10 @@
#error "You must define DROPBEAR_SVR_PUBKEY_AUTH in order to use plugins"
#endif
+#if (DROPBEAR_PLUGIN && !DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT)
+ #error "DROPBEAR_PLUGIN requires DROPBEAR_SVR_PUBKEY_OPTIONS"
+#endif
+
#if !(DROPBEAR_AES128 || DROPBEAR_3DES || DROPBEAR_AES256 || DROPBEAR_CHACHA20POLY1305)
#error "At least one encryption algorithm must be enabled. AES128 is recommended."
#endif
@@ -1,6 +1,6 @@
--- a/src/svr-authpubkey.c
+++ b/src/svr-authpubkey.c
@@ -78,6 +78,13 @@ static void send_msg_userauth_pk_ok(cons
@@ -79,6 +79,13 @@ static void send_msg_userauth_pk_ok(cons
const unsigned char* keyblob, unsigned int keybloblen);
static int checkfileperm(char * filename);
@@ -14,10 +14,10 @@
/* process a pubkey auth request, sending success or failure message as
* appropriate */
void svr_auth_pubkey(int valid_user) {
@@ -462,14 +469,21 @@ static int checkpubkey(const char* keyal
if (checkpubkeyperms() == DROPBEAR_FAILURE) {
@@ -475,14 +482,21 @@ static int checkpubkey(const char* keyal
TRACE(("bad authorized_keys permissions, or file doesn't exist"))
} else {
int fd;
- /* we don't need to check pw and pw_dir for validity, since
- * its been done in checkpubkeyperms. */
- len = strlen(ses.authstate.pw_dir);
@@ -42,9 +42,9 @@
+ user_authkeys_dir, authkeys_file);
+ }
authfile = fopen(filename, "r");
if (!authfile) {
@@ -543,27 +557,41 @@ static int checkpubkeyperms() {
fd = open(filename, O_RDONLY | O_NONBLOCK);
if (fd >= 0) {
@@ -563,27 +577,41 @@ static int checkpubkeyperms() {
goto out;
}
+1 -1
View File
@@ -8,7 +8,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ead
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_BUILD_DEPENDS:=libpcap
PKG_BUILD_DIR:=$(BUILD_DIR)/ead
+5 -2
View File
@@ -428,11 +428,14 @@ handle_send_a(struct ead_packet *pkt, int len, int *nstate)
{
struct ead_msg *msg = &pkt->msg;
struct ead_msg_number *number = EAD_DATA(msg, number);
len = ntohl(msg->len) - sizeof(struct ead_msg_number);
uint32_t msg_len = ntohl(msg->len);
if (len > MAXPARAMLEN + 1)
if (msg_len < sizeof(struct ead_msg_number) ||
msg_len - sizeof(struct ead_msg_number) > MAXPARAMLEN + 1)
return false;
len = msg_len - sizeof(struct ead_msg_number);
A.len = len;
A.data = abuf;
memcpy(A.data, number->data, len);
+1 -1
View File
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=hostapd
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_SOURCE_URL:=https://w1.fi/hostap.git
PKG_SOURCE_PROTO:=git
@@ -0,0 +1,43 @@
From 46dd5a4ffc9bcf44cf8fc45120b3e1e5ec922187 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
Date: Tue, 31 Mar 2026 23:24:04 +0300
Subject: AP MLD: Fix link ID validation in Basic MLE parsing
Link ID 15 can be indicated in the field, but that is not a valid value
and must be rejected to avoid issues pointing beyond the array of links
for a non-AP MLD. Without this, an invalid MLE could result in writing
beyond the end of the buffer and causing process termination or
unexpected behavior.
Fixes: 5f5db9366cde ("AP: MLO: Process Multi-Link element from (Re)Association Request frame")
Signed-off-by: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
---
src/ap/ieee802_11_eht.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -1290,6 +1290,7 @@ u16 hostapd_process_ml_assoc_req(struct
size_t sub_elem_len = *(pos + 1);
size_t sta_info_len;
u16 control;
+ u8 link_id;
wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu",
sub_elem_len);
@@ -1330,8 +1331,13 @@ u16 hostapd_process_ml_assoc_req(struct
goto out;
}
control = WPA_GET_LE16(pos);
- link_info = &info->links[control &
- EHT_PER_STA_CTRL_LINK_ID_MSK];
+ link_id = control & EHT_PER_STA_CTRL_LINK_ID_MSK;
+ if (link_id >= MAX_NUM_MLD_LINKS) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: Invalid Link ID in Per-STA Profile subelement");
+ goto out;
+ }
+ link_info = &info->links[link_id];
pos += 2;
ml_len -= 2;
sub_elem_len -= 2;
@@ -0,0 +1,38 @@
From aa9d345887389a251c63a3781d2ad2940d079193 Mon Sep 17 00:00:00 2001
From: Amarnath Hullur Subramanyam <amarnathhs@google.com>
Date: Thu, 30 Apr 2026 18:24:35 -0700
Subject: BSS: Add bounds check for link_id in Basic MLE parsing
In wpa_bss_parse_basic_ml_element() in bss.c, an extracted link_id is
used without validation against the maximum allowed links
(MAX_NUM_MLD_LINKS). Processing a malformed Basic Multi-Link element
(MLE) with an out-of-bounds link_id could lead to memory corruption.
However, the modified location is within the body of the received frame
and as such, this does not result in additional issues since that area
is controlled by the transmitter of the frame. In any case, it is better
to be explicit with validating the Link ID value.
This commit introduces a strict bounds check immediately after link_id
extraction. If link_id exceeds or equals MAX_NUM_MLD_LINKS, parsing is
gracefully aborted with a debug log entry.
Fixes: de5e01010cb2 ("wpa_supplicant: Support ML probe request")
Signed-off-by: Amarnath Hullur Subramanyam <amarnathhs@google.com>
---
wpa_supplicant/bss.c | 5 +++++
1 file changed, 5 insertions(+)
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1773,6 +1773,11 @@ int wpa_bss_parse_basic_ml_element(struc
ETH_ALEN);
link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
+ if (link_id >= MAX_NUM_MLD_LINKS) {
+ wpa_printf(MSG_DEBUG, "MLD: Invalid link ID %u in Basic MLE",
+ link_id);
+ goto out;
+ }
bss->mld_link_id = link_id;
seen = bss->valid_links = BIT(link_id);
@@ -0,0 +1,45 @@
From a8531e3d871e6fa72f2f85d91e9f787326b2af8b Mon Sep 17 00:00:00 2001
From: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
Date: Tue, 31 Mar 2026 23:16:08 +0300
Subject: MLD: Validate MLE Link ID fields in association rejection case
The Link ID Info field in the Common Info field needs to ignore the
reserved bits to be more extensible for future. Both that link ID for
the association link and the link IDs for other links need to be
verified to be within the valid range (0-14), so check that here. The
parsed link ID was not used for anything yet, but it is better to make
sure this in theory common parser is not exposing invalid data to the
caller should it be used for additional purposes in the future.
Signed-off-by: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
---
wpa_supplicant/events.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -3972,7 +3972,12 @@ static unsigned int wpas_ml_parse_assoc(
pos = common_info->variable;
/* Store the information for the association link */
- ml_info[i].link_id = *pos;
+ ml_info[i].link_id = *pos & EHT_ML_LINK_ID_MSK;
+ if (ml_info[i].link_id >= MAX_NUM_MLD_LINKS) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: Invalid Link ID value for assoc link");
+ goto out;
+ }
pos++;
/* Skip the BSS Parameters Change Count */
@@ -4107,6 +4112,10 @@ static unsigned int wpas_ml_parse_assoc(
MAC2STR(pos + 1), nstr_bitmap_len);
ml_info[i].link_id = ctrl & EHT_PER_STA_CTRL_LINK_ID_MSK;
+ if (ml_info[i].link_id >= MAX_NUM_MLD_LINKS) {
+ wpa_printf(MSG_DEBUG, "MLD: Invalid Link ID value");
+ goto out;
+ }
os_memcpy(ml_info[i].bssid, pos + 1, ETH_ALEN);
pos += sta_info_len;
@@ -0,0 +1,37 @@
From ce1a8612e309fe86133ecf05ffb452b0bdf3b035 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
Date: Mon, 18 May 2026 15:45:15 +0300
Subject: AP MLD: Verify AP MLD link ID validity before updating bitmap of
links
Link ID is 0..14, so ignore value 15 if an invalid frame is processed.
It does not look like the invalid value was actually used to reference
any local array, but in any case, it is better to not mark an invalid
link as being specified.
Signed-off-by: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
---
src/ap/beacon.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -1366,6 +1366,7 @@ static bool parse_ml_probe_req(const str
for_each_element_id(sub, 0, pos, len) {
const struct ieee80211_eht_per_sta_profile *sta;
u16 sta_control;
+ u8 link_id;
if (*links == 0xffff)
*links = 0;
@@ -1385,7 +1386,9 @@ static bool parse_ml_probe_req(const str
* partial profile was requested.
*/
sta_control = le_to_host16(sta->sta_control);
- *links |= BIT(sta_control & EHT_PER_STA_CTRL_LINK_ID_MSK);
+ link_id = sta_control & EHT_PER_STA_CTRL_LINK_ID_MSK;
+ if (link_id < MAX_NUM_MLD_LINKS)
+ *links |= BIT(link_id);
}
if (!for_each_element_completed(sub, pos, len)) {
@@ -0,0 +1,37 @@
From 41c86a2ebed50567c73de23c102c2bf83eb883f2 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
Date: Tue, 31 Mar 2026 17:47:03 +0300
Subject: MLD: Fix length check in common info for association failure cases
It is not sufficient to check that the indicated common info length is
sufficiently large to contain the information; there needs to be a check
for the indicated value to not be too large to go beyond the end of the
MLE as well. Without this, invalid MLE might result in ml_len wrapping
around to a huge value and reading beyond the end of the buffer for the
received frame. This could result in process termination.
Add the missed check for the Common Info field not being truncated in
the MLE in association failure cases.
Fixes: a58a0c592e20 ("MLD: Fix Multi-Link element parsing for association failures")
Signed-off-by: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
---
wpa_supplicant/events.c | 7 +++++++
1 file changed, 7 insertions(+)
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -3960,6 +3960,13 @@ static unsigned int wpas_ml_parse_assoc(
goto out;
}
+ if (sizeof(*ml) + common_info->len > ml_len) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: Truncated common info (common_info->len=%u ml_len=%zu)",
+ common_info->len, ml_len);
+ goto out;
+ }
+
wpa_printf(MSG_DEBUG, "MLD: address: " MACSTR,
MAC2STR(common_info->mld_addr));
@@ -278,7 +278,7 @@ This allows building both hostapd and wpa_supplicant as a single binary
os_memset(&eapol_test, 0, sizeof(eapol_test));
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -6131,8 +6131,8 @@ static int wpas_pasn_auth(struct wpa_sup
@@ -6147,8 +6147,8 @@ static int wpas_pasn_auth(struct wpa_sup
#endif /* CONFIG_PASN */
@@ -289,7 +289,7 @@ This allows building both hostapd and wpa_supplicant as a single binary
{
struct wpa_supplicant *wpa_s = ctx;
int resched;
@@ -7084,7 +7084,7 @@ void wpa_supplicant_event(void *ctx, enu
@@ -7100,7 +7100,7 @@ void wpa_supplicant_event(void *ctx, enu
}
@@ -53,7 +53,7 @@ probe/assoc/auth requests via object subscribe.
}
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -1418,6 +1418,12 @@ void handle_probe_req(struct hostapd_dat
@@ -1421,6 +1421,12 @@ void handle_probe_req(struct hostapd_dat
int mld_id;
u16 links;
#endif /* CONFIG_IEEE80211BE */
@@ -66,7 +66,7 @@ probe/assoc/auth requests via object subscribe.
if (hapd->iconf->rssi_ignore_probe_request && ssi_signal &&
ssi_signal < hapd->iconf->rssi_ignore_probe_request)
@@ -1604,6 +1610,12 @@ void handle_probe_req(struct hostapd_dat
@@ -1607,6 +1613,12 @@ void handle_probe_req(struct hostapd_dat
}
#endif /* CONFIG_P2P */
@@ -622,7 +622,7 @@ as adding/removing interfaces.
CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -6167,6 +6167,7 @@ void supplicant_event(void *ctx, enum wp
@@ -6183,6 +6183,7 @@ void supplicant_event(void *ctx, enum wp
event_to_string(event), event);
#endif /* CONFIG_NO_STDOUT_DEBUG */
@@ -43,7 +43,7 @@ as adding/removing interfaces.
"error: %s", name, strerror(errno));
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -406,7 +406,11 @@ hostapd_interface_init(struct hapd_inter
@@ -365,7 +365,11 @@ hostapd_interface_init(struct hapd_inter
struct hostapd_iface *iface;
int k;
@@ -58,7 +58,7 @@ as adding/removing interfaces.
return NULL;
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -2893,7 +2893,12 @@ hostapd_interface_init_bss(struct hapd_i
@@ -3354,8 +3354,13 @@ hostapd_interface_init_bss(struct hapd_i
}
}
@@ -71,5 +71,6 @@ as adding/removing interfaces.
+ wpa_printf(MSG_INFO, "Configuration file: %s (phy %s)%s",
+ config_fname, phy, iface ? "" : " --> new PHY");
+ }
if (iface) {
struct hostapd_config *conf;
if (iface) {
struct hostapd_config *conf;
struct hostapd_bss_config **tmp_conf;
@@ -36,7 +36,7 @@ full device, e.g. in order to deal with hardware/driver limitations
* ' ' (ascii 32): all environments
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -1634,7 +1634,7 @@ void handle_probe_req(struct hostapd_dat
@@ -1637,7 +1637,7 @@ void handle_probe_req(struct hostapd_dat
if (hapd->conf->no_probe_resp_if_max_sta &&
is_multicast_ether_addr(mgmt->da) &&
is_multicast_ether_addr(mgmt->bssid) &&
@@ -28,7 +28,7 @@ Signed-off-by: David Bauer <mail@david-bauer.net>
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -1477,7 +1477,7 @@ void handle_probe_req(struct hostapd_dat
@@ -1480,7 +1480,7 @@ void handle_probe_req(struct hostapd_dat
* is less likely to see them (Probe Request frame sent on a
* neighboring, but partially overlapping, channel).
*/
@@ -29,7 +29,7 @@ handle reload.
#ifndef CONFIG_NO_HOSTAPD_LOGGER
static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module,
@@ -793,6 +794,11 @@ int main(int argc, char *argv[])
@@ -797,6 +798,11 @@ int main(int argc, char *argv[])
if (os_program_init())
return -1;
@@ -18,7 +18,7 @@ Signed-off-by: Stone Zhang <quic_stonez@quicinc.com>
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -4813,6 +4813,7 @@ static void hostapd_switch_color_timeout
@@ -4818,6 +4818,7 @@ static void hostapd_switch_color_timeout
struct cca_settings settings;
int ret;
+1 -1
View File
@@ -8,7 +8,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=odhcpd
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=$(PROJECT_GIT)/project/odhcpd.git
@@ -1,7 +1,7 @@
From f0d855358b86a36efbfeb5a9de5a1d2a4d9d80fe Mon Sep 17 00:00:00 2001
From 95a69c4cafc3a064bf4e0e0c9d06cea3411a4ba4 Mon Sep 17 00:00:00 2001
From: Haoyi Ci <cihaoyi@outlook.com>
Date: Thu, 2 Oct 2025 16:14:05 +0800
Subject: [PATCH] ndp: Allow NS loopback for master iface
Subject: ndp: Allow NS loopback for master iface
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@@ -1,7 +1,7 @@
From 5eac9c56ff3b0a013c5241f449ca144f70bf4c02 Mon Sep 17 00:00:00 2001
From b655d759c4bc87ae729c86fe85617f48d74c9e40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
Date: Tue, 7 Oct 2025 10:30:15 +0200
Subject: [PATCH] router: fix SLAAC on subnets > 64
Subject: router: fix SLAAC on subnets > 64
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@@ -1,7 +1,7 @@
From d402cdae431668f55f9d82b7072b0afa3b8090df Mon Sep 17 00:00:00 2001
From bf4dbad4ba8867cdb570426de252f3d994e2db2d Mon Sep 17 00:00:00 2001
From: Stephen Groat <stephen.groat@datadoghq.com>
Date: Wed, 8 Oct 2025 11:54:51 -0700
Subject: [PATCH] ndp: fix macOS IPv6 compatibility by using link-local source
Subject: ndp: fix macOS IPv6 compatibility by using link-local source
addresses
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
@@ -1,7 +1,7 @@
From 30780debd691aee7567784daf1fdfd8db500a485 Mon Sep 17 00:00:00 2001
From d9a202773d18ab9f88fc55f79b11b5f61a200085 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Thu, 9 Oct 2025 11:08:14 +0200
Subject: [PATCH] odhcpd: fix a compilation error
Subject: odhcpd: fix a compilation error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@@ -1,7 +1,7 @@
From be7ca7c0792b185263ad86b961ea61129494a7f9 Mon Sep 17 00:00:00 2001
From 7b2d0bd30306dcc014f11b15817b0c5010554e7c Mon Sep 17 00:00:00 2001
From: Fei Lv <feilv@asrmicro.com>
Date: Fri, 14 Nov 2025 15:31:22 +0800
Subject: [PATCH] config: fix memleak during odhcpd reload
Subject: config: fix memleak during odhcpd reload
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@@ -0,0 +1,103 @@
From 8ea9f568dfd8164b0483b5508830f51d488a1c4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Fri, 17 Oct 2025 15:32:08 +0200
Subject: ndp: correctness fix for BPF filter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Note that AF_PACKET sockets start receiving packets as soon as they are
created. Thus, a packet can arrive between the creation of the socket
and the time the real filter is installed. Fix this using the same
technique as used in libpcap, i.e. by installing a drop-all filter,
removing any packets from the socket, then installing the real filter
later (an atomic operation which replaces the drop filter).
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/319
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit 650a5df88ebae82d956831c2f87c7c6eedd8c283)
---
src/ndp.c | 56 +++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 44 insertions(+), 12 deletions(-)
--- a/src/ndp.c
+++ b/src/ndp.c
@@ -39,17 +39,6 @@ static void setup_addr_for_relaying(stru
static void handle_solicit(void *addr, void *data, size_t len,
struct interface *iface, void *dest);
-/* Filter ICMPv6 messages of type neighbor solicitation */
-static struct sock_filter bpf[] = {
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
- offsetof(struct icmp6_hdr, icmp6_type)),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
- BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
- BPF_STMT(BPF_RET | BPF_K, 0),
-};
-static const struct sock_fprog bpf_prog = {sizeof(bpf) / sizeof(*bpf), bpf};
static struct netevent_handler ndp_netevent_handler = { .cb = ndp_netevent_cb, };
/* Initialize NDP-proxy */
@@ -67,6 +56,30 @@ int ndp_init(void)
int ndp_setup_interface(struct interface *iface, bool enable)
{
+ /* Drop everything */
+ static const struct sock_filter bpf_drop_filter[] = {
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ };
+ static const struct sock_fprog bpf_drop = {
+ .len = ARRAY_SIZE(bpf_drop_filter),
+ .filter = (struct sock_filter *)bpf_drop_filter,
+ };
+
+ /* Filter ICMPv6 messages of type neighbor solicitation */
+ static const struct sock_filter bpf[] = {
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
+ offsetof(struct icmp6_hdr, icmp6_type)),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
+ BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ };
+ static const struct sock_fprog bpf_prog = {
+ .len = ARRAY_SIZE(bpf),
+ .filter = (struct sock_filter *)bpf,
+ };
+
int ret = 0, procfd;
bool dump_neigh = false;
char procbuf[64];
@@ -170,8 +183,27 @@ int ndp_setup_interface(struct interface
}
#endif
+ /*
+ * AF_PACKET sockets can receive packets as soon as they are
+ * created, so make sure we don't accept anything...
+ */
+ if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ &bpf_drop, sizeof(bpf_drop))) {
+ syslog(LOG_ERR, "setsockopt(SO_ATTACH_FILTER): %m");
+ ret = -1;
+ goto out;
+ }
+
+ /* ...and remove stray packets... */
+ while (true) {
+ char null[1];
+ if (recv(iface->ndp_event.uloop.fd, null, sizeof(null), MSG_DONTWAIT | MSG_TRUNC) < 0)
+ break;
+ }
+
+ /* ...until the real filter is installed */
if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
- &bpf_prog, sizeof(bpf_prog))) {
+ &bpf_prog, sizeof(bpf_prog))) {
syslog(LOG_ERR, "setsockopt(SO_ATTACH_FILTER): %m");
ret = -1;
goto out;
@@ -0,0 +1,97 @@
From a7100a04a7da0936fd84e0808eded4e30e90696b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Sun, 23 Nov 2025 14:46:49 +0100
Subject: config: fix (S)NTP realloc handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Make sure that realloc errors are dealt with properly in the (S)NTP
option parsing.
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/320
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit b4ab371c541ab481c7dff001964afd621b5a968d)
---
src/config.c | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
--- a/src/config.c
+++ b/src/config.c
@@ -564,10 +564,10 @@ static int parse_ntp_options(uint16_t *d
{
uint16_t sub_opt = 0, sub_len = htons(IPV6_ADDR_LEN);
uint16_t ntp_len = IPV6_ADDR_LEN + 4;
- uint8_t *ntp = *dhcpv6_ntp;
+ uint8_t *ntp;
size_t pos = *dhcpv6_ntp_len;
- ntp = realloc(ntp, pos + ntp_len);
+ ntp = realloc(*dhcpv6_ntp, pos + ntp_len);
if (!ntp)
return -1;
@@ -594,7 +594,7 @@ static int parse_ntp_fqdn(uint16_t *dhcp
{
size_t fqdn_len = strlen(fqdn);
uint16_t sub_opt = 0, sub_len = 0, ntp_len = 0;
- uint8_t *ntp = *dhcpv6_ntp;
+ uint8_t *ntp;
size_t pos = *dhcpv6_ntp_len;
uint8_t buf[256] = {0};
@@ -607,7 +607,7 @@ static int parse_ntp_fqdn(uint16_t *dhcp
ntp_len = len + 4;
- ntp = realloc(ntp, pos + ntp_len);
+ ntp = realloc(*dhcpv6_ntp, pos + ntp_len);
if (!ntp)
return -1;
@@ -1472,32 +1472,34 @@ int config_parse_interface(void *data, s
continue;
char *str = blobmsg_get_string(cur);
- struct in_addr addr4;
- struct in6_addr addr6;
+ struct in_addr addr4, *tmp4;
+ struct in6_addr addr6, *tmp6;
if (inet_pton(AF_INET, str, &addr4) == 1) {
if (addr4.s_addr == INADDR_ANY)
goto err;
- iface->dhcpv4_ntp = realloc(iface->dhcpv4_ntp,
- (++iface->dhcpv4_ntp_cnt) * sizeof(*iface->dhcpv4_ntp));
- if (!iface->dhcpv4_ntp)
+ tmp4 = realloc(iface->dhcpv4_ntp, (iface->dhcpv4_ntp_cnt + 1) * sizeof(*iface->dhcpv4_ntp));
+ if (!tmp4)
goto err;
- iface->dhcpv4_ntp[iface->dhcpv4_ntp_cnt - 1] = addr4;
+ iface->dhcpv4_ntp = tmp4;
+ iface->dhcpv4_ntp[iface->dhcpv4_ntp_cnt++] = addr4;
+
} else if (inet_pton(AF_INET6, str, &addr6) == 1) {
if (IN6_IS_ADDR_UNSPECIFIED(&addr6))
goto err;
- iface->dhcpv6_sntp = realloc(iface->dhcpv6_sntp,
- (++iface->dhcpv6_sntp_cnt) * sizeof(*iface->dhcpv6_sntp));
- if (!iface->dhcpv6_sntp)
+ tmp6 = realloc(iface->dhcpv6_sntp, (iface->dhcpv6_sntp_cnt + 1) * sizeof(*iface->dhcpv6_sntp));
+ if (!tmp6)
goto err;
- iface->dhcpv6_sntp[iface->dhcpv6_sntp_cnt - 1] = addr6;
+ iface->dhcpv6_sntp = tmp6;
+ iface->dhcpv6_sntp[iface->dhcpv6_sntp_cnt++] = addr6;
if (!parse_ntp_options(&iface->dhcpv6_ntp_len, addr6, &iface->dhcpv6_ntp))
iface->dhcpv6_ntp_cnt++;
+
} else {
if (!parse_ntp_fqdn(&iface->dhcpv6_ntp_len, str, &iface->dhcpv6_ntp))
iface->dhcpv6_ntp_cnt++;
@@ -0,0 +1,41 @@
From 061253933a52a394df7e79eae312212b1911d7fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Sun, 23 Nov 2025 14:50:00 +0100
Subject: config: fix realloc() handling for the "upstream" option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Deal properly with realloc() failure.
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/320
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit adb2b8a1afffd805753dcc2e52c4764028116676)
---
src/config.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
--- a/src/config.c
+++ b/src/config.c
@@ -1062,16 +1062,17 @@ int config_parse_interface(void *data, s
if (overwrite && (c = tb[IFACE_ATTR_UPSTREAM])) {
struct blob_attr *cur;
unsigned rem;
+ char *tmp;
blobmsg_for_each_attr(cur, c, rem) {
if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, false))
continue;
- iface->upstream = realloc(iface->upstream,
- iface->upstream_len + blobmsg_data_len(cur));
- if (!iface->upstream)
+ tmp = realloc(iface->upstream, iface->upstream_len + blobmsg_data_len(cur));
+ if (!tmp)
goto err;
+ iface->upstream = tmp;
memcpy(iface->upstream + iface->upstream_len, blobmsg_get_string(cur), blobmsg_data_len(cur));
iface->upstream_len += blobmsg_data_len(cur);
}
@@ -0,0 +1,38 @@
From 7474f1b5fb05705d9f04ccd45eeeea995ee9f09a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Sun, 23 Nov 2025 14:53:35 +0100
Subject: config: fix realloc() error handling for "dhcpv6_raw" option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Check the return value from realloc() to avoid leaking memory.
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/320
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit 38b0920e376b236ea0b56686d08a4ef49b4bfcbe)
---
src/config.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
--- a/src/config.c
+++ b/src/config.c
@@ -1241,8 +1241,15 @@ int config_parse_interface(void *data, s
iface->dhcpv4_forcereconf = blobmsg_get_bool(c);
if ((c = tb[IFACE_ATTR_DHCPV6_RAW])) {
- iface->dhcpv6_raw_len = blobmsg_data_len(c) / 2;
- iface->dhcpv6_raw = realloc(iface->dhcpv6_raw, iface->dhcpv6_raw_len);
+ void *tmp;
+ size_t opt_len = blobmsg_data_len(c) / 2;
+
+ tmp = realloc(iface->dhcpv6_raw, opt_len);
+ if (!tmp)
+ goto err;
+
+ iface->dhcpv6_raw = tmp;
+ iface->dhcpv6_raw_len = opt_len;
odhcpd_unhexlify(iface->dhcpv6_raw, iface->dhcpv6_raw_len, blobmsg_get_string(c));
}
@@ -0,0 +1,33 @@
From 4e3aeeb71ba64f5fafff779e08e902510a4baff9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Sun, 23 Nov 2025 15:00:45 +0100
Subject: config: handle realloc failure in piofolder parsing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Another realloc that goes unchecked.
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/320
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit 2e1042c6c07faad22bc300d1c526b24089b1ced9)
---
src/config.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
--- a/src/config.c
+++ b/src/config.c
@@ -1893,7 +1893,11 @@ void config_load_ra_pio(struct interface
free(iface->pios);
iface->pios = NULL;
} else if (iface->pio_cnt != pio_cnt) {
- iface->pios = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt);
+ struct ra_pio *tmp;
+
+ tmp = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt);
+ if (tmp)
+ iface->pios = tmp;
}
}
@@ -0,0 +1,32 @@
From 6bfc8547642eb61c755b36bbcc6616f742832aff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Sun, 23 Nov 2025 15:12:01 +0100
Subject: dhcpv6: handle realloc failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Another realloc failure case.
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/320
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit b9db4d7061a08bf82a25222074065cce71973d0c)
---
src/dhcpv6-ia.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
--- a/src/dhcpv6-ia.c
+++ b/src/dhcpv6-ia.c
@@ -1636,8 +1636,9 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *bu
hdr->msg_type == DHCPV6_MSG_REQUEST ||
hdr->msg_type == DHCPV6_MSG_REBIND)) {
if ((!(a->flags & OAF_STATIC) || !a->hostname) && hostname_len > 0) {
- a->hostname = realloc(a->hostname, hostname_len + 1);
- if (a->hostname) {
+ char *hostname = realloc(a->hostname, hostname_len + 1);
+ if (hostname) {
+ a->hostname = hostname;
memcpy(a->hostname, hostname, hostname_len);
a->hostname[hostname_len] = 0;
@@ -0,0 +1,37 @@
From 4ff35533494ea5b531540a69e90ede1124eaede7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Thu, 27 Nov 2025 11:35:09 +0100
Subject: dhcpv6-ia: fix realloc bug
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Commit b9db4d7061a08bf82a25222074065cce71973d0c introduced a bug, the
"hostname" variable used for the realloc would shadow the real hostname
defined at the beginning of the function. Fix this by using a different
variable name.
Fixes: b9db4d7061a0 ("dhcpv6: handle realloc failure")
Signed-off-by: David Härdeman <david@hardeman.nu>
Link: https://github.com/openwrt/odhcpd/pull/326
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
(cherry picked from commit d21e504b38ab4c880c43b7f1649104bb2f0d2d8b)
---
src/dhcpv6-ia.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
--- a/src/dhcpv6-ia.c
+++ b/src/dhcpv6-ia.c
@@ -1636,9 +1636,9 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *bu
hdr->msg_type == DHCPV6_MSG_REQUEST ||
hdr->msg_type == DHCPV6_MSG_REBIND)) {
if ((!(a->flags & OAF_STATIC) || !a->hostname) && hostname_len > 0) {
- char *hostname = realloc(a->hostname, hostname_len + 1);
- if (hostname) {
- a->hostname = hostname;
+ char *tmp = realloc(a->hostname, hostname_len + 1);
+ if (tmp) {
+ a->hostname = tmp;
memcpy(a->hostname, hostname, hostname_len);
a->hostname[hostname_len] = 0;
@@ -0,0 +1,51 @@
From 67ef12f1fbd69915ed72fbd030afe70ed08c96f8 Mon Sep 17 00:00:00 2001
From: Alexander Couzens <lynxis@fe80.eu>
Date: Fri, 6 Feb 2026 14:15:03 +0100
Subject: dhcpv6-ia: dhcpv6_log_ia_addr(): parse return of snprintf
snprintf() returns also in the failure case how many characters would have been required
to print the full string.
Check return of snprintf() and ensure the log_ctxt->buf_idx never points
outside of the given log_ctxt->buf.
Fixes: TOB-OWRT-5
Reported-by: Trail of Bits
Signed-off-by: Alexander Couzens <lynxis@fe80.eu>
(cherry picked from commit 13cec018c0d9bd452f4cb5e583c52fdf206cdc31)
---
src/dhcpv6-ia.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
--- a/src/dhcpv6-ia.c
+++ b/src/dhcpv6-ia.c
@@ -1266,6 +1266,7 @@ static size_t build_ia(uint8_t *buf, siz
struct log_ctxt {
char *buf;
int buf_len;
+ /* if full, buf_idx will point to the last valid memory in buf */
int buf_idx;
};
@@ -1274,10 +1275,20 @@ static void dhcpv6_log_ia_addr(struct in
{
struct log_ctxt *ctxt = (struct log_ctxt *)arg;
char addrbuf[INET6_ADDRSTRLEN];
+ int ret;
+
+ /* Log buffer full */
+ if (ctxt->buf_idx >= ctxt->buf_len - 1)
+ return;
inet_ntop(AF_INET6, addr, addrbuf, sizeof(addrbuf));
- ctxt->buf_idx += snprintf(ctxt->buf + ctxt->buf_idx, ctxt->buf_len - ctxt->buf_idx,
- "%s/%d ", addrbuf, prefix);
+ ret = snprintf(ctxt->buf + ctxt->buf_idx, ctxt->buf_len - ctxt->buf_idx,
+ "%s/%d ", addrbuf, prefix);
+
+ if (ret + ctxt->buf_idx < ctxt->buf_len - 1)
+ ctxt->buf_idx += ret;
+ else
+ ctxt->buf_idx = ctxt->buf_len - 1;
}
static void dhcpv6_log(uint8_t msgtype, struct interface *iface, time_t now,
@@ -0,0 +1,120 @@
From 3c3223c0aadb8a3949f7cd4050a911a65c0abd70 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:06:55 +0200
Subject: dhcpv6: bound nested-relay recursion to HOP_COUNT_LIMIT
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
handle_nested_message() and update_nested_message() recurse once for
every OPTION_RELAY_MSG inside a Relay-Forward message, with no depth
limit. A crafted UDP datagram packed full of nested Relay-Forward
options can drive recursion ~1700 deep on a ~64KB packet and exhaust
the daemon's stack.
Each nested Relay-Forward layer is exactly one relay hop: a relay agent
wraps the message it received in a new Relay-Forward and increments
hop_count. So the recursion depth here is just the relay-chain length
seen structurally. RFC8415 bounds that chain via HOP_COUNT_LIMIT
(defined in §7.6; a relay discards an over-limit Relay-forward per
§19.1.2), so a conforming message can never nest deeper than
HOP_COUNT_LIMIT layers.
Cap the descent at DHCPV6_HOP_COUNT_LIMIT accordingly. We count the
actual recursion depth instead of trusting each header's on-wire
hop_count, because an attacker controls the nesting and every per-layer
hop_count independently (e.g. nest deeply while stamping hop_count=0
everywhere). This is the same limit relay_client_request() already
enforces on the outer header of a forwarded message.
Note: DHCPV6_HOP_COUNT_LIMIT is 32, the value from the obsoleted
RFC3315 §5.5; RFC8415 §7.6 lowered the default to 8. Either way 32 is a
safe structural ceiling that drops only pathologically nested messages,
never a legitimate relay chain.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit ee0a11f40fa48302482fd6e85458e26d48de6e3a)
---
src/dhcpv6.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -188,7 +188,7 @@ enum {
IOV_TOTAL
};
-static void handle_nested_message(uint8_t *data, size_t len,
+static void handle_nested_message(uint8_t *data, size_t len, unsigned depth,
struct dhcpv6_client_header **c_hdr, uint8_t **opts,
uint8_t **end, struct iovec iov[IOV_TOTAL])
{
@@ -212,19 +212,26 @@ static void handle_nested_message(uint8_
return;
}
+ /* Each nested Relay-Forward is one relay hop; RFC8415 bounds a relay
+ * chain via HOP_COUNT_LIMIT (defined in §7.6, enforced in §19.1.2),
+ * so refuse to recurse deeper and avoid stack exhaustion on crafted
+ * relay loops. */
+ if (depth >= DHCPV6_HOP_COUNT_LIMIT)
+ return;
+
dhcpv6_for_each_option(r_hdr->options, data + len, otype, olen, odata) {
if (otype == DHCPV6_OPT_RELAY_MSG) {
iov[IOV_RELAY_MSG].iov_base = odata + olen;
iov[IOV_RELAY_MSG].iov_len = (((uint8_t *)iov[IOV_NESTED].iov_base) +
iov[IOV_NESTED].iov_len) - (odata + olen);
- handle_nested_message(odata, olen, c_hdr, opts, end, iov);
+ handle_nested_message(odata, olen, depth + 1, c_hdr, opts, end, iov);
return;
}
}
}
-static void update_nested_message(uint8_t *data, size_t len, ssize_t pdiff)
+static void update_nested_message(uint8_t *data, size_t len, unsigned depth, ssize_t pdiff)
{
struct dhcpv6_relay_header *hdr = (struct dhcpv6_relay_header*)data;
if (hdr->msg_type != DHCPV6_MSG_RELAY_FORW)
@@ -232,6 +239,10 @@ static void update_nested_message(uint8_
hdr->msg_type = DHCPV6_MSG_RELAY_REPL;
+ /* Bound recursion to mirror handle_nested_message(). */
+ if (depth >= DHCPV6_HOP_COUNT_LIMIT)
+ return;
+
uint16_t otype, olen;
uint8_t *odata;
dhcpv6_for_each_option(hdr->options, data + len, otype, olen, odata) {
@@ -239,7 +250,7 @@ static void update_nested_message(uint8_
olen += pdiff;
odata[-2] = (olen >> 8) & 0xff;
odata[-1] = olen & 0xff;
- update_nested_message(odata, olen - pdiff, pdiff);
+ update_nested_message(odata, olen - pdiff, depth + 1, pdiff);
return;
}
}
@@ -564,7 +575,7 @@ static void handle_client_request(void *
};
if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
- handle_nested_message(data, len, &hdr, &opts, &opts_end, iov);
+ handle_nested_message(data, len, 0, &hdr, &opts, &opts_end, iov);
switch (hdr->msg_type) {
case DHCPV6_MSG_SOLICIT:
@@ -730,7 +741,7 @@ static void handle_client_request(void *
}
if (iov[IOV_NESTED].iov_len > 0) /* Update length */
- update_nested_message(data, len, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len +
+ update_nested_message(data, len, 0, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len +
iov[IOV_RAPID_COMMIT].iov_len + iov[IOV_DNS].iov_len +
iov[IOV_DNS_ADDR].iov_len + iov[IOV_SEARCH].iov_len +
iov[IOV_SEARCH_DOMAIN].iov_len + iov[IOV_PDBUF].iov_len +
@@ -0,0 +1,52 @@
From d1e66d0afb8ed749bee242c5ad9f322a0a9130e0 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:09:09 +0200
Subject: dhcpv4: honor Pad/End option encoding when iterating options
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The dhcpv4_for_each_option() macro always read a length byte at offset 1
and advanced by len+2, which is wrong for the Pad (0) and End (255)
options. Per RFC2132 §3.1/§3.2 / RFC1497 those two options are a single octet
with no length byte, so the parser was effectively interpreting the
byte after a Pad as a length and skipping a variable, attacker-chosen
number of bytes — letting a crafted request hide later options (e.g.
DHCP message type, client-id) from the server. End was likewise
treated as a long-with-length option instead of terminating the scan.
Rewrite the macro to advance by 1 on Pad, stop on End, and otherwise
validate that code+len+data all fit in the buffer.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit db034cfd5eab2d0f3315cfb3cd74002fefa15828)
---
src/dhcpv4.h | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
--- a/src/dhcpv4.h
+++ b/src/dhcpv4.h
@@ -98,8 +98,17 @@ struct dhcpv4_option {
};
+/* RFC2132 §3.1/§3.2 (orig. RFC1497): the Pad (0) and End (255) options are
+ * 1 octet long and have no length byte. Every other DHCPv4 option is
+ * { code, len, data[len] }. Treat Pad as a 1-byte no-op, End as loop
+ * termination, and reject any other option whose declared length runs past
+ * the buffer.
+ */
#define dhcpv4_for_each_option(start, end, opt)\
- for (opt = (struct dhcpv4_option*)(start); \
- &opt[1] <= (struct dhcpv4_option*)(end) && \
- &opt->data[opt->len] <= (end); \
- opt = (struct dhcpv4_option*)&opt->data[opt->len])
+ for (uint8_t *_o = (uint8_t *)(start); \
+ _o < (uint8_t *)(end) && \
+ (opt = (struct dhcpv4_option *)_o)->type != DHCPV4_OPT_END && \
+ (opt->type == DHCPV4_OPT_PAD || \
+ (_o + 2 <= (uint8_t *)(end) && \
+ _o + 2 + opt->len <= (uint8_t *)(end))); \
+ _o += (opt->type == DHCPV4_OPT_PAD) ? 1 : 2 + opt->len)
@@ -0,0 +1,62 @@
From 4100329ec73d4767602ca2c507ec1e1c1264752f Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:17:55 +0200
Subject: dhcpv6-ia: avoid undefined shifts in assign_pd()
assign_pd() computes (1 << (64 - length)) where the literal is a plain
int. When the prefix length is <= 32 the shift count is >= 32 bits,
which is undefined behaviour, and even at length == 33 the result
(1 << 31) is signed-overflow UB.
In normal operation the delegated prefix length is bounded by
dhcpv6_pd_min_len whose default keeps the shift in safe territory, but
that knob is user-configurable down to 1 in UCI, so a misconfigured
interface trips UB on every PD assignment. Compute the shift in
uint64_t and truncate explicitly to uint32_t so the arithmetic is
well-defined; for sane prefix lengths (>=33) the value is unchanged.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 19bf3b73580b50030490c04e44adb6a14fe8c31c)
---
src/dhcpv6-ia.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
--- a/src/dhcpv6-ia.c
+++ b/src/dhcpv6-ia.c
@@ -769,8 +769,14 @@ static bool assign_pd(struct interface *
} else if (iface->addr6_len < 1)
return false;
- /* Try honoring the hint first */
- uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1;
+ /* Try honoring the hint first.
+ *
+ * Subnet-id slots per delegated prefix = 2^(64 - length); the literal
+ * 1 is plain int, so shifting it by 32-63 bits is undefined behaviour
+ * for short prefix lengths (the user can drive this via
+ * dhcpv6_pd_min_len). Compute the shift in uint64_t and truncate.
+ */
+ uint32_t current = 1, asize = (uint32_t)((1ULL << (64 - assign->length)) - 1);
if (assign->assigned_subnet_id) {
list_for_each_entry(c, &iface->ia_assignments, head) {
if (c->flags & OAF_DHCPV6_NA)
@@ -785,7 +791,7 @@ static bool assign_pd(struct interface *
return true;
}
- current = (c->assigned_subnet_id + (1 << (64 - c->length)));
+ current = (uint32_t)(c->assigned_subnet_id + (1ULL << (64 - c->length)));
}
}
@@ -807,7 +813,7 @@ static bool assign_pd(struct interface *
return true;
}
- current = (c->assigned_subnet_id + (1 << (64 - c->length)));
+ current = (uint32_t)(c->assigned_subnet_id + (1ULL << (64 - c->length)));
}
return false;
@@ -0,0 +1,48 @@
From e587d403b689c04141cfeac638c68e898c5b565e Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:21:10 +0200
Subject: =?UTF-8?q?ndp:=20enforce=20RFC4861=20=C2=A77.1.1=20hop-limit=20an?=
=?UTF-8?q?d=20ICMP-code=20checks=20on=20NS?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
handle_solicit() previously trusted whatever the AF_PACKET socket
delivered, so a Neighbor Solicitation with Hop Limit < 255 or a
non-zero ICMP Code was forwarded to peer interfaces (triggering proxy
NDP lookups and an NA on the requesting interface). RFC4861 §7.1.1
requires NS messages with those properties to be silently discarded —
the hop-limit rule in particular is what prevents off-link attackers
from forging NS / DAD packets onto our link.
The router.c equivalent (router_icmpv6_valid + the IPV6_RECVHOPLIMIT
cmsg check in odhcpd_receive_packets) already does this for RA/RS;
the NDP path needs to enforce it itself because AF_PACKET sees raw
frames with no kernel-side L3 validation.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 0eade3d4b7a37106f6498f4ce952ecd6ec1b932d)
---
src/ndp.c | 9 +++++++++
1 file changed, 9 insertions(+)
--- a/src/ndp.c
+++ b/src/ndp.c
@@ -382,6 +382,15 @@ static void handle_solicit(void *addr, v
if (len < sizeof(*ip6) + sizeof(*req))
return; // Invalid total length
+ /* RFC4861 §7.1.1: a Neighbor Solicitation MUST be silently discarded
+ * if the IP Hop Limit field is not 255 or the ICMP Code is non-zero.
+ * The hop-limit check is what protects against off-link attackers
+ * forging NS / DAD messages, so it must not be skipped. Because the
+ * NDP socket is AF_PACKET (not a kernel-managed ICMPv6 socket), we
+ * have to enforce it ourselves. */
+ if (ip6->ip6_hlim != 255 || req->nd_ns_hdr.icmp6_code != 0)
+ return;
+
if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
@@ -0,0 +1,46 @@
From 82dca288b46f1cb5c6f5eaffb2080ebca4a3d651 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 30 May 2026 01:29:46 +0200
Subject: dhcpv6: validate minimum length in relay_client_request()
relay_server_response() refuses packets shorter than the relay header,
but the client-side relay path skipped any length check. With UDP
permitting zero/short payloads, a 0- or 1-byte datagram would let the
switch read h->msg_type from data[0] and (if it happened to equal
DHCPV6_MSG_RELAY_FORW = 12) h->hop_count from data[1] out of bounds.
The bytes come from the kernel receive buffer so it is a contained UB
read, but it is reachable.
relay_client_request() casts data to a struct dhcpv6_relay_header and
reads only h->msg_type (offset 0) and, for a RELAY_FORW, h->hop_count
(offset 1); the payload is forwarded verbatim and never parsed here.
Bound len on offsetof(struct dhcpv6_relay_header, link_address) so the
guard names the struct the function actually uses and the exact extent
it reads, mirroring how relay_server_response() bounds on its own
header.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit cc04882b3fac7643921a194ab65650274a69b2d6)
---
src/dhcpv6.c | 8 ++++++++
1 file changed, 8 insertions(+)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -902,6 +902,14 @@ static void relay_client_request(struct
struct odhcpd_ipaddr *ip;
struct sockaddr_in6 s;
+ /* A bare UDP socket can deliver a zero/short payload; the relay-reply
+ * path (relay_server_response) checks this but the client-side relay
+ * did not. relay_client_request() reads h->msg_type, plus h->hop_count
+ * for a RELAY_FORW, out of the relay header and forwards the rest
+ * verbatim, so require at least those two leading header bytes. */
+ if (len < offsetof(struct dhcpv6_relay_header, link_address))
+ return;
+
if (h->msg_type == DHCPV6_MSG_RELAY_REPL ||
h->msg_type == DHCPV6_MSG_RECONFIGURE ||
h->msg_type == DHCPV6_MSG_REPLY ||
@@ -0,0 +1,42 @@
From 60185a154c404004730c3136cc4cb63c8d61c978 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 11 Jul 2026 22:15:32 +0200
Subject: dhcpv4: copy ifname into arpreq without reading past the source
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
dhcpv4_set_dest_addr() filled arp.arp_dev (a fixed 16-byte field) via
memcpy(..., iface->ifname, sizeof(arp.arp_dev)). iface->ifname is a
strdup'd string whose allocation is just strlen(ifname)+1 bytes — for
typical names like "eth0" or "br-lan" that is 57 bytes, so the memcpy
reads ~10 bytes past the heap allocation. The bytes happen to be
inside the same malloc chunk on glibc so it has never blown up in
practice, but it is undefined behaviour and would trip valgrind / ASan.
Switch to the strncpy form already used for ifr_name elsewhere; the
struct is zero-initialised so the trailing byte stays NUL.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit 7c834a54ae6da732edaf2e8021fa3681e69951a3)
---
src/dhcpv4.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
--- a/src/dhcpv4.c
+++ b/src/dhcpv4.c
@@ -971,7 +971,11 @@ void dhcpv4_handle_msg(void *addr, void
if (!(iface->ifflags & IFF_NOARP)) {
memcpy(arp.arp_ha.sa_data, req->chaddr, 6);
memcpy(&arp.arp_pa, &dest, sizeof(arp.arp_pa));
- memcpy(arp.arp_dev, iface->ifname, sizeof(arp.arp_dev));
+ /* arp_dev is a 16-byte fixed buffer; strdup'd ifname is
+ * typically shorter than that, so memcpy()ing the full
+ * field length reads past the allocation. Match the
+ * strncpy pattern used elsewhere for ifr_name. */
+ strncpy(arp.arp_dev, iface->ifname, sizeof(arp.arp_dev) - 1);
if (ioctl(sock, SIOCSARP, &arp) < 0)
syslog(LOG_ERR, "ioctl(SIOCSARP): %m");
@@ -0,0 +1,75 @@
From 5669df2f672f093933dc67de549b765206ae6523 Mon Sep 17 00:00:00 2001
From: Hauke Mehrtens <hauke@hauke-m.de>
Date: Sat, 23 May 2026 14:39:03 +0200
Subject: dhcpv6: avoid unaligned uint16_t reads in ORO option parsing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Option Request Option payload is an array of 16-bit codes, but the
buffer (odata) is at an arbitrary offset inside the wire packet — on
strict-alignment platforms (ARM v5/v6, MIPS, SPARC) reading it through
a uint16_t * pointer triggers a misaligned access, which the kernel
either fixes up at high cost or signals as SIGBUS. The same pattern
appeared in the top-level ORO scan and again in the inner per-option
loop. Use byte-pointer access plus memcpy() to extract each code
portably.
Assisted-by: Claude:claude-opus-4-7
Link: https://github.com/openwrt/odhcpd/pull/401
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit fa270221504c32861a9457dfa815b4215eaabbb0)
---
src/dhcpv6.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -424,7 +424,11 @@ static void handle_client_request(void *
uint16_t otype, olen;
uint8_t *odata;
- uint16_t *reqopts = NULL;
+ /* OPTION_ORO payload is an array of uint16_t but the underlying buffer
+ * isn't guaranteed to be 2-byte aligned (it's at an arbitrary offset
+ * inside the packed wire packet), so keep it as a byte pointer and
+ * memcpy each value out rather than casting to uint16_t *. */
+ uint8_t *reqopts = NULL;
size_t reqopts_cnt = 0;
/* FIXME: this should be merged with the second loop further down */
@@ -432,14 +436,17 @@ static void handle_client_request(void *
/* Requested options, array of uint16_t, RFC 8415 §21.7 */
if (otype == DHCPV6_OPT_ORO) {
reqopts_cnt = olen / sizeof(uint16_t);
- reqopts = (uint16_t *)odata;
+ reqopts = odata;
break;
}
}
/* Requested options */
for (size_t i = 0; i < reqopts_cnt; i++) {
- uint16_t opt = ntohs(reqopts[i]);
+ uint16_t opt;
+
+ memcpy(&opt, &reqopts[i * sizeof(uint16_t)], sizeof(opt));
+ opt = ntohs(opt);
switch (opt) {
case DHCPV6_OPT_SNTP_SERVERS:
@@ -647,8 +654,12 @@ static void handle_client_request(void *
iov[IOV_RAPID_COMMIT].iov_len = sizeof(rapid_commit);
o_rapid_commit = true;
} else if (otype == DHCPV6_OPT_ORO) {
- for (int i=0; i < olen/2; i++) {
- uint16_t option = ntohs(((uint16_t *)odata)[i]);
+ for (int i = 0; i < olen / 2; i++) {
+ uint16_t option;
+
+ /* odata is not guaranteed to be uint16-aligned. */
+ memcpy(&option, &odata[i * 2], sizeof(option));
+ option = ntohs(option);
switch (option) {
#ifdef DHCPV4_SUPPORT

Some files were not shown because too many files have changed in this diff Show More