summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Fedoryshchenko <denys.f@collabora.com>2026-09-07 22:08:04 +0300
committerDenys Fedoryshchenko <denys.f@collabora.com>2026-09-07 22:08:04 +0300
commit0648072b19b509fa4ca90664344371d1cfa01935 (patch)
treeaf6ee6a544cb7e9016f0cb3838b71f40e9389d83
parentc414f0a725a16cbd4c7063ff221dabb420b96df5 (diff)
downloadaccel-ppp-0648072b19b509fa4ca90664344371d1cfa01935.tar.gz
accel-ppp-0648072b19b509fa4ca90664344371d1cfa01935.zip
dhcpv6: reject malformed options and bound relay replies
Reject duplicate Rapid-Commit/AFTR options, validate AFTR labels, and limit relay nesting. Measure reply space from the allocation, propagate allocation failure through reply builders, and encode relay lengths correctly. Adapted from Ritika Chopra's accel-ppp-ng PR #40, T8473/T8474/T8475. Upstream already has the status, AFTR-printing and inner relay bounds fixes. Co-authored-by: Ritika Chopra <r.chopra@vyos.io>
-rw-r--r--accel-pppd/ipv6/dhcpv6.c80
-rw-r--r--accel-pppd/ipv6/dhcpv6.h1
-rw-r--r--accel-pppd/ipv6/dhcpv6_packet.c54
3 files changed, 115 insertions, 20 deletions
diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c
index a0b159cf..9b957f0f 100644
--- a/accel-pppd/ipv6/dhcpv6.c
+++ b/accel-pppd/ipv6/dhcpv6.c
@@ -205,7 +205,7 @@ static void insert_dp_routes(struct ap_session *ses, struct dhcpv6_pd *pd, struc
pd->dp_active = 1;
}
-static void insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, int code)
+static int insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, int code)
{
struct dhcpv6_option *opt1;
struct dhcpv6_opt_status *status;
@@ -215,11 +215,15 @@ static void insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt,
else
opt1 = dhcpv6_option_alloc(pkt, D6_OPTION_STATUS_CODE, sizeof(struct dhcpv6_opt_status) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ return -1;
+
status = (struct dhcpv6_opt_status *)opt1->hdr;
status->code = htons(code);
+ return 0;
}
-static void insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt)
+static int insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt)
{
struct dhcpv6_option *opt1;
int i, j, dns_count;
@@ -236,21 +240,28 @@ static void insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt)
dns, MAX_DNS_COUNT);
if (dns_count) {
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, dns_count * sizeof(addr));
+ if (!opt1)
+ return -1;
for (j = 0, addr_ptr = opt1->hdr->data; j < dns_count; j++, addr_ptr += sizeof(addr))
memcpy(addr_ptr, dns + j, sizeof(addr));
}
} else if (code == D6_OPTION_DOMAIN_LIST) {
if (conf_dnssl_size) {
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DOMAIN_LIST, conf_dnssl_size);
+ if (!opt1)
+ return -1;
memcpy(opt1->hdr->data, conf_dnssl, conf_dnssl_size);
}
} else if (code == D6_OPTION_AFTR_NAME) {
if (conf_aftr_gw_size) {
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_AFTR_NAME, conf_aftr_gw_size);
+ if (!opt1)
+ return -1;
memcpy(opt1->hdr->data, conf_aftr_gw, conf_aftr_gw_size);
}
}
}
+ return 0;
}
static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, int code)
@@ -277,6 +288,8 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
continue;
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_NA, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ goto out;
memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
ia_na = (struct dhcpv6_opt_ia_na *)opt1->hdr;
@@ -284,9 +297,11 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
ia_na->T2 = conf_pref_lifetime == -1 ? -1 : htonl((conf_pref_lifetime * 4) / 5);
if (req->hdr->type == D6_RENEW && pd->addr_iaid != ia_na->iaid) {
- insert_status(reply, opt1, D6_STATUS_NoBinding);
+ if (insert_status(reply, opt1, D6_STATUS_NoBinding))
+ goto out;
} else if (list_empty(&ses->ipv6->addr_list) || f) {
- insert_status(reply, opt1, D6_STATUS_NoAddrsAvail);
+ if (insert_status(reply, opt1, D6_STATUS_NoAddrsAvail))
+ goto out;
} else {
if (req->hdr->type == D6_REQUEST || req->rapid_commit)
@@ -296,6 +311,8 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
list_for_each_entry(a, &ses->ipv6->addr_list, entry) {
opt2 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAADDR, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt2)
+ goto out;
ia_addr = (struct dhcpv6_opt_ia_addr *)opt2->hdr;
build_ip6_addr(a, ses->ipv6->peer_intf_id, &addr);
@@ -342,13 +359,16 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
if (!f1) {
opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAADDR, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt3)
+ goto out;
memcpy(opt3->hdr->data, opt2->hdr->data, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
ia_addr = (struct dhcpv6_opt_ia_addr *)opt3->hdr;
ia_addr->pref_lifetime = 0;
ia_addr->valid_lifetime = 0;
- insert_status(reply, opt3, D6_STATUS_NotOnLink);
+ if (insert_status(reply, opt3, D6_STATUS_NotOnLink))
+ goto out;
}
}
}
@@ -363,6 +383,8 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
continue;
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_PD, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ goto out;
memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
ia_na = (struct dhcpv6_opt_ia_na *)opt1->hdr;
@@ -380,9 +402,11 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
}
if ((req->hdr->type == D6_RENEW) && pd->dp_iaid != ia_na->iaid) {
- insert_status(reply, opt1, D6_STATUS_NoBinding);
+ if (insert_status(reply, opt1, D6_STATUS_NoBinding))
+ goto out;
} else if (!ses->ipv6_dp || list_empty(&ses->ipv6_dp->prefix_list) || f2) {
- insert_status(reply, opt1, D6_STATUS_NoPrefixAvail);
+ if (insert_status(reply, opt1, D6_STATUS_NoPrefixAvail))
+ goto out;
} else {
if (req->hdr->type == D6_REQUEST || req->rapid_commit) {
@@ -395,6 +419,8 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
list_for_each_entry(a, &ses->ipv6_dp->prefix_list, entry) {
opt2 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAPREFIX, sizeof(*ia_prefix) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt2)
+ goto out;
ia_prefix = (struct dhcpv6_opt_ia_prefix *)opt2->hdr;
memcpy(&ia_prefix->prefix, &a->addr, sizeof(a->addr));
@@ -424,12 +450,15 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
if (!f1) {
opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAPREFIX, sizeof(*ia_prefix) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt3)
+ goto out;
memcpy(opt3->hdr->data, opt2->hdr->data, sizeof(*ia_prefix) - sizeof(struct dhcpv6_opt_hdr));
ia_prefix = (struct dhcpv6_opt_ia_prefix *)opt3->hdr;
ia_prefix->pref_lifetime = 0;
ia_prefix->valid_lifetime = 0;
- insert_status(reply, opt3, D6_STATUS_NotOnLink);
+ if (insert_status(reply, opt3, D6_STATUS_NotOnLink))
+ goto out;
}
}
}
@@ -444,21 +473,27 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
continue;
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_TA, sizeof(struct dhcpv6_opt_ia_ta) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ goto out;
memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
- insert_status(reply, opt1, D6_STATUS_NoAddrsAvail);
+ if (insert_status(reply, opt1, D6_STATUS_NoAddrsAvail))
+ goto out;
// Option Request
} else if (ntohs(opt->hdr->code) == D6_OPTION_ORO) {
- insert_oro(reply, opt);
-
- } else if (ntohs(opt->hdr->code) == D6_OPTION_RAPID_COMMIT) {
- if (req->hdr->type == D6_SOLICIT)
- dhcpv6_option_alloc(reply, D6_OPTION_RAPID_COMMIT, 0);
+ if (insert_oro(reply, opt))
+ goto out;
}
}
+ if (req->hdr->type == D6_SOLICIT && req->rapid_commit &&
+ !dhcpv6_option_alloc(reply, D6_OPTION_RAPID_COMMIT, 0))
+ goto out;
+
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_PREFERENCE, 1);
+ if (!opt1)
+ goto out;
*(uint8_t *)opt1->hdr->data = 255;
//insert_status(reply, NULL, D6_STATUS_Success);
@@ -472,6 +507,7 @@ static void dhcpv6_send_reply(struct dhcpv6_packet *req, struct dhcpv6_pd *pd, i
net->sendto(pd->hnd.fd, reply->hdr, reply->endptr - (void *)reply->hdr, 0, (struct sockaddr *)&req->addr, sizeof(req->addr));
+out:
dhcpv6_packet_free(reply);
}
@@ -496,6 +532,8 @@ static void dhcpv6_send_reply2(struct dhcpv6_packet *req, struct dhcpv6_pd *pd,
// IA_NA
if (ntohs(opt->hdr->code) == D6_OPTION_IA_NA) {
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_NA, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ goto out;
memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
ia_na = (struct dhcpv6_opt_ia_na *)opt1->hdr;
@@ -525,6 +563,8 @@ static void dhcpv6_send_reply2(struct dhcpv6_packet *req, struct dhcpv6_pd *pd,
}
opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAADDR, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt3)
+ goto out;
memcpy(opt3->hdr->data, opt2->hdr->data, sizeof(*ia_addr) - sizeof(struct dhcpv6_opt_hdr));
ia_addr = (struct dhcpv6_opt_ia_addr *)opt3->hdr;
@@ -549,6 +589,8 @@ static void dhcpv6_send_reply2(struct dhcpv6_packet *req, struct dhcpv6_pd *pd,
// IA_PD
} else if (ntohs(opt->hdr->code) == D6_OPTION_IA_PD) {
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_IA_PD, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt1)
+ goto out;
memcpy(opt1->hdr + 1, opt->hdr + 1, ntohs(opt1->hdr->len));
ia_na = (struct dhcpv6_opt_ia_na *)opt1->hdr;
@@ -589,6 +631,8 @@ static void dhcpv6_send_reply2(struct dhcpv6_packet *req, struct dhcpv6_pd *pd,
}
opt3 = dhcpv6_nested_option_alloc(reply, opt1, D6_OPTION_IAPREFIX, sizeof(*ia_prefix) - sizeof(struct dhcpv6_opt_hdr));
+ if (!opt3)
+ goto out;
memcpy(opt3->hdr->data, opt2->hdr->data, sizeof(*ia_prefix) - sizeof(struct dhcpv6_opt_hdr));
ia_prefix = (struct dhcpv6_opt_ia_prefix *)opt3->hdr;
@@ -609,11 +653,15 @@ static void dhcpv6_send_reply2(struct dhcpv6_packet *req, struct dhcpv6_pd *pd,
f2 = 1;
}
// Option Request
- } else if (ntohs(opt->hdr->code) == D6_OPTION_ORO)
- insert_oro(reply, opt);
+ } else if (ntohs(opt->hdr->code) == D6_OPTION_ORO) {
+ if (insert_oro(reply, opt))
+ goto out;
+ }
}
opt1 = dhcpv6_option_alloc(reply, D6_OPTION_PREFERENCE, 1);
+ if (!opt1)
+ goto out;
*(uint8_t *)opt1->hdr->data = 255;
//insert_status(reply, NULL, D6_STATUS_Success);
diff --git a/accel-pppd/ipv6/dhcpv6.h b/accel-pppd/ipv6/dhcpv6.h
index 2326e639..7dfc71a1 100644
--- a/accel-pppd/ipv6/dhcpv6.h
+++ b/accel-pppd/ipv6/dhcpv6.h
@@ -10,6 +10,7 @@
#define DHCPV6_CLIENT_PORT 546
#define DHCPV6_SERV_PORT 547
+#define DHCPV6_HOP_COUNT_LIMIT 8
#define D6_OPTION_CLIENTID 1
#define D6_OPTION_SERVERID 2
diff --git a/accel-pppd/ipv6/dhcpv6_packet.c b/accel-pppd/ipv6/dhcpv6_packet.c
index f1c168b6..9190c505 100644
--- a/accel-pppd/ipv6/dhcpv6_packet.c
+++ b/accel-pppd/ipv6/dhcpv6_packet.c
@@ -63,6 +63,26 @@ static struct dict_option known_options[] = {
{ 0 }
};
+/* RFC 6334: one uncompressed, terminated DNS name. */
+static int validate_aftr_name(const uint8_t *data, unsigned int len)
+{
+ unsigned int offset = 0, label_len;
+
+ if (len <= 3 || len > 255)
+ return 0;
+
+ while (offset < len) {
+ label_len = data[offset++];
+ if (!label_len)
+ return offset > 1 && offset == len;
+ if (label_len > 63 || label_len > len - offset)
+ return 0;
+ offset += label_len;
+ }
+
+ return 0;
+}
+
static void *parse_option(void *ptr, void *endptr, struct list_head *opt_list)
{
struct dict_option *dopt;
@@ -75,6 +95,12 @@ static void *parse_option(void *ptr, void *endptr, struct list_head *opt_list)
return NULL;
}
+ if (opth->code == htons(D6_OPTION_AFTR_NAME) &&
+ !validate_aftr_name(opth->data, ntohs(opth->len))) {
+ log_warn("dhcpv6: invalid AFTR-Name option\n");
+ return NULL;
+ }
+
opt = _malloc(sizeof(*opt));
if (!opt) {
log_emerg("out of memory\n");
@@ -117,6 +143,7 @@ struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size)
struct dhcpv6_relay *rel;
struct dhcpv6_relay_hdr *rhdr;
struct dhcpv6_msg_hdr *inner_hdr;
+ int aftr_name_seen = 0, relay_depth = 0;
void *ptr, *endptr, *relay_end, *inner_end;
if (size < sizeof(struct dhcpv6_msg_hdr)) {
@@ -141,6 +168,10 @@ struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size)
endptr = ((void *)pkt->hdr) + size;
while (pkt->hdr->type == D6_RELAY_FORW) {
+ if (relay_depth++ >= DHCPV6_HOP_COUNT_LIMIT) {
+ log_warn("dhcpv6: relay nesting limit exceeded\n");
+ goto error;
+ }
rhdr = (struct dhcpv6_relay_hdr *)pkt->hdr;
if (((void *)rhdr) + sizeof(*rhdr) > endptr) {
log_warn("dhcpv6: invalid packet received\n");
@@ -205,8 +236,18 @@ struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size)
pkt->clientid = ptr;
else if (opth->code == htons(D6_OPTION_SERVERID))
pkt->serverid = ptr;
- else if (opth->code == htons(D6_OPTION_RAPID_COMMIT))
+ else if (opth->code == htons(D6_OPTION_AFTR_NAME)) {
+ if (aftr_name_seen++) {
+ log_warn("dhcpv6: duplicate AFTR-Name option\n");
+ goto error;
+ }
+ } else if (opth->code == htons(D6_OPTION_RAPID_COMMIT)) {
+ if (pkt->rapid_commit || opth->len) {
+ log_warn("dhcpv6: invalid or duplicate Rapid-Commit option\n");
+ goto error;
+ }
pkt->rapid_commit = 1;
+ }
ptr = parse_option(ptr, endptr, &pkt->opt_list);
if (!ptr)
@@ -224,7 +265,8 @@ struct dhcpv6_option *dhcpv6_option_alloc(struct dhcpv6_packet *pkt, int code, i
{
struct dhcpv6_option *opt;
- if ((void *)pkt->hdr->data + BUF_SIZE - pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + len)
+ if (len < 0 || len > BUF_SIZE ||
+ (char *)(pkt + 1) + BUF_SIZE - (char *)pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
return NULL;
opt = _malloc(sizeof(*opt));
@@ -251,7 +293,8 @@ struct dhcpv6_option *dhcpv6_nested_option_alloc(struct dhcpv6_packet *pkt, stru
{
struct dhcpv6_option *opt;
- if ((void *)pkt->hdr->data + BUF_SIZE - pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + len)
+ if (len < 0 || len > BUF_SIZE ||
+ (char *)(pkt + 1) + BUF_SIZE - (char *)pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
return NULL;
opt = _malloc(sizeof(*opt));
@@ -297,7 +340,7 @@ void dhcpv6_fill_relay_info(struct dhcpv6_packet *pkt)
memcpy(&rhdr->peer_addr, &rel->peer_addr, sizeof(rhdr->peer_addr));
opt = (struct dhcpv6_opt_hdr *)rhdr->data;
opt->code = htons(D6_OPTION_RELAY_MSG);
- opt->len = (uint8_t *)pkt->endptr - rhdr->data;
+ opt->len = htons((uint8_t *)pkt->endptr - opt->data);
}
rel = list_entry(pkt->relay_list.next, typeof(*rel), entry);
@@ -325,6 +368,9 @@ struct dhcpv6_packet *dhcpv6_packet_alloc_reply(struct dhcpv6_packet *req, int t
while (!list_empty(&req->relay_list)) {
rel = list_entry(req->relay_list.next, typeof(*rel), entry);
+ if ((char *)(pkt + 1) + BUF_SIZE - (char *)pkt->hdr <
+ sizeof(struct dhcpv6_relay_hdr) + sizeof(struct dhcpv6_opt_hdr) + sizeof(*pkt->hdr))
+ goto error;
rel->hdr = (void *)pkt->hdr;
pkt->hdr = (void *)rel->hdr + sizeof(struct dhcpv6_relay_hdr) + sizeof(struct dhcpv6_opt_hdr);
list_move_tail(&rel->entry, &pkt->relay_list);