Refer:
https://build.opensuse.org/projects/SUSE:SLE-12-SP5:Update/packages/freeradius-server.34052/files/CVE-2024-3596.patch?expand=1
https://github.com/FreeRADIUS/freeradius-server/commit/ae4293ecf56de6d61bc4506b36a0a4766d29bab4

diff -Nur freeradius-server-3.0.15_bak/man/man1/radclient.1 freeradius-server-3.0.15/man/man1/radclient.1
--- freeradius-server-3.0.15_bak/man/man1/radclient.1	2024-07-16 09:42:44.299009476 +0800
+++ freeradius-server-3.0.15/man/man1/radclient.1	2024-07-16 10:05:00.218116906 +0800
@@ -5,6 +5,7 @@
 .B radclient
 .RB [ \-4 ]
 .RB [ \-6 ]
+.RB [ \-b ]
 .RB [ \-c
 .IR count ]
 .RB [ \-d
@@ -52,6 +53,13 @@
 Use IPv4 (default)
 .IP \-6
 Use IPv6
+.IP \-b
+Enforce the Blast RADIUS checks.  All replies to an Access-Request packet
+must contain a Message-Authenticator as the first attribute.
+
+For compatibility with old servers, this flag is not set by default.
+However, radclient still checks for the Blast RADIUS signature, and
+discards packets which match the attack.
 .IP \-c\ \fIcount\fP
 Send each packet \fIcount\fP times.
 .IP \-d\ \fIraddb_directory\fP
diff -Nur freeradius-server-3.0.15_bak/raddb/clients.conf freeradius-server-3.0.15/raddb/clients.conf
--- freeradius-server-3.0.15_bak/raddb/clients.conf	2024-07-16 09:42:44.301009479 +0800
+++ freeradius-server-3.0.15/raddb/clients.conf	2024-07-16 10:05:00.213116898 +0800
@@ -100,15 +100,30 @@
 	secret = testing123
 
 	#
-	#  Old-style clients do not send a Message-Authenticator
-	#  in an Access-Request.  RFC 5080 suggests that all clients
-	#  SHOULD include it in an Access-Request.  The configuration
-	#  item below allows the server to require it.  If a client
-	#  is required to include a Message-Authenticator and it does
-	#  not, then the packet will be silently discarded.
+	#  The global configuration "security.require_message_authenticator"
+	#  flag sets the default for all clients.  That default can be
+	#  over-ridden here, by setting it to "no".
+	#
+	#  This flag exists solely for legacy clients which do not send
+	#  Message-Authenticator in all Access-Request packets.  We do not
+	#  recommend setting it to "no".
+	#
+	#  allowed values: yes, no
+	#
+#	require_message_authenticator = yes
+
+	#
+	#  The global configuration "security.limit_proxy_state"
+	#  flag sets the default for all clients.  That default can be
+	#  over-ridden here, by setting it to "no".
+	#
+	#  This flag exists solely for legacy clients which do not send
+	#  Message-Authenticator in all Access-Request packets.  We do not
+	#  recommend setting it to "no".
 	#
 	#  allowed values: yes, no
-	require_message_authenticator = no
+	#
+#      limit_proxy_state = yes
 
 	#
 	#  The short name is used as an alias for the fully qualified
diff -Nur freeradius-server-3.0.15_bak/raddb/proxy.conf freeradius-server-3.0.15/raddb/proxy.conf
--- freeradius-server-3.0.15_bak/raddb/proxy.conf	2024-07-16 09:42:44.308009490 +0800
+++ freeradius-server-3.0.15/raddb/proxy.conf	2024-07-16 10:05:00.216116903 +0800
@@ -204,6 +204,20 @@
 	#
 	secret = testing123
 
+	#  The global configuration "security.require_message_authenticator"
+	#  flag sets the default for all home servers.  That default can be
+	#  over-ridden here, by setting it to "no".
+	#
+	#  This flag exists solely for legacy home servers which do
+	#  not send Message-Authenticator in all Access-Accept,
+	#  Access-Reject, or Access-Challenge packets.  We do not
+	#  recommend setting it to "no".
+	#
+	#  allowed values: yes, no
+	#
+	#require_message_authenticator = no
+
+
 	############################################################
 	#
 	#  The rest of the configuration items listed here are optional,
diff -Nur freeradius-server-3.0.15_bak/raddb/radiusd.conf.in freeradius-server-3.0.15/raddb/radiusd.conf.in
--- freeradius-server-3.0.15_bak/raddb/radiusd.conf.in	2024-07-16 09:42:44.308009490 +0800
+++ freeradius-server-3.0.15/raddb/radiusd.conf.in	2024-07-16 10:05:00.209116892 +0800
@@ -461,6 +461,64 @@
 	#
 	status_server = yes
 
+	#
+	#  Global configuration for requiring Message-Authenticator
+	#  in all Access-* packets sent over UDP or TCP.  This flag
+	#  is ignored for TLS.
+
+	#
+	#  This flag sets the global default for all clients and home
+	#  servers.  It can be over-ridden in an individual client or
+	#  home server definition by adding a flag to that section:
+	#
+	#	require_message_authenticator = no
+	#
+	#  If the server produces error message which says "Packet
+	#  does not contain required Message-Authenticator attribute",
+	#  then this configuration item has to be updated.
+	#
+	#  WARNING: This item should always be left as "yes",
+	#  otherwise it is possible for MITM attackers to create fake
+	#  Access-Accept packets to the NAS!
+	#
+	require_message_authenticator = yes
+
+	#
+	#  Global configuration for requiring Message-Authenticator
+	#  Access-Request packets from a NAS, but only if those
+	#  packets also contain Proxy-State.  This flag only applies
+	#  to packets sent over UDP or TCP.  This flag is ignored for
+	#  TLS.
+	#
+	#  This flag sets the global default for all clients.  It can
+	#  be over-ridden in an individual client definition by adding
+	#  a flag to that section:
+	#
+	#	limit_proxy_state = no
+	#
+	#  If "require_message_authenticator" is set to "yes", this
+	#  configuration item is ignored.
+	#
+	#  If "require_message_authenticator" is set to "no", this
+	#  configuration item is checked.
+	#
+	#  This configuration item should ALWAYS be set to "yes".
+	#
+	#  The only reason to set it to "no" is when the client is a
+	#  proxy, AND the proxy does not send Message-Authenticator in
+	#  Access-Request packets.  Even then, the best approach to
+	#  fix the issue is to (1) update the proxy to send
+	#  Message-Authenticator, and if that can't be done, then (2)
+	#  set this flag to "no", but ONLY on a per-client basis.
+	#
+	#  WARNING: Setting both this flag and the
+	#  "require_message_authenticator" flag to "no" will allow
+	#  MITM attackers to create fake Access-Accept packets to the
+	#  NAS!  At least one of them MUST be set to "yes" for the
+	#  system to have any protection against the attack.
+	#
+	limit_proxy_state = yes
+
 @openssl_version_check_config@
 }
 
