diff options
Diffstat (limited to 'accel-pppd/ipv6')
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6.c | 80 | ||||
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6.h | 1 | ||||
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6_packet.c | 54 |
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); |
