summaryrefslogtreecommitdiff
path: root/accel-pppd/ipv6
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ipv6')
-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);