diff -Nur freeradius-server-3.0.15_bak/src/include/clients.h freeradius-server-3.0.15/src/include/clients.h
--- freeradius-server-3.0.15_bak/src/include/clients.h	2024-07-16 09:42:44.319009507 +0800
+++ freeradius-server-3.0.15/src/include/clients.h	2024-07-16 10:05:00.210116893 +0800
@@ -39,7 +39,9 @@
 
 	char const		*secret;		//!< Secret PSK.
 
-	bool			message_authenticator;	//!< Require RADIUS message authenticator in requests.
+	bool			require_ma;		//!< Require RADIUS message authenticator in requests.
+
+	bool			limit_proxy_state;	//!< Limit Proxy-State in requests
 
 	char const		*nas_type;		//!< Type of client (arbitrary).
 
diff -Nur freeradius-server-3.0.15_bak/src/include/conffile.h freeradius-server-3.0.15/src/include/conffile.h
--- freeradius-server-3.0.15_bak/src/include/conffile.h	2024-07-16 09:42:44.319009507 +0800
+++ freeradius-server-3.0.15/src/include/conffile.h	2024-07-16 10:05:00.212116897 +0800
@@ -140,6 +140,7 @@
 #define PW_TYPE_MULTI		(1 << 18) //!< CONF_PAIR can have multiple copies.
 #define PW_TYPE_NOT_EMPTY	(1 << 19) //!< CONF_PAIR is required to have a non zero length value.
 #define PW_TYPE_FILE_EXISTS	((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist
