mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-07-27 07:57:27 +00:00
radvd: fix CVE-2026-48715
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 <roland.kovacs@est.tech> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
committed by
Anuj Mittal
parent
0a044f3363
commit
8cba40ff88
@@ -0,0 +1,213 @@
|
|||||||
|
From 545487bba7b8bcf447ca119f45eb6a1b8eb70dc7 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Robin H. Johnson" <robbat2@gentoo.org>
|
||||||
|
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 <robbat2@orbis-terrarum.net>
|
||||||
|
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 <roland.kovacs@est.tech>
|
||||||
|
---
|
||||||
|
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 0ac39e4..a58c655 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 bb918c8..032d705 100644
|
||||||
|
--- a/radvdump.c
|
||||||
|
+++ b/radvdump.c
|
||||||
|
@@ -23,11 +23,18 @@
|
||||||
|
#define bswap64(y) (y)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-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;
|
||||||
|
@@ -45,12 +52,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) {
|
||||||
|
@@ -65,6 +74,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
|
||||||
|
@@ -89,12 +101,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;
|
||||||
|
@@ -126,7 +155,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));
|
||||||
|
@@ -208,7 +237,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) {
|
||||||
|
@@ -320,7 +350,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) {
|
||||||
|
@@ -376,6 +407,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));
|
||||||
|
@@ -519,6 +576,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
|
||||||
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
From c0efc056a45a75977a1f400e8023dcdacab84c07 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Robin H. Johnson" <robbat2@gentoo.org>
|
||||||
|
Date: Sun, 17 May 2026 16:21:39 -0700
|
||||||
|
Subject: [PATCH] refactor: make drop_root_privileges reusable
|
||||||
|
|
||||||
|
Signed-off-by: Robin H. Johnson <robbat2@gentoo.org>
|
||||||
|
|
||||||
|
Upstream-Status: Backport [https://github.com/radvd-project/radvd/commit/11c92dafc392dab1843964316950e297fbd35d33]
|
||||||
|
|
||||||
|
Note: dependency of CVE-2026-48715
|
||||||
|
|
||||||
|
Signed-off-by: Roland Kovacs <roland.kovacs@est.tech>
|
||||||
|
---
|
||||||
|
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 3784b76..0d3fa1d 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);
|
||||||
|
@@ -862,21 +861,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 38f6533..ae4f471 100644
|
||||||
|
--- a/radvd.h
|
||||||
|
+++ b/radvd.h
|
||||||
|
@@ -381,6 +381,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 c033794..f18385e 100644
|
||||||
|
--- a/util.c
|
||||||
|
+++ b/util.c
|
||||||
|
@@ -287,3 +287,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
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ SRC_URI = "http://v6web.litech.org/radvd/dist/radvd-${PV}.tar.gz \
|
|||||||
file://volatiles.03_radvd \
|
file://volatiles.03_radvd \
|
||||||
file://radvd.default \
|
file://radvd.default \
|
||||||
file://radvd.conf \
|
file://radvd.conf \
|
||||||
|
file://CVE-2026-48715_dep.patch \
|
||||||
|
file://CVE-2026-48715.patch \
|
||||||
"
|
"
|
||||||
SRC_URI[sha256sum] = "af37c5a81d59f3bdc00d83056606ffa1810d4550beed6caa4f81181246494220"
|
SRC_URI[sha256sum] = "af37c5a81d59f3bdc00d83056606ffa1810d4550beed6caa4f81181246494220"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user