dhcp6: Sanitize DHCPv6 IA option parsing
Sanitize code for parsing DHCPv6 IA NA and TA options and their nested Status options so that the options can be fully and properly ignored should they not be conformant to the specification. Do this by defining a proper DHCP6Option structure and sending that structure to the parsing function. The parsing function will then not manipulate either any option data pointers or their lengths in order to iterate over the current option. Needless to say, this affects a few files including the test program.
This commit is contained in:
parent
e0026dcbd2
commit
3bc424a3cc
|
@ -29,6 +29,13 @@
|
|||
#include "macro.h"
|
||||
#include "sparse-endian.h"
|
||||
|
||||
/* Common option header */
|
||||
typedef struct DHCP6Option {
|
||||
be16_t code;
|
||||
be16_t len;
|
||||
uint8_t data[];
|
||||
} _packed_ DHCP6Option;
|
||||
|
||||
typedef struct DHCP6Address DHCP6Address;
|
||||
|
||||
struct DHCP6Address {
|
||||
|
@ -76,8 +83,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
|
|||
int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
|
||||
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
|
||||
size_t *optlen, uint8_t **optvalue);
|
||||
int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
||||
DHCP6IA *ia);
|
||||
int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia);
|
||||
int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
|
||||
struct in6_addr **addrs, size_t count,
|
||||
size_t *allocated);
|
||||
|
|
|
@ -36,12 +36,6 @@
|
|||
#define DHCP6_OPTION_IA_NA_LEN 12
|
||||
#define DHCP6_OPTION_IA_TA_LEN 4
|
||||
|
||||
typedef struct DHCP6Option {
|
||||
be16_t code;
|
||||
be16_t len;
|
||||
uint8_t data[];
|
||||
} _packed_ DHCP6Option;
|
||||
|
||||
static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
|
||||
size_t optlen) {
|
||||
DHCP6Option *option = (DHCP6Option*) *buf;
|
||||
|
@ -213,11 +207,11 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
||||
DHCP6IA *ia) {
|
||||
int r;
|
||||
uint16_t opt, status;
|
||||
size_t optlen;
|
||||
int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
|
||||
uint16_t iatype, optlen;
|
||||
size_t i, len;
|
||||
int r = 0, status;
|
||||
uint16_t opt;
|
||||
size_t iaaddr_offset;
|
||||
DHCP6Address *addr;
|
||||
uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
|
||||
|
@ -225,17 +219,19 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
assert_return(ia, -EINVAL);
|
||||
assert_return(!ia->addresses, -EINVAL);
|
||||
|
||||
iatype = be16toh(iaoption->code);
|
||||
len = be16toh(iaoption->len);
|
||||
|
||||
switch (iatype) {
|
||||
case SD_DHCP6_OPTION_IA_NA:
|
||||
|
||||
if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
|
||||
sizeof(addr->iaaddr)) {
|
||||
if (len < DHCP6_OPTION_IA_NA_LEN) {
|
||||
r = -ENOBUFS;
|
||||
goto error;
|
||||
}
|
||||
|
||||
iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
|
||||
memcpy(&ia->ia_na, *buf, sizeof(ia->ia_na));
|
||||
memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
|
||||
|
||||
lt_t1 = be32toh(ia->ia_na.lifetime_t1);
|
||||
lt_t2 = be32toh(ia->ia_na.lifetime_t2);
|
||||
|
@ -250,14 +246,13 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
break;
|
||||
|
||||
case SD_DHCP6_OPTION_IA_TA:
|
||||
if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
|
||||
sizeof(addr->iaaddr)) {
|
||||
if (len < DHCP6_OPTION_IA_TA_LEN) {
|
||||
r = -ENOBUFS;
|
||||
goto error;
|
||||
}
|
||||
|
||||
iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
|
||||
memcpy(&ia->ia_ta.id, *buf, sizeof(ia->ia_ta));
|
||||
memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
|
||||
|
||||
break;
|
||||
|
||||
|
@ -267,15 +262,21 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
}
|
||||
|
||||
ia->type = iatype;
|
||||
i = iaaddr_offset;
|
||||
|
||||
*buflen -= iaaddr_offset;
|
||||
*buf += iaaddr_offset;
|
||||
while (i < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
|
||||
|
||||
while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
|
||||
if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) {
|
||||
r = -ENOBUFS;
|
||||
goto error;
|
||||
}
|
||||
|
||||
opt = be16toh(option->code);
|
||||
optlen = be16toh(option->len);
|
||||
|
||||
switch (opt) {
|
||||
case SD_DHCP6_OPTION_IAADDR:
|
||||
|
||||
addr = new0(DHCP6Address, 1);
|
||||
if (!addr) {
|
||||
r = -ENOMEM;
|
||||
|
@ -283,8 +284,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
}
|
||||
|
||||
LIST_INIT(addresses, addr);
|
||||
|
||||
memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
|
||||
memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
|
||||
|
||||
lt_valid = be32toh(addr->iaaddr.lifetime_valid);
|
||||
lt_pref = be32toh(addr->iaaddr.lifetime_valid);
|
||||
|
@ -302,10 +302,12 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
break;
|
||||
|
||||
case SD_DHCP6_OPTION_STATUS_CODE:
|
||||
if (optlen < sizeof(status))
|
||||
break;
|
||||
if (optlen < sizeof(status)) {
|
||||
r = -ENOMSG;
|
||||
goto error;
|
||||
}
|
||||
|
||||
status = (*buf)[0] << 8 | (*buf)[1];
|
||||
status = option->data[0] << 8 | option->data[1];
|
||||
if (status) {
|
||||
log_dhcp6_client(client, "IA status %d",
|
||||
status);
|
||||
|
@ -320,16 +322,9 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
break;
|
||||
}
|
||||
|
||||
*buflen -= optlen;
|
||||
*buf += optlen;
|
||||
i += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
if (r == -ENOMSG)
|
||||
r = 0;
|
||||
|
||||
if (*buflen)
|
||||
r = -ENOMSG;
|
||||
|
||||
if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
|
||||
lt_t1 = lt_min / 2;
|
||||
lt_t2 = lt_min / 10 * 8;
|
||||
|
@ -341,9 +336,6 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
|
|||
}
|
||||
|
||||
error:
|
||||
*buf += *buflen;
|
||||
*buflen = 0;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ struct DHCP6Message {
|
|||
} _packed_;
|
||||
be32_t transaction_id;
|
||||
};
|
||||
uint8_t options[];
|
||||
} _packed_;
|
||||
|
||||
typedef struct DHCP6Message DHCP6Message;
|
||||
|
|
|
@ -725,23 +725,32 @@ static int client_parse_message(
|
|||
DHCP6Message *message,
|
||||
size_t len,
|
||||
sd_dhcp6_lease *lease) {
|
||||
size_t pos = 0;
|
||||
int r;
|
||||
uint8_t *optval, *option, *id = NULL;
|
||||
uint16_t optcode, status;
|
||||
size_t optlen, id_len;
|
||||
bool clientid = false;
|
||||
be32_t iaid_lease;
|
||||
uint8_t *id = NULL;
|
||||
size_t id_len;
|
||||
|
||||
assert(client);
|
||||
assert(message);
|
||||
assert(len >= sizeof(DHCP6Message));
|
||||
assert(lease);
|
||||
|
||||
option = (uint8_t *)message + sizeof(DHCP6Message);
|
||||
len -= sizeof(DHCP6Message);
|
||||
|
||||
while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
|
||||
&optval)) >= 0) {
|
||||
while (pos < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&message->options[pos];
|
||||
uint16_t optcode, optlen, status;
|
||||
uint8_t *optval;
|
||||
be32_t iaid_lease;
|
||||
|
||||
if (len < sizeof(DHCP6Option) || len < sizeof(DHCP6Option) + be16toh(option->len))
|
||||
return -ENOBUFS;
|
||||
|
||||
optcode = be16toh(option->code);
|
||||
optlen = be16toh(option->len);
|
||||
optval = option->data;
|
||||
|
||||
switch (optcode) {
|
||||
case SD_DHCP6_OPTION_CLIENTID:
|
||||
if (clientid) {
|
||||
|
@ -779,7 +788,7 @@ static int client_parse_message(
|
|||
if (optlen != 1)
|
||||
return -EINVAL;
|
||||
|
||||
r = dhcp6_lease_set_preference(lease, *optval);
|
||||
r = dhcp6_lease_set_preference(lease, optval[0]);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -806,8 +815,7 @@ static int client_parse_message(
|
|||
break;
|
||||
}
|
||||
|
||||
r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
|
||||
&lease->ia);
|
||||
r = dhcp6_option_parse_ia(option, &lease->ia);
|
||||
if (r < 0 && r != -ENOMSG)
|
||||
return r;
|
||||
|
||||
|
@ -859,11 +867,9 @@ static int client_parse_message(
|
|||
break;
|
||||
}
|
||||
|
||||
pos += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
if (r == -ENOMSG)
|
||||
r = 0;
|
||||
|
||||
if (r < 0 || !clientid) {
|
||||
log_dhcp6_client(client, "%s has incomplete options",
|
||||
dhcp6_message_type_to_string(message->type));
|
||||
|
|
|
@ -217,14 +217,13 @@ static uint8_t fqdn_wire[16] = {
|
|||
static int test_advertise_option(sd_event *e) {
|
||||
_cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
|
||||
DHCP6Message *advertise = (DHCP6Message *)msg_advertise;
|
||||
uint8_t *optval, *opt = msg_advertise + sizeof(DHCP6Message);
|
||||
uint16_t optcode;
|
||||
size_t optlen, len = sizeof(msg_advertise) - sizeof(DHCP6Message);
|
||||
size_t len = sizeof(msg_advertise) - sizeof(DHCP6Message), pos = 0;
|
||||
be32_t val;
|
||||
uint8_t preference = 255;
|
||||
struct in6_addr addr;
|
||||
uint32_t lt_pref, lt_valid;
|
||||
int r;
|
||||
uint8_t *opt;
|
||||
bool opt_clientid = false;
|
||||
struct in6_addr *addrs;
|
||||
char **domains;
|
||||
|
@ -232,14 +231,19 @@ static int test_advertise_option(sd_event *e) {
|
|||
if (verbose)
|
||||
printf("* %s\n", __FUNCTION__);
|
||||
|
||||
assert_se(len >= sizeof(DHCP6Message));
|
||||
|
||||
assert_se(dhcp6_lease_new(&lease) >= 0);
|
||||
|
||||
assert_se(advertise->type == DHCP6_ADVERTISE);
|
||||
assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) ==
|
||||
0x0fb4e5);
|
||||
|
||||
while ((r = dhcp6_option_parse(&opt, &len, &optcode, &optlen,
|
||||
&optval)) >= 0) {
|
||||
while (pos < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&advertise->options[pos];
|
||||
const uint16_t optcode = be16toh(option->code);
|
||||
const uint16_t optlen = be16toh(option->len);
|
||||
uint8_t *optval = option->data;
|
||||
|
||||
switch(optcode) {
|
||||
case SD_DHCP6_OPTION_CLIENTID:
|
||||
|
@ -261,9 +265,7 @@ static int test_advertise_option(sd_event *e) {
|
|||
val = htobe32(120);
|
||||
assert_se(!memcmp(optval + 8, &val, sizeof(val)));
|
||||
|
||||
assert_se(dhcp6_option_parse_ia(&optval, &optlen,
|
||||
optcode,
|
||||
&lease->ia) >= 0);
|
||||
assert_se(dhcp6_option_parse_ia(option, &lease->ia) >= 0);
|
||||
|
||||
break;
|
||||
|
||||
|
@ -309,11 +311,11 @@ static int test_advertise_option(sd_event *e) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pos += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
|
||||
assert_se(r == -ENOMSG);
|
||||
|
||||
assert_se(pos == len);
|
||||
assert_se(opt_clientid);
|
||||
|
||||
sd_dhcp6_lease_reset_address_iter(lease);
|
||||
|
@ -415,15 +417,11 @@ static int test_client_send_reply(DHCP6Message *request) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
|
||||
size_t len) {
|
||||
static int test_client_verify_request(DHCP6Message *request, size_t len) {
|
||||
_cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
|
||||
uint8_t *optval;
|
||||
uint16_t optcode;
|
||||
size_t optlen;
|
||||
size_t pos = 0;
|
||||
bool found_clientid = false, found_iana = false, found_serverid = false,
|
||||
found_elapsed_time = false, found_fqdn = false;
|
||||
int r;
|
||||
struct in6_addr addr;
|
||||
be32_t val;
|
||||
uint32_t lt_pref, lt_valid;
|
||||
|
@ -432,8 +430,14 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
|
|||
|
||||
assert_se(dhcp6_lease_new(&lease) >= 0);
|
||||
|
||||
while ((r = dhcp6_option_parse(&option, &len,
|
||||
&optcode, &optlen, &optval)) >= 0) {
|
||||
len -= sizeof(DHCP6Message);
|
||||
|
||||
while (pos < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&request->options[pos];
|
||||
uint16_t optcode = be16toh(option->code);
|
||||
uint16_t optlen = be16toh(option->len);
|
||||
uint8_t *optval = option->data;
|
||||
|
||||
switch(optcode) {
|
||||
case SD_DHCP6_OPTION_CLIENTID:
|
||||
assert_se(!found_clientid);
|
||||
|
@ -458,8 +462,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
|
|||
val = htobe32(120);
|
||||
assert_se(!memcmp(optval + 8, &val, sizeof(val)));
|
||||
|
||||
assert_se(!dhcp6_option_parse_ia(&optval, &optlen,
|
||||
optcode, &lease->ia));
|
||||
assert_se(!dhcp6_option_parse_ia(option, &lease->ia));
|
||||
|
||||
break;
|
||||
|
||||
|
@ -489,9 +492,10 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
|
|||
assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire)));
|
||||
break;
|
||||
}
|
||||
|
||||
pos += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
assert_se(r == -ENOMSG);
|
||||
assert_se(found_clientid && found_iana && found_serverid &&
|
||||
found_elapsed_time);
|
||||
|
||||
|
@ -526,19 +530,21 @@ static int test_client_send_advertise(DHCP6Message *solicit) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option,
|
||||
size_t len) {
|
||||
uint8_t *optval;
|
||||
uint16_t optcode;
|
||||
size_t optlen;
|
||||
static int test_client_verify_solicit(DHCP6Message *solicit, size_t len) {
|
||||
bool found_clientid = false, found_iana = false,
|
||||
found_elapsed_time = false, found_fqdn = false;
|
||||
int r;
|
||||
size_t pos = 0;
|
||||
|
||||
assert_se(solicit->type == DHCP6_SOLICIT);
|
||||
|
||||
while ((r = dhcp6_option_parse(&option, &len,
|
||||
&optcode, &optlen, &optval)) >= 0) {
|
||||
len -= sizeof(DHCP6Message);
|
||||
|
||||
while (pos < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&solicit->options[pos];
|
||||
uint16_t optcode = be16toh(option->code);
|
||||
uint16_t optlen = be16toh(option->len);
|
||||
uint8_t *optval = option->data;
|
||||
|
||||
switch(optcode) {
|
||||
case SD_DHCP6_OPTION_CLIENTID:
|
||||
assert_se(!found_clientid);
|
||||
|
@ -578,9 +584,11 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option,
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
pos += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
assert_se(r == -ENOMSG);
|
||||
assert_se(pos == len);
|
||||
assert_se(found_clientid && found_iana && found_elapsed_time);
|
||||
|
||||
return 0;
|
||||
|
@ -623,17 +631,15 @@ static void test_client_information_cb(sd_dhcp6_client *client, int event,
|
|||
assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
|
||||
|
||||
assert_se(sd_dhcp6_client_start(client) >= 0);
|
||||
|
||||
}
|
||||
|
||||
static int test_client_verify_information_request(DHCP6Message *information_request,
|
||||
uint8_t *option, size_t len) {
|
||||
size_t len) {
|
||||
|
||||
_cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
|
||||
uint8_t *optval;
|
||||
uint16_t optcode;
|
||||
size_t optlen;
|
||||
size_t pos = 0;
|
||||
bool found_clientid = false, found_elapsed_time = false;
|
||||
int r;
|
||||
struct in6_addr addr;
|
||||
uint32_t lt_pref, lt_valid;
|
||||
|
||||
|
@ -641,8 +647,14 @@ static int test_client_verify_information_request(DHCP6Message *information_requ
|
|||
|
||||
assert_se(dhcp6_lease_new(&lease) >= 0);
|
||||
|
||||
while ((r = dhcp6_option_parse(&option, &len,
|
||||
&optcode, &optlen, &optval)) >= 0) {
|
||||
len -= sizeof(DHCP6Message);
|
||||
|
||||
while (pos < len) {
|
||||
DHCP6Option *option = (DHCP6Option *)&information_request->options[pos];
|
||||
uint16_t optcode = be16toh(option->code);
|
||||
uint16_t optlen = be16toh(option->len);
|
||||
uint8_t *optval = option->data;
|
||||
|
||||
switch(optcode) {
|
||||
case SD_DHCP6_OPTION_CLIENTID:
|
||||
assert_se(!found_clientid);
|
||||
|
@ -671,9 +683,11 @@ static int test_client_verify_information_request(DHCP6Message *information_requ
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
pos += sizeof(*option) + optlen;
|
||||
}
|
||||
|
||||
assert_se(r == -ENOMSG);
|
||||
assert_se(pos == len);
|
||||
assert_se(found_clientid && found_elapsed_time);
|
||||
|
||||
sd_dhcp6_lease_reset_address_iter(lease);
|
||||
|
@ -689,7 +703,6 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
|
|||
struct in6_addr mcast =
|
||||
IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
|
||||
DHCP6Message *message;
|
||||
uint8_t *option;
|
||||
|
||||
assert_se(s == test_dhcp_fd[0]);
|
||||
assert_se(server_address);
|
||||
|
@ -699,21 +712,19 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
|
|||
assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast));
|
||||
|
||||
message = (DHCP6Message *)packet;
|
||||
option = (uint8_t *)(message + 1);
|
||||
len -= sizeof(DHCP6Message);
|
||||
|
||||
assert_se(message->transaction_id & 0x00ffffff);
|
||||
|
||||
if (test_client_message_num == 0) {
|
||||
test_client_verify_information_request(message, option, len);
|
||||
test_client_verify_information_request(message, len);
|
||||
test_client_send_reply(message);
|
||||
test_client_message_num++;
|
||||
} else if (test_client_message_num == 1) {
|
||||
test_client_verify_solicit(message, option, len);
|
||||
test_client_verify_solicit(message, len);
|
||||
test_client_send_advertise(message);
|
||||
test_client_message_num++;
|
||||
} else if (test_client_message_num == 2) {
|
||||
test_client_verify_request(message, option, len);
|
||||
test_client_verify_request(message, len);
|
||||
test_client_send_reply(message);
|
||||
test_client_message_num++;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue