[RFC PATCH v1 07/13] SAE: Add DecoyAuth fragment handling

Jeff Hansen x at jeffhansen.com
Fri Sep 11 12:20:02 PDT 2026


Add bounded serialization and ordered reassembly for DecoyAuth
value payloads that do not fit in a single SAE Authentication frame.

Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
 src/common/sae.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/common/sae.h |  16 +++++
 2 files changed, 191 insertions(+), 1 deletion(-)

diff --git a/src/common/sae.c b/src/common/sae.c
index e42f2e9a2..687407d58 100644
--- a/src/common/sae.c
+++ b/src/common/sae.c
@@ -160,6 +160,9 @@ void sae_clear_temp_data(struct sae_data *sae)
 	crypto_ec_point_deinit(tmp->own_commit_element_ecc, 0);
 	crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0);
 	wpabuf_free(tmp->anti_clogging_token);
+#ifdef CONFIG_SAE_DECOYAUTH
+	wpabuf_free(tmp->decoyauth_rx_values);
+#endif /* CONFIG_SAE_DECOYAUTH */
 	wpabuf_free(tmp->own_rejected_groups);
 	wpabuf_free(tmp->peer_rejected_groups);
 	os_free(tmp->pw_id);
@@ -1539,7 +1542,6 @@ static int sae_ap_prepare_commit_internal(const u8 *addr1, const u8 *addr2,
 		goto fail;
 	}
 
-
 	for (int i = 0; !candidates_ready && i < num_passwords; i++) {
 		struct crypto_bignum **encoded_point;
 		const struct sae_pt *pt = NULL;
@@ -1726,6 +1728,68 @@ int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
 }
 
 
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae)
+{
+	size_t len = sae_write_values_len(sae);
+
+	return (len + SAE_DECOYAUTH_FRAGMENT_DATA_LEN - 1) /
+		SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+}
+
+
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+				 u16 fragment)
+{
+	struct wpabuf *values = NULL, *payload = NULL;
+	const u8 *pos;
+	size_t total, offset, frag_len, remaining;
+	u16 count;
+	int ret = -1;
+
+	total = sae_write_values_len(sae);
+	count = sae_decoyauth_fragment_count(sae);
+	if (!total || !count || fragment >= count || total > UINT32_MAX)
+		return -1;
+	offset = (size_t) fragment * SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+	frag_len = total - offset;
+	if (frag_len > SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+		frag_len = SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+
+	values = wpabuf_alloc(total);
+	payload = wpabuf_alloc(SAE_DECOYAUTH_FRAGMENT_HEADER_LEN + frag_len);
+	if (!values || !payload || sae_write_values(sae, values) < 0)
+		goto out;
+
+	wpabuf_put_u8(payload, SAE_DECOYAUTH_VENDOR_VERSION);
+	wpabuf_put_le16(payload, fragment);
+	wpabuf_put_le16(payload, count);
+	WPA_PUT_LE32(wpabuf_put(payload, 4), total);
+	wpabuf_put_data(payload, wpabuf_head_u8(values) + offset, frag_len);
+
+	wpabuf_put_le16(buf, sae->group);
+	pos = wpabuf_head_u8(payload);
+	remaining = wpabuf_len(payload);
+	while (remaining) {
+		size_t elem_len =
+			remaining > SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN ?
+			SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN : remaining;
+
+		wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
+		wpabuf_put_u8(buf, elem_len + 4);
+		WPA_PUT_BE32(wpabuf_put(buf, 4),
+			     SAE_DECOYAUTH_VALUES_VENDOR_TYPE);
+		wpabuf_put_data(buf, pos, elem_len);
+		pos += elem_len;
+		remaining -= elem_len;
+	}
+	ret = 0;
+out:
+	wpabuf_free(payload);
+	wpabuf_free(values);
+	return ret;
+}
+
+
 #endif /* CONFIG_SAE_DECOYAUTH */
 
 
@@ -2636,6 +2700,83 @@ static struct wpabuf *sae_parse_decoyauth_ie_values(const u8 *pos,
 }
 
 
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+				 size_t len, int *ie_offset)
+{
+	struct sae_temporary_data *tmp;
+	struct wpabuf *fragment = NULL;
+	const u8 *pos, *end, *next, *payload;
+	size_t payload_len, expected_len, offset;
+	u32 total;
+	u16 index, count;
+	int ret = -1;
+
+	if (!sae || !sae->tmp || len < 2 ||
+	    WPA_GET_LE16(data) != sae->group)
+		return -1;
+	tmp = sae->tmp;
+	pos = data + 2;
+	end = data + len;
+	fragment = sae_parse_decoyauth_ie_values(pos, end, &next);
+	if (!fragment || wpabuf_len(fragment) <
+	    SAE_DECOYAUTH_FRAGMENT_HEADER_LEN)
+		goto out;
+	payload = wpabuf_head_u8(fragment);
+	payload_len = wpabuf_len(fragment);
+	if (payload[0] != SAE_DECOYAUTH_VENDOR_VERSION)
+		goto out;
+	index = WPA_GET_LE16(payload + 1);
+	count = WPA_GET_LE16(payload + 3);
+	total = WPA_GET_LE32(payload + 5);
+	payload += SAE_DECOYAUTH_FRAGMENT_HEADER_LEN;
+	payload_len -= SAE_DECOYAUTH_FRAGMENT_HEADER_LEN;
+	if (!count || index >= count || !total ||
+	    total > 2U * SAE_MAX_PASSWORDS * SAE_MAX_ECC_PRIME_LEN ||
+	    count != (total + SAE_DECOYAUTH_FRAGMENT_DATA_LEN - 1) /
+		SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+		goto out;
+	offset = (size_t) index * SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+	expected_len = total - offset;
+	if (expected_len > SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+		expected_len = SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+	if (payload_len != expected_len)
+		goto out;
+
+	if (index < tmp->decoyauth_rx_next) {
+		if (!tmp->decoyauth_rx_values ||
+		    tmp->decoyauth_rx_total != total ||
+		    tmp->decoyauth_rx_count != count ||
+		    wpabuf_len(tmp->decoyauth_rx_values) < offset + payload_len ||
+		    os_memcmp(wpabuf_head_u8(tmp->decoyauth_rx_values) + offset,
+			      payload, payload_len) != 0)
+			goto out;
+	} else {
+		if (index != tmp->decoyauth_rx_next)
+			goto out;
+		if (index == 0) {
+			wpabuf_free(tmp->decoyauth_rx_values);
+			tmp->decoyauth_rx_values = wpabuf_alloc(total);
+			tmp->decoyauth_rx_total = total;
+			tmp->decoyauth_rx_count = count;
+			if (!tmp->decoyauth_rx_values)
+				goto out;
+		} else if (!tmp->decoyauth_rx_values ||
+			   tmp->decoyauth_rx_total != total ||
+			   tmp->decoyauth_rx_count != count) {
+			goto out;
+		}
+		wpabuf_put_data(tmp->decoyauth_rx_values, payload, payload_len);
+		tmp->decoyauth_rx_next++;
+	}
+	if (ie_offset)
+		*ie_offset = next - data;
+	ret = 0;
+out:
+	wpabuf_free(fragment);
+	return ret;
+}
+
+
 #endif /* CONFIG_SAE_DECOYAUTH */
 
 
@@ -3323,6 +3464,14 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
 			return WLAN_STATUS_UNSPECIFIED_FAILURE;
 		value_data = wpabuf_head_u8(values);
 		value_len = wpabuf_len(values);
+	} else if (sae_commit_elem_is_zero(commit_elem, commit_elem_len)) {
+		if (sae->tmp->decoyauth_rx_values &&
+		    sae->tmp->decoyauth_rx_next == sae->tmp->decoyauth_rx_count &&
+		    wpabuf_len(sae->tmp->decoyauth_rx_values) ==
+		    sae->tmp->decoyauth_rx_total) {
+			value_data = wpabuf_head_u8(sae->tmp->decoyauth_rx_values);
+			value_len = wpabuf_len(sae->tmp->decoyauth_rx_values);
+		}
 	}
 
 	if (value_data) {
@@ -3331,6 +3480,11 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
 		res = sae_parse_decoyauth_values(sae, password, password_len,
 						 value_data, value_len);
 		wpabuf_free(values);
+		wpabuf_free(sae->tmp->decoyauth_rx_values);
+		sae->tmp->decoyauth_rx_values = NULL;
+		sae->tmp->decoyauth_rx_total = 0;
+		sae->tmp->decoyauth_rx_count = 0;
+		sae->tmp->decoyauth_rx_next = 0;
 		if (res != WLAN_STATUS_SUCCESS)
 			return res;
 	} else {
@@ -3768,6 +3922,26 @@ int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
 }
 
 
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae)
+{
+	return 0;
+}
+
+
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+				 u16 fragment)
+{
+	return -1;
+}
+
+
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+				 size_t len, int *ie_offset)
+{
+	return -1;
+}
+
+
 u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
 			size_t password_len, const u8 *data, size_t len,
 			const u8 **token, size_t *token_len,
diff --git a/src/common/sae.h b/src/common/sae.h
index 35c51a43b..4a6641db5 100644
--- a/src/common/sae.h
+++ b/src/common/sae.h
@@ -23,6 +23,11 @@
 #define SAE_DECOYAUTH_VENDOR_VERSION 1
 #define SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN 251
 #define SAE_DECOYAUTH_VALUES_ELEM_OVERHEAD 6
+#define SAE_DECOYAUTH_MAX_AUTH_BODY_LEN 2300
+#define SAE_DECOYAUTH_CAPABILITY_IE_LEN 8
+#define SAE_DECOYAUTH_FRAGMENT_DATA_LEN 1900
+#define SAE_DECOYAUTH_FRAGMENT_HEADER_LEN 9
+#define SAE_DECOYAUTH_FRAGMENT_STATUS 65534
 
 enum sae_decoyauth_mode {
 	SAE_DECOYAUTH_DISABLED = 0,
@@ -82,6 +87,11 @@ struct sae_temporary_data {
 	struct crypto_bignum *prime_buf;
 	struct crypto_bignum *order_buf;
 	struct wpabuf *anti_clogging_token;
+	struct wpabuf *decoyauth_rx_values;
+	u32 decoyauth_rx_total;
+	u16 decoyauth_rx_count;
+	u16 decoyauth_rx_next;
+	u16 decoyauth_fragment_request;
 	const char *decoyauth_cache_dir;
 	unsigned int decoyauth_cache_entries;
 	u8 *pw_id;
@@ -98,6 +108,7 @@ struct sae_temporary_data {
 	unsigned int own_addr_higher:1;
 	unsigned int try_other_password:1;
 	unsigned int decoyauth:1;
+	unsigned int decoyauth_fragmented:1;
 	unsigned int decoyauth_commit_processed:1;
 
 #ifdef CONFIG_SAE_PK
@@ -201,6 +212,11 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
 			int *decoyauth_used);
 size_t sae_write_values_len(const struct sae_data *sae);
 int sae_write_values(struct sae_data *sae, struct wpabuf *buf);
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae);
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+				 u16 fragment);
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+				 size_t len, int *ie_offset);
 int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf);
 int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
 		      int *ie_offset);
-- 
2.53.0




More information about the Hostap mailing list