+#define PW_TYPE_IGNORE_DEFAULT	(1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing
 /* @} **/
 
 #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\
diff -Nur freeradius-server-3.0.15_bak/src/include/libradius.h freeradius-server-3.0.15/src/include/libradius.h
--- freeradius-server-3.0.15_bak/src/include/libradius.h	2024-07-16 09:42:44.319009507 +0800
+++ freeradius-server-3.0.15/src/include/libradius.h	2024-07-16 10:05:00.214116900 +0800
@@ -402,6 +402,7 @@
 	size_t			partial;
 	int			proto;
 #endif
+	bool			tls;		//!< uses secure transport
 } RADIUS_PACKET;
 
 typedef enum {
@@ -507,6 +508,11 @@
 /* radius.c */
 int		rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret);
 bool		rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason);
+/*
+ *     1 == require_ma
+ *     2 == msg_peek
+ *     3 == limit_proxy_state
+ */
 RADIUS_PACKET	*rad_recv(TALLOC_CTX *ctx, int fd, int flags);
 ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code);
 void		rad_recv_discard(int sockfd);
diff -Nur freeradius-server-3.0.15_bak/src/include/radiusd.h freeradius-server-3.0.15/src/include/radiusd.h
--- freeradius-server-3.0.15_bak/src/include/radiusd.h	2024-07-16 09:42:44.319009507 +0800
+++ freeradius-server-3.0.15/src/include/radiusd.h	2024-07-16 10:05:00.209116892 +0800
@@ -169,6 +169,9 @@
 
 	bool		exiting;			//!< are we exiting?
 
+	bool		require_ma;			//!< global configuration for all clients and home servers
+
+	bool		limit_proxy_state;		//!< global configuration for all clients
 
 #ifdef ENABLE_OPENSSL_VERSION_CHECK
 	char const	*allow_vulnerable_openssl;	//!< The CVE number of the last security issue acknowledged.
diff -Nur freeradius-server-3.0.15_bak/src/include/realms.h freeradius-server-3.0.15/src/include/realms.h
--- freeradius-server-3.0.15_bak/src/include/realms.h	2024-07-16 09:42:44.321009510 +0800
+++ freeradius-server-3.0.15/src/include/realms.h	2024-07-16 10:05:00.216116903 +0800
@@ -58,6 +58,8 @@
 							//!< stats or when specifying home servers for a pool.
 
 	bool			dual;			//!< One of a pair of homeservers on consecutive ports.
+	bool			require_ma;		//!< for all replies to Access-Request and Status-Server
+
 	char const		*server;		//!< For internal proxying
 	char const		*parent_server;
 
diff -Nur freeradius-server-3.0.15_bak/src/lib/radius.c freeradius-server-3.0.15/src/lib/radius.c
--- freeradius-server-3.0.15_bak/src/lib/radius.c	2024-07-16 09:42:44.321009510 +0800
+++ freeradius-server-3.0.15/src/lib/radius.c	2024-07-16 10:05:00.216116903 +0800
@@ -1740,6 +1740,7 @@
 	uint16_t		total_length;
 	int			len;
 	VALUE_PAIR const	*reply;
+	bool			seen_ma = false;
 
 	/*
 	 *	A 4K packet, aligned on 64-bits.
@@ -1804,6 +1805,27 @@
 	 */
 
 	/*
+	 *      Always add Message-Authenticator for replies to
+	 *      Access-Request packets.
+	 *
+	 *      It must be the FIRST attribute in the packet.
+	 */
+	if (!packet->tls &&
+	    ((original && (original->code == PW_CODE_ACCESS_REQUEST)) ||
+	     (packet->code == PW_CODE_ACCESS_REQUEST))) {
+	        seen_ma = true;
+	
+	        packet->offset = RADIUS_HDR_LEN;
+	
+	        ptr[0] = PW_MESSAGE_AUTHENTICATOR;
+	        ptr[1] = 18;
+	        memset(ptr + 2, 0, 16);
+	
+	        ptr += 18;
+	        total_length += 18;
+	}
+
+	/*
 	 *	Loop over the reply attributes for the packet.
 	 */
 	reply = packet->vps;
