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]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to