Prior to version 2.21, the `radvdump` utility shipped with radvd contains a stack buffer overflow in the Route Information option parser.
When processing a crafted ICMPv6 Router Advertisement, `print_ff()` copies up to 2032 bytes from attacker-controlled packet data into a 16-byte `struct in6_addr` on the stack, overflowing by up to 2016 bytes. Signed-off-by: Roland Kovacs <[email protected]> --- .../radvd/files/CVE-2026-48715.patch | 213 ++++++++++++++++++ .../radvd/files/CVE-2026-48715_dep.patch | 106 +++++++++ .../recipes-daemons/radvd/radvd_2.19.bb | 2 + 3 files changed, 321 insertions(+) create mode 100644 meta-networking/recipes-daemons/radvd/files/CVE-2026-48715.patch create mode 100644 meta-networking/recipes-daemons/radvd/files/CVE-2026-48715_dep.patch diff --git a/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715.patch b/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715.patch new file mode 100644 index 0000000000..e15ae4aaba --- /dev/null +++ b/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715.patch @@ -0,0 +1,213 @@ +From 44c72b548ed11da60ff3b8d43711e74d64032526 Mon Sep 17 00:00:00 2001 +From: "Robin H. Johnson" <[email protected]> +Date: Sun, 17 May 2026 15:52:03 -0700 +Subject: [PATCH] feat: harden radvdump (CVE-2026-48715) + +- drop root after startup & validate +- ensure buffers are zeroed between parsed packets +- Jumbo packets were truncated in parsing due to outdated MSG_SIZE_RECV +- Route Information option length field was not sufficiently validated + +Thanks to @TristanInSec for finding the Route Information +length validation issue. + +Signed-off-by: Robin H. Johnson <[email protected]> +Reference: https://github.com/radvd-project/radvd/security/advisories/GHSA-52px-gh9p-m379 + +CVE: CVE-2026-48715 +Upstream-Status: Backport [https://github.com/radvd-project/radvd/commit/068bde13e3fd6a5fcdb6859e6a2acd293a325dc5] + +Changes: Added the missing '-u username' argument to the help output. + +Signed-off-by: Roland Kovacs <[email protected]> +--- + radvdump.8.man | 10 +++++++ + radvdump.c | 78 +++++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 78 insertions(+), 10 deletions(-) + +diff --git a/radvdump.8.man b/radvdump.8.man +index ea9932c..25dbad8 100644 +--- a/radvdump.8.man ++++ b/radvdump.8.man +@@ -20,6 +20,7 @@ radvdump \- dump router advertisements + .B radvdump + .B "[ \-vhfe ]" + .BI "[ \-d " debuglevel " ]" ++.BI "[ \-u " username " ]" + + .SH DESCRIPTION + .B radvdump +@@ -62,6 +63,15 @@ debugging level of 0 completely turns off debugging. + + The default debugging level is 0. + ++.TP ++.BR "\-u " username, " \-\-username " username ++If specified, drops root privileges and changes user ID to ++.I username ++and group ID to the primary group of ++.IR username . ++This is recommended for security reasons. ++Defaults to "nobody", pass "" or "root" to not drop privileges by default. ++ + .SH FILES + + .nf +diff --git a/radvdump.c b/radvdump.c +index 0a20272..a939970 100644 +--- a/radvdump.c ++++ b/radvdump.c +@@ -17,11 +17,18 @@ + #include "config.h" + #include "includes.h" + +-static char usage_str[] = "[-vhfe] [-d level]"; ++static char usage_str[] = "[-vhfe] [-d level] [-u username]"; + + #ifdef HAVE_GETOPT_LONG +-struct option prog_opt[] = {{"debug", 1, 0, 'd'}, {"file-format", 0, 0, 'f'}, {"exclude-defaults", 0, 0, 'e'}, +- {"version", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {NULL, 0, 0, 0}}; ++struct option prog_opt[] = { ++ {"debug", 1, 0, 'd'}, ++ {"username", 1, 0, 'u'}, ++ {"file-format", 0, 0, 'f'}, ++ {"exclude-defaults", 0, 0, 'e'}, ++ {"version", 0, 0, 'v'}, ++ {"help", 0, 0, 'h'}, ++ {NULL, 0, 0, 0} ++}; + #endif + + int sock = -1; +@@ -39,12 +46,14 @@ int main(int argc, char *argv[]) + int opt_idx; + #endif + char const *pname = ((pname = strrchr(argv[0], '/')) != NULL) ? pname + 1 : argv[0]; ++ char username[256]; ++ strncpy(username, "nobody", 255); + + /* parse args */ + #ifdef HAVE_GETOPT_LONG +- while ((c = getopt_long(argc, argv, "d:fehv", prog_opt, &opt_idx)) > 0) ++ while ((c = getopt_long(argc, argv, "d:fehvu:", prog_opt, &opt_idx)) > 0) + #else +- while ((c = getopt(argc, argv, "d:fehv")) > 0) ++ while ((c = getopt(argc, argv, "d:fehvu:")) > 0) + #endif + { + switch (c) { +@@ -59,6 +68,9 @@ int main(int argc, char *argv[]) + case 'v': + version(); + break; ++ case 'u': ++ strncpy(username, optarg, 255); ++ break; + case 'h': + usage(pname); + #ifdef HAVE_GETOPT_LONG +@@ -83,12 +95,29 @@ int main(int argc, char *argv[]) + exit(1); + } + ++ if(strncmp(username, "", 255) == 0 || strncmp(username, "-", 255) == 0) { ++ // Must opt out of security ++ } else { ++ if (drop_root_privileges(username) < 0) { ++ perror("drop_root_privileges"); ++ flog(LOG_ERR, "unable to drop root privileges"); ++ exit(1); ++ } ++ dlog(LOG_DEBUG, 5, "running as user: %s/%d", username, geteuid()); ++ } ++ ++#define _MSG_SIZE MSG_SIZE_RECV ++#define _CHDR_SIZE CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) + for (;;) { +- unsigned char msg[MSG_SIZE_RECV]; ++ unsigned char msg[_MSG_SIZE]; + int hoplimit = 0; + struct in6_pktinfo *pkt_info = NULL; + struct sockaddr_in6 rcv_addr; +- unsigned char chdr[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))]; ++ unsigned char chdr[_CHDR_SIZE]; ++ // safety; completely zero buffers for each receive ++ memset(&msg, 0, _MSG_SIZE); ++ memset(&chdr, 0, _CHDR_SIZE); ++ + int len = recv_rs_ra(sock, msg, &rcv_addr, &pkt_info, &hoplimit, chdr); + if (len > 0) { + struct icmp6_hdr *icmph; +@@ -120,7 +149,7 @@ int main(int argc, char *argv[]) + } else if (icmph->icmp6_type == ND_ROUTER_ADVERT) + print_ff(msg, len, &rcv_addr, hoplimit, (unsigned int)pkt_info->ipi6_ifindex, edefs); + } else if (len == 0) { +- flog(LOG_ERR, "received zero lenght packet"); ++ flog(LOG_ERR, "received zero length packet"); + exit(1); + } else { + flog(LOG_ERR, "recv_rs_ra: %s", strerror(errno)); +@@ -202,7 +231,8 @@ static void print_ff(unsigned char *msg, int len, struct sockaddr_in6 *addr, int + flog(LOG_ERR, "option length greater than total" + " length in RA (type %d, optlen %d, len %d)", + (int)*opt_str, optlen, len); +- break; ++ // Do not process anything further in this RA. ++ goto end_of_interface; + } + + switch (*opt_str) { +@@ -289,7 +319,8 @@ static void print_ff(unsigned char *msg, int len, struct sockaddr_in6 *addr, int + flog(LOG_ERR, "option length greater than total" + " length in RA (type %d, optlen %d, len %d)", + (int)*opt_str, optlen, orig_len); +- break; ++ // Do not process anything further in this RA. ++ goto end_of_interface; + } + + switch (*opt_str) { +@@ -345,6 +376,32 @@ static void print_ff(unsigned char *msg, int len, struct sockaddr_in6 *addr, int + } else { + struct in6_addr addr; + memset(&addr, 0, sizeof(addr)); ++ /* RFC4191 section 2.3 (reformatted) ++ Length 8-bit unsigned integer. ++ ++ The length of the option (including the Type and Length ++ fields) in units of 8 octets. ++ ++ The Length field is 1, 2, or 3 depending on the Prefix ++ Length. ++ ++ If Prefix Length is greater than 64, then Length must be 3. ++ ++ If Prefix Length is greater than 0, then Length must be 2 or ++ 3. ++ ++ If Prefix Length is zero, then Length must be 1, 2, or 3. ++ */ ++ if ( ++ (rinfo->nd_opt_ri_len > 3) ++ || (rinfo->nd_opt_ri_len == 0) ++ || (rinfo->nd_opt_ri_prefix_len > 64 && rinfo->nd_opt_ri_len < 3) ++ || (rinfo->nd_opt_ri_prefix_len > 0 && rinfo->nd_opt_ri_len < 1) ++ ) { ++ flog(LOG_ERR, "Invalid Route Information option length %d from %s", rinfo->nd_opt_ri_len, addr_str); ++ printf("# Invalid Route Information option length %d, per RFC4191 section 2.3 ", rinfo->nd_opt_ri_len); ++ break; ++ } + if (rinfo->nd_opt_ri_len > 1) + memcpy(&addr, &rinfo->nd_opt_ri_prefix, (rinfo->nd_opt_ri_len - 1) * 8); + addrtostr(&addr, prefix_str, sizeof(prefix_str)); +@@ -444,6 +501,7 @@ static void print_ff(unsigned char *msg, int len, struct sockaddr_in6 *addr, int + opt_str += optlen; + } + ++end_of_interface: + printf("}; # End of interface definition\n"); + + fflush(stdout); +-- +2.34.1 + diff --git a/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715_dep.patch b/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715_dep.patch new file mode 100644 index 0000000000..eaabb0f243 --- /dev/null +++ b/meta-networking/recipes-daemons/radvd/files/CVE-2026-48715_dep.patch @@ -0,0 +1,106 @@ +From 3b7602ee0da3c8313e147e539e0efc01dd34a0df Mon Sep 17 00:00:00 2001 +From: "Robin H. Johnson" <[email protected]> +Date: Sun, 17 May 2026 16:21:39 -0700 +Subject: [PATCH] refactor: make drop_root_privileges reusable + +Signed-off-by: Robin H. Johnson <[email protected]> + +Upstream-Status: Backport [https://github.com/radvd-project/radvd/commit/11c92dafc392dab1843964316950e297fbd35d33] + +Note: dependency of CVE-2026-48715 + +Signed-off-by: Roland Kovacs <[email protected]> +--- + radvd.c | 16 ---------------- + radvd.h | 1 + + util.c | 31 +++++++++++++++++++++++++++++++ + 3 files changed, 32 insertions(+), 16 deletions(-) + +diff --git a/radvd.c b/radvd.c +index 22255b1..e807121 100644 +--- a/radvd.c ++++ b/radvd.c +@@ -79,7 +79,6 @@ static volatile int sigterm_received = 0; + static volatile int sigusr1_received = 0; + + static int check_conffile_perm(const char *, const char *); +-static int drop_root_privileges(const char *); + static int open_and_lock_pid_file(char const *daemon_pid_file_ident); + static int write_pid_file(char const *daemon_pid_file_ident, pid_t pid); + static pid_t daemonp(char const *daemon_pid_file_ident); +@@ -841,21 +840,6 @@ static void reset_prefix_lifetimes_foo(struct Interface *iface, void *data) + + static void reset_prefix_lifetimes(struct Interface *ifaces) { for_each_iface(ifaces, reset_prefix_lifetimes_foo, 0); } + +-static int drop_root_privileges(const char *username) +-{ +- struct passwd *pw = getpwnam(username); +- if (pw) { +- if (initgroups(username, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) { +- flog(LOG_ERR, "Couldn't change to '%.32s' uid=%d gid=%d", username, pw->pw_uid, pw->pw_gid); +- return -1; +- } +- } else { +- flog(LOG_ERR, "Couldn't find user '%.32s'", username); +- return -1; +- } +- return 0; +-} +- + static int check_conffile_perm(const char *username, const char *conf_file) + { + FILE *fp = fopen(conf_file, "r"); +diff --git a/radvd.h b/radvd.h +index 2a61c75..4276bbe 100644 +--- a/radvd.h ++++ b/radvd.h +@@ -341,6 +341,7 @@ struct safe_buffer_list *new_safe_buffer_list(void); + void safe_buffer_list_free(struct safe_buffer_list *sbl); + struct safe_buffer_list *safe_buffer_list_append(struct safe_buffer_list *sbl); + void safe_buffer_list_to_safe_buffer(struct safe_buffer_list *sbl, struct safe_buffer *sb); ++int drop_root_privileges(const char *); + + /* privsep.c */ + int privsep_interface_curhlim(const char *iface, uint32_t hlim); +diff --git a/util.c b/util.c +index b160cc9..012f16b 100644 +--- a/util.c ++++ b/util.c +@@ -283,3 +283,34 @@ struct in6_addr get_prefix6(struct in6_addr const *addr, struct in6_addr const * + + return prefix; + } ++ ++int drop_root_privileges(const char *username) ++{ ++ struct passwd *pw = getpwnam(username); ++ if (pw) { ++ if (initgroups(username, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) { ++ flog(LOG_ERR, "Couldn't change to '%.32s' uid=%d gid=%d", username, pw->pw_uid, pw->pw_gid); ++ return -1; ++ } ++ // If the target 0 was zero; then the later checks would return false ++ // positives. This can take place if explicitly running as '-u root'. ++ if(pw->pw_uid == 0) { ++ return 0; ++ } ++ // Validate that we cannot get root again ++ if (setuid(0) != -1) { ++ flog(LOG_ERR, "Drop root privileged unsuccessful, setuid regained root"); ++ return -1; ++ } ++ ++ if (seteuid(0) != -1) { ++ flog(LOG_ERR, "Drop root privileged unsuccessful, seteuid regained root"); ++ return -1; ++ } ++ ++ } else { ++ flog(LOG_ERR, "Couldn't find user '%.32s'", username); ++ return -1; ++ } ++ return 0; ++} +-- +2.34.1 + diff --git a/meta-networking/recipes-daemons/radvd/radvd_2.19.bb b/meta-networking/recipes-daemons/radvd/radvd_2.19.bb index a9b5f79424..7f73143649 100644 --- a/meta-networking/recipes-daemons/radvd/radvd_2.19.bb +++ b/meta-networking/recipes-daemons/radvd/radvd_2.19.bb @@ -21,6 +21,8 @@ SRC_URI = "http://v6web.litech.org/radvd/dist/radvd-${PV}.tar.gz \ file://radvd.default \ file://radvd.conf \ file://0001-Reverts-the-include.h-change-in-46883f8a1a02fe42040d.patch \ + file://CVE-2026-48715_dep.patch \ + file://CVE-2026-48715.patch \ " SRC_URI[sha256sum] = "c36470706fec3a9e6bed394ffea08acaff5dac647848d26b96bb9b9c65d58da0" -- 2.34.1
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#128117): https://lists.openembedded.org/g/openembedded-devel/message/128117 Mute This Topic: https://lists.openembedded.org/mt/120188062/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