@@ -1851,10 +1873,22 @@
 		}
 
 		/*
+		 *	How much room do we have left?
+		 */
+		room = ((uint8_t *) data) + sizeof(data) - ptr;
+
+		/*
 		 *	Set the Message-Authenticator to the correct
 		 *	length and initial value.
 		 */
 		if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) {
+			// don't encode Message-Authenticator twice
+			if (seen_ma) {
+			    reply = reply->next;
+			    continue;
+			}
+			if (room < 18) break;
+
 			/*
 			 *	Cache the offset to the
 			 *	Message-Authenticator
@@ -1862,14 +1896,16 @@
 			packet->offset = total_length;
 			last_len = 16;
 		} else {
+			if (room < (2 + reply->vp_length)) break;
+
 			last_len = reply->vp_length;
 		}
 		last_name = reply->da->name;
 
-		room = ((uint8_t *) data) + sizeof(data) - ptr;
-
-		if (room <= 2) break;
-
+		/*
+		 *	Note that this also checks "room", as the
+		 *	attribute may be a VSA, etc.
+		 */
 		len = rad_vp2attr(packet, original, secret, &reply, ptr, room);
 		if (len < 0) return -1;
 
@@ -1980,7 +2016,7 @@
 	 *	If there's a Message-Authenticator, update it
 	 *	now.
 	 */
-	if (packet->offset > 0) {
+	if ((packet->offset > 0) && ((packet->offset + 18) <= packet->data_len)) {
 		uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
 
 		switch (packet->code) {
@@ -2341,6 +2377,8 @@
 	radius_packet_t		*hdr;
 	char			host_ipaddr[128];
 	bool			require_ma = false;
+	bool			limit_proxy_state = false;
+	bool			seen_proxy_state = false;
 	bool			seen_ma = false;
 	uint32_t		num_attributes;
 	decode_fail_t		failure = DECODE_FAIL_NONE;
@@ -2391,13 +2429,14 @@
 	/*
 	 *	Message-Authenticator is required in Status-Server
 	 *	packets, otherwise they can be trivially forged.
-	 */
-	if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true;
-
-	/*
+	 *
 	 *	It's also required if the caller asks for it.
+	 *
+	 *	We only limit Proxy-State if we're not requiring
+	 *	Message-Authenticator.
 	 */
-	if (flags) require_ma = true;
+	require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER);
+	limit_proxy_state = ((flags & 0x04) != 0) & !require_ma;
 
 	/*
 	 *	Repeat the length checks.  This time, instead of
@@ -2560,6 +2599,10 @@
 			non_eap = true;
 			break;
 
+		case PW_PROXY_STATE:
+			seen_proxy_state = true;
+			break;
+
 		case PW_MESSAGE_AUTHENTICATOR:
 			if (attr[1] != 2 + AUTH_VECTOR_LEN) {
 				FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d",
@@ -2631,6 +2674,18 @@
 			   inet_ntop(packet->src_ipaddr.af,
 				     &packet->src_ipaddr.ipaddr,
 				     host_ipaddr, sizeof(host_ipaddr)));
+		failure = DECODE_FAIL_MA_MISSING;
+		goto finish;
+	}
+
+	/*
+	 *	The client is a NAS which shouldn't send Proxy-State, but it did!
+	 */
+	if (limit_proxy_state && seen_proxy_state && !seen_ma) {
+		FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s:  Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes",
+			   inet_ntop(packet->src_ipaddr.af,
+				     &packet->src_ipaddr.ipaddr,
+				     host_ipaddr, sizeof(host_ipaddr)));
 		failure = DECODE_FAIL_MA_MISSING;
 		goto finish;
 	}
diff -Nur freeradius-server-3.0.15_bak/src/main/client.c freeradius-server-3.0.15/src/main/client.c
--- freeradius-server-3.0.15_bak/src/main/client.c	2024-07-16 09:42:44.323009514 +0800
+++ freeradius-server-3.0.15/src/main/client.c	2024-07-16 10:05:00.211116895 +0800
@@ -315,7 +315,8 @@
 		    (old->coa_server == client->coa_server) &&
 		    (old->coa_pool == client->coa_pool) &&
 #endif
-		    (old->message_authenticator == client->message_authenticator)) {
+		    (old->require_ma == client->require_ma) &&
+		    (old->limit_proxy_state == client->limit_proxy_state)) {
 			WARN("Ignoring duplicate client %s", client->longname);
 			client_free(client);
 			return true;
@@ -501,7 +502,8 @@
 
 	{ "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL },
 
-	{ "require_message_authenticator",  FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" },
+	{ "require_message_authenticator",  FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_IGNORE_DEFAULT, RADCLIENT, require_ma), NULL },
+	{ "limit_proxy_state",  FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_IGNORE_DEFAULT, RADCLIENT, limit_proxy_state), NULL },
 
 	{ "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL },
 	{ "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL },
@@ -697,7 +699,7 @@
 	{ "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL },
 	{ "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL },
 
-	{ "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL },
+	{ "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, require_ma), NULL },
 
 	{ "FreeRADIUS-Client-Secret",  FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" },
 	{ "FreeRADIUS-Client-Shortname",  FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" },
@@ -875,6 +877,15 @@
 	c = talloc_zero(ctx, RADCLIENT);
 	c->cs = cs;
 
+	/*
+	 *	Set the "require message authenticator" and "limit
+	 *	proxy state" flags from the global default.  If the
+	 *	configuration item exists, AND is set, it will
+	 *	over-ride the flag.
+	 */
+	c->require_ma = main_config.require_ma;
+	c->limit_proxy_state = main_config.limit_proxy_state;
+
 	memset(&cl_ipaddr, 0, sizeof(cl_ipaddr));
 	cl_netmask = 255;
 
@@ -1191,7 +1202,7 @@
 	if (shortname) c->shortname = talloc_typed_strdup(c, shortname);
 	if (type) c->nas_type = talloc_typed_strdup(c, type);
 	if (server) c->server = talloc_typed_strdup(c, server);
-	c->message_authenticator = require_ma;
+	c->require_ma = require_ma;
 
 	return c;
 }
diff -Nur freeradius-server-3.0.15_bak/src/main/conffile.c freeradius-server-3.0.15/src/main/conffile.c
--- freeradius-server-3.0.15_bak/src/main/conffile.c	2024-07-16 09:42:44.324009515 +0800
+++ freeradius-server-3.0.15/src/main/conffile.c	2024-07-16 10:05:00.213116898 +0800
@@ -1388,6 +1388,7 @@
 {
 	int rcode;
 	bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists;
+	bool ignore_dflt;
 	char **q;
 	char const *value;
 	CONF_PAIR *cp = NULL;
@@ -1408,6 +1409,7 @@
 	cant_be_empty = (type & PW_TYPE_NOT_EMPTY);
 	tmpl = (type & PW_TYPE_TMPL);
 	multi = (type & PW_TYPE_MULTI);
+	ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT);
 
 	if (attribute) required = true;
 	if (required) cant_be_empty = true;	/* May want to review this in the future... */
@@ -1431,7 +1433,7 @@
 	 *	section, use the default value.
 	 */
 	if (!cp) {
-		if (deprecated) return 0;	/* Don't set the default value */
+		if (deprecated || ignore_dflt) return 0;	/* Don't set the default value */
 
 		rcode = 1;
 		value = dflt;
diff -Nur freeradius-server-3.0.15_bak/src/main/listen.c freeradius-server-3.0.15/src/main/listen.c
--- freeradius-server-3.0.15_bak/src/main/listen.c	2024-07-16 09:42:44.324009515 +0800
+++ freeradius-server-3.0.15/src/main/listen.c	2024-07-16 10:05:00.212116897 +0800
@@ -1536,7 +1536,7 @@
 	 *	Now that we've sanity checked everything, receive the
 	 *	packet.
 	 */
-	packet = rad_recv(ctx, listener->fd, client->message_authenticator);
+	packet = rad_recv(ctx, listener->fd, client->require_ma | (((int) client->limit_proxy_state) << 2));
 	if (!packet) {
 		FR_STATS_INC(auth, total_malformed_requests);
 		if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
@@ -1929,7 +1929,7 @@
 	 *	Now that we've sanity checked everything, receive the
 	 *	packet.
 	 */
-	packet = rad_recv(ctx, listener->fd, client->message_authenticator);
+	packet = rad_recv(ctx, listener->fd, client->require_ma);
 	if (!packet) {
 		FR_STATS_INC(coa, total_malformed_requests);
 		if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
diff -Nur freeradius-server-3.0.15_bak/src/main/mainconfig.c freeradius-server-3.0.15/src/main/mainconfig.c
--- freeradius-server-3.0.15_bak/src/main/mainconfig.c	2024-07-16 09:42:44.324009515 +0800
+++ freeradius-server-3.0.15/src/main/mainconfig.c	2024-07-16 10:05:00.210116893 +0800
@@ -153,6 +153,8 @@
 	{ "max_attributes",  FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) },
 	{ "reject_delay",  FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) },
 	{ "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"},
+	{ "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.require_ma), "yes"},
+	{ "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.limit_proxy_state), "yes"},
 #ifdef ENABLE_OPENSSL_VERSION_CHECK
 	{ "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"},
 #endif
diff -Nur freeradius-server-3.0.15_bak/src/main/process.c freeradius-server-3.0.15/src/main/process.c
--- freeradius-server-3.0.15_bak/src/main/process.c	2024-07-16 09:42:44.324009515 +0800
+++ freeradius-server-3.0.15/src/main/process.c	2024-07-16 10:05:00.218116906 +0800
@@ -2485,11 +2485,32 @@
 	 *	ignore it.  This does the MD5 calculations in the
 	 *	server core, but I guess we can fix that later.
 	 */
-	if (!request->proxy_reply &&
-	    (rad_verify(packet, request->proxy,
-			request->home_server->secret) != 0)) {
-		DEBUG("Ignoring spoofed proxy reply.  Signature is invalid");
-		return 0;
+	if (!request->proxy_reply) {
+		decode_fail_t reason;
+		
+		/*
+		 *      If the home server configuration requires a Message-Authenticator, then set the flag,
+		 *      but only if the proxied packet is Access-Request or Status-Sercer.
+		 *
+		 *      The realms.c file already clears require_ma for TLS connections.
+		 */
+		bool require_ma = request->home_server->require_ma && (request->proxy->code == PW_CODE_ACCESS_REQUEST);
+
+		if (!request->home_server) {
+			proxy_reply_too_late(request);
+			return 0;
+		}
+ 
+		if (!rad_packet_ok(packet, require_ma, &reason)) {
+			DEBUG("Ignoring invalid packet - %s", fr_strerror());
+			return 0;
+		}
+
+		if (rad_verify(packet, request->proxy,
+			       request->home_server->secret) != 0) {
+			DEBUG("Ignoring spoofed proxy reply.  Signature is invalid");
+			return 0;
+		}
 	}
 
 	/*
diff -Nur freeradius-server-3.0.15_bak/src/main/radclient.c freeradius-server-3.0.15/src/main/radclient.c
--- freeradius-server-3.0.15_bak/src/main/radclient.c	2024-07-16 09:42:44.324009515 +0800
+++ freeradius-server-3.0.15/src/main/radclient.c	2024-07-16 10:05:00.219116908 +0800
@@ -53,6 +53,7 @@
 static int resend_count = 1;
 static bool done = true;
 static bool print_filename = false;
+static bool blast_radius = false;
 
 static fr_ipaddr_t client_ipaddr;
 static uint16_t client_port = 0;
@@ -86,6 +87,7 @@
 	fprintf(stderr, "  <command>              One of auth, acct, status, coa, disconnect or auto.\n");
 	fprintf(stderr, "  -4                     Use IPv4 address of server\n");
 	fprintf(stderr, "  -6                     Use IPv6 address of server.\n");
+	fprintf(stderr, "  -b                     Mandate checks for Blast RADIUS issue (this is not set by default).\n");
 	fprintf(stderr, "  -c <count>             Send each packet 'count' times.\n");
 	fprintf(stderr, "  -d <raddb>             Set user dictionary directory (defaults to " RADDBDIR ").\n");
 	fprintf(stderr, "  -D <dictdir>           Set main dictionary directory (defaults to " DICTDIR ").\n");
@@ -986,6 +988,131 @@
 }
 
 /*
+ *     Do Blast RADIUS checks.
+ *
+ *     The request is an Access-Request, and does NOT contain Proxy-State.
+ *
+ *     The reply is a raw packet, and is NOT yet decoded.
+ */
+static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply)
+{
+	uint8_t *attr, *end;
+	VALUE_PAIR *vp;
+	bool have_message_authenticator = false;
+ 
+	/*
+	*      We've received a raw packet.  Nothing has (as of yet) checked
+	*      anything in it other than the length, and that it's a
+	*      well-formed RADIUS packet.
+	*/
+	switch (reply->data[0]) {
+	case PW_CODE_ACCESS_ACCEPT:
+	case PW_CODE_ACCESS_REJECT:
+	case PW_CODE_ACCESS_CHALLENGE:
+	       if (reply->data[1] != request->packet->id) {
+		       ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id);
+		       return -1;
+	       }
+	       break;
+ 
+	default:
+	       ERROR("Invalid reply code %d to Access-Request", reply->data[0]);
+	       return -1;
+	}
+ 
+	/*
+	*      If the reply has a Message-Authenticator, then it MIGHT be fine.
+	*/
+	attr = reply->data + 20;
+	end = reply->data + reply->data_len;
+ 
+	/*
+	*      It should be the first attribute, so we warn if it isn't there.
+	*
+	*      But it's not a fatal error.
+	*/
+	if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) {
+	       RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute.	The packet may be vulnerable to Blast RADIUS attacks.",
+		      fr_packet_codes[reply->data[0]]);
+	}
+ 
+	/*
+	*      Set up for Proxy-State checks.
+	*
+	*      If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack.
+	*/
+	vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY);
+ 
+	while (attr < end) {
+	       /*
+		*      Blast RADIUS work-arounds require that
+		*      Message-Authenticator is the first attribute in the
+		*      reply.  Note that we don't check for it being the
+		*      first attribute, but simply that it exists.
+		*
+		*      That check is a balance between securing the reply
+		*      packet from attacks, and not violating the RFCs which
+		*      say that there is no order to attributes in the
+		*      packet.
+		*
+		*      However, no matter the status of the '-b' flag we
+		*      still can check for the signature of the attack, and
+		*      discard packets which are suspicious.  This behavior
+		*      protects radclient from the attack, without mandating
+		*      new behavior on the server side.
+		*
+		*      Note that we don't set the '-b' flag by default.
+		*      radclient is intended for testing / debugging, and is
+		*      not intended to be used as part of a secure login /
+		*      user checking system.
+		*/
+	       if (attr[0] == PW_MESSAGE_AUTHENTICATOR) {
+		       have_message_authenticator = true;
+		       goto next;
+	       }
+ 
+	       /*
+		*      If there are Proxy-State attributes in the reply, they must
+		*      match EXACTLY the Proxy-State attributes in the request.
+		*
+		*      Note that we don't care if there are more Proxy-States
+		*      in the request than in the reply.  The Blast RADIUS
+		*      issue requires _adding_ Proxy-State attributes, and
+		*      cannot work when the server _deletes_ Proxy-State
+		*      attributes.
+		*/
+	       if (attr[0] == PW_PROXY_STATE) {
+		       if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) {
+			       ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id);
+			       ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent.");
+			       return -1;
+		       }
+ 
+		       vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY);
+	       }
+ 
+	next:
+	       attr += attr[1];
+	}
+ 
+	/*
+	*      If "-b" is set, then we require Message-Authenticator in the reply.
+	*/
+	if (blast_radius && !have_message_authenticator) {
+	       ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.",
+		     fr_packet_codes[reply->data[0]]);
+	       return -1;
+	}
+ 
+	/*
+	*      The packet doesn't look like it's a Blast RADIUS attack.  The
+	*      caller will now verify the packet signature.
+	*/
+	return 0;
+}
+
+
+/*
  *	Receive one packet, maybe.
  */
 static int recv_one_packet(int wait_time)
@@ -1063,6 +1190,20 @@
 	request = fr_packet2myptr(rc_request_t, packet, packet_p);
 
 	/*
+	 *	We want radclient to be able to send any packet, including
+	 *	imperfect ones.  However, we do NOT want to be vulnerable to
+	 *	the "Blast RADIUS" issue.  Instead of adding command-line
+	 *	flags to enable/disable similar flags to what the server
+	 *	sends, we just do a few more smart checks to double-check
+	 *	things.
+	 */
+	if ((request->packet->code == PW_CODE_ACCESS_REQUEST) &&
+	    blast_radius_check(request, reply) < 0) {
+		rad_free(&reply);
+		return -1;
+	}
+ 
+	/*
 	 *	Fails the signature validation: not a real reply.
 	 *	FIXME: Silently drop it and listen for another packet.
 	 */
@@ -1192,7 +1333,7 @@
 		exit(1);
 	}
 
-	while ((c = getopt(argc, argv, "46c:d:D:f:Fhi:n:p:qr:sS:t:vx"
+	while ((c = getopt(argc, argv, "46bc:d:D:f:Fhi:n:p:qr:sS:t:vx"
 #ifdef WITH_TCP
 		"P:"
 #endif
@@ -1205,6 +1346,10 @@
 			force_af = AF_INET6;
 			break;
 
+		case 'b':
+			blast_radius = true;
+			break;
+
 		case 'c':
 			if (!isdigit((int) *optarg))
 				usage();
diff -Nur freeradius-server-3.0.15_bak/src/main/realms.c freeradius-server-3.0.15/src/main/realms.c
--- freeradius-server-3.0.15_bak/src/main/realms.c	2024-07-16 09:42:44.325009517 +0800
+++ freeradius-server-3.0.15/src/main/realms.c	2024-07-16 10:05:00.218116906 +0800
@@ -363,6 +363,7 @@
 #endif
 
 static CONF_PARSER home_server_config[] = {
+	{ "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_IGNORE_DEFAULT, home_server_t, require_ma), NULL },
 	{ "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL },
 	{ "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL },
 	{ "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL },
@@ -636,6 +637,7 @@
 	home->cs = cs;
 	home->state = HOME_STATE_UNKNOWN;
 	home->proto = IPPROTO_UDP;
+	home->require_ma = main_config.require_ma;
 
 	/*
 	 *	Parse the configuration into the home server
@@ -915,6 +917,11 @@
 		 *	Parse the SSL client configuration.
 		 */
 		if (tls) {
+			/*
+			 *	We don't require this for TLS connections.
+			 */
+			home->require_ma = false;
+
 			home->tls = tls_client_conf_parse(tls);
 			if (!home->tls) {
 				goto error;
diff -Nur freeradius-server-3.0.15_bak/src/main/tls_listen.c freeradius-server-3.0.15/src/main/tls_listen.c
--- freeradius-server-3.0.15_bak/src/main/tls_listen.c	2024-07-16 09:42:44.325009517 +0800
+++ freeradius-server-3.0.15/src/main/tls_listen.c	2024-07-16 10:05:00.214116900 +0800
@@ -286,6 +286,8 @@
 	packet->vps = NULL;
 	PTHREAD_MUTEX_UNLOCK(&sock->mutex);
 
+	packet->tls = true;
+
 	if (!rad_packet_ok(packet, 0, NULL)) {
 		if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
 		DEBUG("Closing TLS socket from client");
@@ -677,6 +679,8 @@
 	memcpy(packet->data, data, packet->data_len);
 	memcpy(packet->vector, packet->data + 4, 16);
 
+    packet->tls = true;
+
 	/*
 	 *	FIXME: Client MIB updates?
 	 */
@@ -729,6 +733,7 @@
 	 *	if there's no packet, encode it here.
 	 */
 	if (!request->proxy->data) {
+		request->reply->tls = true;
 		request->proxy_listener->encode(request->proxy_listener,
 						request);
 	}
