git: b31fbebeb3d5 - main - Relax rtsock message restrictions.

Nathan Whitehorn nwhitehorn at freebsd.org
Thu Apr 22 20:59:33 UTC 2021


Thanks! Is this an erratum candidate for 13.0? I was pretty surprised 
when a production machine stopped routing after an upgrade, and I can't 
be the only one.
-Nathan

On 4/20/21 5:35 PM, Alexander V. Chernikov wrote:
> The branch main has been updated by melifaro:
>
> URL: https://cgit.FreeBSD.org/src/commit/?id=b31fbebeb3d59af359a3417cddfbcf666b2c56c9
>
> commit b31fbebeb3d59af359a3417cddfbcf666b2c56c9
> Author:     Alexander V. Chernikov <melifaro at FreeBSD.org>
> AuthorDate: 2021-04-19 20:49:18 +0000
> Commit:     Alexander V. Chernikov <melifaro at FreeBSD.org>
> CommitDate: 2021-04-20 21:34:19 +0000
>
>      Relax rtsock message restrictions.
>      
>      Address multiple issues with strict rtsock message validation.
>      
>      D28668 "normalisation" approach was based on the assumption that
>       we always have at least "standard" sockaddr len.
>      It turned out to be false - certain older applications like quagga
>       or routed abuse sin[6]_len field and set it to the offset to the
>       first fully-zero bit in the mask. It is impossible to normalise
>       such sockaddrs without reallocation.
>      
>      With that in mind, change the approach to use a distinct memory
>       buffer for the altered sockaddrs. This allows supporting the older
>       software while maintaining the guarantee on the "standard" sockaddrs.
>      
>      PR:     255273,255089
>      Differential Revision:  https://reviews.freebsd.org/D29826
>      MFC after:      3 days
> ---
>   sys/net/rtsock.c | 271 ++++++++++++++++++++++++++++++++++++-------------------
>   1 file changed, 177 insertions(+), 94 deletions(-)
>
> diff --git a/sys/net/rtsock.c b/sys/net/rtsock.c
> index 5194a2a15c1e..b7a7e5170c74 100644
> --- a/sys/net/rtsock.c
> +++ b/sys/net/rtsock.c
> @@ -126,6 +126,13 @@ struct ifa_msghdrl32 {
>   
>   #endif /* COMPAT_FREEBSD32 */
>   
> +struct linear_buffer {
> +	char		*base;	/* Base allocated memory pointer */
> +	uint32_t	offset;	/* Currently used offset */
> +	uint32_t	size;	/* Total buffer size */
> +};
> +#define	SCRATCH_BUFFER_SIZE	1024
> +
>   #define	RTS_PID_PRINTF(_fmt, ...) \
>       printf("rtsock:%s(): PID %d: " _fmt "\n", __func__, curproc->p_pid, ## __VA_ARGS__)
>   
> @@ -177,7 +184,7 @@ static int	rtsock_msg_buffer(int type, struct rt_addrinfo *rtinfo,
>   			struct walkarg *w, int *plen);
>   static int	rt_xaddrs(caddr_t cp, caddr_t cplim,
>   			struct rt_addrinfo *rtinfo);
> -static int	cleanup_xaddrs(struct rt_addrinfo *info);
> +static int	cleanup_xaddrs(struct rt_addrinfo *info, struct linear_buffer *lb);
>   static int	sysctl_dumpentry(struct rtentry *rt, void *vw);
>   static int	sysctl_dumpnhop(struct rtentry *rt, struct nhop_object *nh,
>   			uint32_t weight, struct walkarg *w);
> @@ -621,7 +628,8 @@ fill_blackholeinfo(struct rt_addrinfo *info, union sockaddr_union *saun)
>    * Returns 0 on success.
>    */
>   static int
> -fill_addrinfo(struct rt_msghdr *rtm, int len, u_int fibnum, struct rt_addrinfo *info)
> +fill_addrinfo(struct rt_msghdr *rtm, int len, struct linear_buffer *lb, u_int fibnum,
> +    struct rt_addrinfo *info)
>   {
>   	int error;
>   	sa_family_t saf;
> @@ -641,7 +649,7 @@ fill_addrinfo(struct rt_msghdr *rtm, int len, u_int fibnum, struct rt_addrinfo *
>   		return (EINVAL);
>   
>   	info->rti_flags = rtm->rtm_flags;
> -	error = cleanup_xaddrs(info);
> +	error = cleanup_xaddrs(info, lb);
>   	if (error != 0)
>   		return (error);
>   	saf = info->rti_info[RTAX_DST]->sa_family;
> @@ -878,6 +886,45 @@ export_rtaddrs(const struct rtentry *rt, struct sockaddr *dst,
>   #endif
>   }
>   
> +static int
> +update_rtm_from_info(struct rt_addrinfo *info, struct rt_msghdr **prtm,
> +    int alloc_len)
> +{
> +	struct rt_msghdr *rtm, *orig_rtm = NULL;
> +	struct walkarg w;
> +	int len;
> +
> +	rtm = *prtm;
> +	/* Check if we need to realloc storage */
> +	rtsock_msg_buffer(rtm->rtm_type, info, NULL, &len);
> +	if (len > alloc_len) {
> +		struct rt_msghdr *tmp_rtm;
> +
> +		tmp_rtm = malloc(len, M_TEMP, M_NOWAIT);
> +		if (tmp_rtm == NULL)
> +			return (ENOBUFS);
> +		bcopy(rtm, tmp_rtm, rtm->rtm_msglen);
> +		orig_rtm = rtm;
> +		rtm = tmp_rtm;
> +		alloc_len = len;
> +
> +		/*
> +		 * Delay freeing original rtm as info contains
> +		 * data referencing it.
> +		 */
> +	}
> +
> +	w.w_tmem = (caddr_t)rtm;
> +	w.w_tmemsize = alloc_len;
> +	rtsock_msg_buffer(rtm->rtm_type, info, &w, &len);
> +	rtm->rtm_addrs = info->rti_addrs;
> +
> +	if (orig_rtm != NULL)
> +		free(orig_rtm, M_TEMP);
> +	*prtm = rtm;
> +	return (0);
> +}
> +
>   
>   /*
>    * Update sockaddrs, flags, etc in @prtm based on @rc data.
> @@ -891,11 +938,10 @@ static int
>   update_rtm_from_rc(struct rt_addrinfo *info, struct rt_msghdr **prtm,
>       int alloc_len, struct rib_cmd_info *rc, struct nhop_object *nh)
>   {
> -	struct walkarg w;
>   	union sockaddr_union saun;
> -	struct rt_msghdr *rtm, *orig_rtm = NULL;
> +	struct rt_msghdr *rtm;
>   	struct ifnet *ifp;
> -	int error, len;
> +	int error;
>   
>   	rtm = *prtm;
>   	union sockaddr_union sa_dst, sa_mask;
> @@ -927,28 +973,8 @@ update_rtm_from_rc(struct rt_addrinfo *info, struct rt_msghdr **prtm,
>   	} else if (ifp != NULL)
>   		rtm->rtm_index = ifp->if_index;
>   
> -	/* Check if we need to realloc storage */
> -	rtsock_msg_buffer(rtm->rtm_type, info, NULL, &len);
> -	if (len > alloc_len) {
> -		struct rt_msghdr *tmp_rtm;
> -
> -		tmp_rtm = malloc(len, M_TEMP, M_NOWAIT);
> -		if (tmp_rtm == NULL)
> -			return (ENOBUFS);
> -		bcopy(rtm, tmp_rtm, rtm->rtm_msglen);
> -		orig_rtm = rtm;
> -		rtm = tmp_rtm;
> -		alloc_len = len;
> -
> -		/*
> -		 * Delay freeing original rtm as info contains
> -		 * data referencing it.
> -		 */
> -	}
> -
> -	w.w_tmem = (caddr_t)rtm;
> -	w.w_tmemsize = alloc_len;
> -	rtsock_msg_buffer(rtm->rtm_type, info, &w, &len);
> +	if ((error = update_rtm_from_info(info, prtm, alloc_len)) != 0)
> +		return (error);
>   
>   	rtm->rtm_flags = rc->rc_rt->rte_flags | nhop_get_rtflags(nh);
>   	if (rtm->rtm_flags & RTF_GWFLAG_COMPAT)
> @@ -956,11 +982,6 @@ update_rtm_from_rc(struct rt_addrinfo *info, struct rt_msghdr **prtm,
>   			(rtm->rtm_flags & ~RTF_GWFLAG_COMPAT);
>   	rt_getmetrics(rc->rc_rt, nh, &rtm->rtm_rmx);
>   	rtm->rtm_rmx.rmx_weight = rc->rc_nh_weight;
> -	rtm->rtm_addrs = info->rti_addrs;
> -
> -	if (orig_rtm != NULL)
> -		free(orig_rtm, M_TEMP);
> -	*prtm = rtm;
>   
>   	return (0);
>   }
> @@ -985,6 +1006,17 @@ save_add_notification(struct rib_cmd_info *rc, void *_cbdata)
>   }
>   #endif
>   
> +static struct sockaddr *
> +alloc_sockaddr_aligned(struct linear_buffer *lb, int len)
> +{
> +	len |= (sizeof(uint64_t) - 1);
> +	if (lb->offset + len > lb->size)
> +		return (NULL);
> +	struct sockaddr *sa = (struct sockaddr *)(lb->base + lb->offset);
> +	lb->offset += len;
> +	return (sa);
> +}
> +
>   /*ARGSUSED*/
>   static int
>   route_output(struct mbuf *m, struct socket *so, ...)
> @@ -1022,12 +1054,17 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   	 * buffer aligned on 1k boundaty.
>   	 */
>   	alloc_len = roundup2(len, 1024);
> -	if ((rtm = malloc(alloc_len, M_TEMP, M_NOWAIT)) == NULL)
> +	int total_len = alloc_len + SCRATCH_BUFFER_SIZE;
> +	if ((rtm = malloc(total_len, M_TEMP, M_NOWAIT)) == NULL)
>   		senderr(ENOBUFS);
>   
>   	m_copydata(m, 0, len, (caddr_t)rtm);
>   	bzero(&info, sizeof(info));
>   	nh = NULL;
> +	struct linear_buffer lb = {
> +		.base = (char *)rtm + alloc_len,
> +		.size = SCRATCH_BUFFER_SIZE,
> +	};
>   
>   	if (rtm->rtm_version != RTM_VERSION) {
>   		/* Do not touch message since format is unknown */
> @@ -1042,19 +1079,19 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   	 * caller PID and error value.
>   	 */
>   
> -	if ((error = fill_addrinfo(rtm, len, fibnum, &info)) != 0) {
> +	if ((error = fill_addrinfo(rtm, len, &lb, fibnum, &info)) != 0) {
>   		senderr(error);
>   	}
> +	/* fill_addringo() embeds scope into IPv6 addresses */
> +#ifdef INET6
> +	rti_need_deembed = 1;
> +#endif
>   
>   	saf = info.rti_info[RTAX_DST]->sa_family;
>   
>   	/* support for new ARP code */
>   	if (rtm->rtm_flags & RTF_LLDATA) {
>   		error = lla_rt_output(rtm, &info);
> -#ifdef INET6
> -		if (error == 0)
> -			rti_need_deembed = 1;
> -#endif
>   		goto flush;
>   	}
>   
> @@ -1067,7 +1104,6 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   			error = EINVAL;
>   		if (error != 0)
>   			senderr(error);
> -		/* TODO: rebuild rtm from scratch */
>   	}
>   
>   	switch (rtm->rtm_type) {
> @@ -1079,9 +1115,6 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   		}
>   		error = rib_action(fibnum, rtm->rtm_type, &info, &rc);
>   		if (error == 0) {
> -#ifdef INET6
> -			rti_need_deembed = 1;
> -#endif
>   #ifdef ROUTE_MPATH
>   			if (NH_IS_NHGRP(rc.rc_nh_new) ||
>   			    (rc.rc_nh_old && NH_IS_NHGRP(rc.rc_nh_old))) {
> @@ -1110,12 +1143,7 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   			}
>   #endif
>   			nh = rc.rc_nh_old;
> -			goto report;
>   		}
> -#ifdef INET6
> -		/* rt_msg2() will not be used when RTM_DELETE fails. */
> -		rti_need_deembed = 1;
> -#endif
>   		break;
>   
>   	case RTM_GET:
> @@ -1124,13 +1152,18 @@ route_output(struct mbuf *m, struct socket *so, ...)
>   			senderr(error);
>   		nh = rc.rc_nh_new;
>   
> -report:
>   		if (!can_export_rte(curthread->td_ucred,
>   		    info.rti_info[RTAX_NETMASK] == NULL,
>   		    info.rti_info[RTAX_DST])) {
>   			senderr(ESRCH);
>   		}
> +		break;
>   
> +	default:
> +		senderr(EOPNOTSUPP);
> +	}
> +
> +	if (error == 0) {
>   		error = update_rtm_from_rc(&info, &rtm, alloc_len, &rc, nh);
>   		/*
>   		 * Note that some sockaddr pointers may have changed to
> @@ -1147,12 +1180,6 @@ report:
>   #ifdef INET6
>   		rti_need_deembed = 0;
>   #endif
> -		if (error != 0)
> -			senderr(error);
> -		break;
> -
> -	default:
> -		senderr(EOPNOTSUPP);
>   	}
>   
>   flush:
> @@ -1174,6 +1201,10 @@ flush:
>   					bcopy(sin6, info.rti_info[i],
>   						    sizeof(*sin6));
>   			}
> +			if (update_rtm_from_info(&info, &rtm, alloc_len) != 0) {
> +				if (error != 0)
> +					error = ENOBUFS;
> +			}
>   		}
>   	}
>   #endif
> @@ -1350,9 +1381,10 @@ cleanup_xaddrs_lladdr(struct rt_addrinfo *info)
>   }
>   
>   static int
> -cleanup_xaddrs_gateway(struct rt_addrinfo *info)
> +cleanup_xaddrs_gateway(struct rt_addrinfo *info, struct linear_buffer *lb)
>   {
>   	struct sockaddr *gw = info->rti_info[RTAX_GATEWAY];
> +	struct sockaddr *sa;
>   
>   	if (info->rti_flags & RTF_LLDATA)
>   		return (cleanup_xaddrs_lladdr(info));
> @@ -1362,11 +1394,17 @@ cleanup_xaddrs_gateway(struct rt_addrinfo *info)
>   	case AF_INET:
>   		{
>   			struct sockaddr_in *gw_sin = (struct sockaddr_in *)gw;
> -			if (gw_sin->sin_len < sizeof(struct sockaddr_in)) {
> +
> +			/* Ensure reads do not go beyoud SA boundary */
> +			if (SA_SIZE(gw) < offsetof(struct sockaddr_in, sin_zero)) {
>   				RTS_PID_PRINTF("gateway sin_len too small: %d", gw->sa_len);
>   				return (EINVAL);
>   			}
> -			fill_sockaddr_inet(gw_sin, gw_sin->sin_addr);
> +			sa = alloc_sockaddr_aligned(lb, sizeof(struct sockaddr_in));
> +			if (sa == NULL)
> +				return (ENOBUFS);
> +			fill_sockaddr_inet((struct sockaddr_in *)sa, gw_sin->sin_addr);
> +			info->rti_info[RTAX_GATEWAY] = sa;
>   		}
>   		break;
>   #endif
> @@ -1392,13 +1430,17 @@ cleanup_xaddrs_gateway(struct rt_addrinfo *info)
>   				RTS_PID_PRINTF("gateway sdl_len too small: %d", gw_sdl->sdl_len);
>   				return (EINVAL);
>   			}
> +			sa = alloc_sockaddr_aligned(lb, sizeof(struct sockaddr_dl_short));
> +			if (sa == NULL)
> +				return (ENOBUFS);
>   
>   			const struct sockaddr_dl_short sdl = {
>   				.sdl_family = AF_LINK,
> -				.sdl_len = sdl_min_len,
> +				.sdl_len = sizeof(struct sockaddr_dl_short),
>   				.sdl_index = gw_sdl->sdl_index,
>   			};
> -			memcpy(gw_sdl, &sdl, sdl_min_len);
> +			*((struct sockaddr_dl_short *)sa) = sdl;
> +			info->rti_info[RTAX_GATEWAY] = sa;
>   			break;
>   		}
>   	}
> @@ -1416,39 +1458,57 @@ remove_netmask(struct rt_addrinfo *info)
>   
>   #ifdef INET
>   static int
> -cleanup_xaddrs_inet(struct rt_addrinfo *info)
> +cleanup_xaddrs_inet(struct rt_addrinfo *info, struct linear_buffer *lb)
>   {
>   	struct sockaddr_in *dst_sa, *mask_sa;
> +	const int sa_len = sizeof(struct sockaddr_in);
> +	struct in_addr dst, mask;
>   
>   	/* Check & fixup dst/netmask combination first */
>   	dst_sa = (struct sockaddr_in *)info->rti_info[RTAX_DST];
>   	mask_sa = (struct sockaddr_in *)info->rti_info[RTAX_NETMASK];
>   
> -	struct in_addr mask = {
> -		.s_addr = mask_sa ? mask_sa->sin_addr.s_addr : INADDR_BROADCAST,
> -	};
> -	struct in_addr dst = {
> -		.s_addr = htonl(ntohl(dst_sa->sin_addr.s_addr) & ntohl(mask.s_addr))
> -	};
> -
> -	if (dst_sa->sin_len < sizeof(struct sockaddr_in)) {
> -		printf("dst sin_len too small\n");
> +	/* Ensure reads do not go beyound the buffer size */
> +	if (SA_SIZE(dst_sa) < offsetof(struct sockaddr_in, sin_zero))
>   		return (EINVAL);
> -	}
> -	if (mask_sa && mask_sa->sin_len < sizeof(struct sockaddr_in)) {
> -		RTS_PID_PRINTF("prefix mask sin_len too small: %d", mask_sa->sin_len);
> -		return (EINVAL);
> -	}
> +
> +	if ((mask_sa != NULL) && mask_sa->sin_len < sizeof(struct sockaddr_in)) {
> +		/*
> +		 * Some older routing software encode mask length into the
> +		 * sin_len, thus resulting in "truncated" sockaddr.
> +		 */
> +		int len = mask_sa->sin_len - offsetof(struct sockaddr_in, sin_addr);
> +		if (len >= 0) {
> +			mask.s_addr = 0;
> +			if (len > sizeof(struct in_addr))
> +				len = sizeof(struct in_addr);
> +			memcpy(&mask, &mask_sa->sin_addr, len);
> +		} else {
> +			RTS_PID_PRINTF("prefix mask sin_len too small: %d", mask_sa->sin_len);
> +			return (EINVAL);
> +		}
> +	} else
> +		mask.s_addr = mask_sa ? mask_sa->sin_addr.s_addr : INADDR_BROADCAST;
> +
> +	dst.s_addr = htonl(ntohl(dst_sa->sin_addr.s_addr) & ntohl(mask.s_addr));
> +
> +	/* Construct new "clean" dst/mask sockaddresses */
> +	if ((dst_sa = (struct sockaddr_in *)alloc_sockaddr_aligned(lb, sa_len)) == NULL)
> +		return (ENOBUFS);
>   	fill_sockaddr_inet(dst_sa, dst);
> +	info->rti_info[RTAX_DST] = (struct sockaddr *)dst_sa;
>   
> -	if (mask.s_addr != INADDR_BROADCAST)
> +	if (mask.s_addr != INADDR_BROADCAST) {
> +		if ((mask_sa = (struct sockaddr_in *)alloc_sockaddr_aligned(lb, sa_len)) == NULL)
> +			return (ENOBUFS);
>   		fill_sockaddr_inet(mask_sa, mask);
> -	else
> +		info->rti_info[RTAX_NETMASK] = (struct sockaddr *)mask_sa;
> +	} else
>   		remove_netmask(info);
>   
>   	/* Check gateway */
>   	if (info->rti_info[RTAX_GATEWAY] != NULL)
> -		return (cleanup_xaddrs_gateway(info));
> +		return (cleanup_xaddrs_gateway(info, lb));
>   
>   	return (0);
>   }
> @@ -1456,43 +1516,66 @@ cleanup_xaddrs_inet(struct rt_addrinfo *info)
>   
>   #ifdef INET6
>   static int
> -cleanup_xaddrs_inet6(struct rt_addrinfo *info)
> +cleanup_xaddrs_inet6(struct rt_addrinfo *info, struct linear_buffer *lb)
>   {
> +	struct sockaddr *sa;
>   	struct sockaddr_in6 *dst_sa, *mask_sa;
> -	struct in6_addr mask;
> +	struct in6_addr mask, *dst;
> +	const int sa_len = sizeof(struct sockaddr_in6);
>   
>   	/* Check & fixup dst/netmask combination first */
>   	dst_sa = (struct sockaddr_in6 *)info->rti_info[RTAX_DST];
>   	mask_sa = (struct sockaddr_in6 *)info->rti_info[RTAX_NETMASK];
>   
> -	mask = mask_sa ? mask_sa->sin6_addr : in6mask128;
> -	IN6_MASK_ADDR(&dst_sa->sin6_addr, &mask);
> -
>   	if (dst_sa->sin6_len < sizeof(struct sockaddr_in6)) {
>   		RTS_PID_PRINTF("prefix dst sin6_len too small: %d", dst_sa->sin6_len);
>   		return (EINVAL);
>   	}
> +
>   	if (mask_sa && mask_sa->sin6_len < sizeof(struct sockaddr_in6)) {
> -		RTS_PID_PRINTF("rtsock: prefix mask sin6_len too small: %d", mask_sa->sin6_len);
> -		return (EINVAL);
> -	}
> -	fill_sockaddr_inet6(dst_sa, &dst_sa->sin6_addr, 0);
> +		/*
> +		 * Some older routing software encode mask length into the
> +		 * sin6_len, thus resulting in "truncated" sockaddr.
> +		 */
> +		int len = mask_sa->sin6_len - offsetof(struct sockaddr_in6, sin6_addr);
> +		if (len >= 0) {
> +			bzero(&mask, sizeof(mask));
> +			if (len > sizeof(struct in6_addr))
> +				len = sizeof(struct in6_addr);
> +			memcpy(&mask, &mask_sa->sin6_addr, len);
> +		} else {
> +			RTS_PID_PRINTF("rtsock: prefix mask sin6_len too small: %d", mask_sa->sin6_len);
> +			return (EINVAL);
> +		}
> +	} else
> +		mask = mask_sa ? mask_sa->sin6_addr : in6mask128;
>   
> -	if (!IN6_ARE_ADDR_EQUAL(&mask, &in6mask128))
> -		fill_sockaddr_inet6(mask_sa, &mask, 0);
> -	else
> +	dst = &dst_sa->sin6_addr;
> +	IN6_MASK_ADDR(dst, &mask);
> +
> +	if ((sa = alloc_sockaddr_aligned(lb, sa_len)) == NULL)
> +		return (ENOBUFS);
> +	fill_sockaddr_inet6((struct sockaddr_in6 *)sa, dst, 0);
> +	info->rti_info[RTAX_DST] = sa;
> +
> +	if (!IN6_ARE_ADDR_EQUAL(&mask, &in6mask128)) {
> +		if ((sa = alloc_sockaddr_aligned(lb, sa_len)) == NULL)
> +			return (ENOBUFS);
> +		fill_sockaddr_inet6((struct sockaddr_in6 *)sa, &mask, 0);
> +		info->rti_info[RTAX_NETMASK] = sa;
> +	} else
>   		remove_netmask(info);
>   
>   	/* Check gateway */
>   	if (info->rti_info[RTAX_GATEWAY] != NULL)
> -		return (cleanup_xaddrs_gateway(info));
> +		return (cleanup_xaddrs_gateway(info, lb));
>   
>   	return (0);
>   }
>   #endif
>   
>   static int
> -cleanup_xaddrs(struct rt_addrinfo *info)
> +cleanup_xaddrs(struct rt_addrinfo *info, struct linear_buffer *lb)
>   {
>   	int error = EAFNOSUPPORT;
>   
> @@ -1511,12 +1594,12 @@ cleanup_xaddrs(struct rt_addrinfo *info)
>   	switch (info->rti_info[RTAX_DST]->sa_family) {
>   #ifdef INET
>   	case AF_INET:
> -		error = cleanup_xaddrs_inet(info);
> +		error = cleanup_xaddrs_inet(info, lb);
>   		break;
>   #endif
>   #ifdef INET6
>   	case AF_INET6:
> -		error = cleanup_xaddrs_inet6(info);
> +		error = cleanup_xaddrs_inet6(info, lb);
>   		break;
>   #endif
>   	}
>



More information about the dev-commits-src-main mailing list