2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2014-06-19 14:39:20 +02:00
|
|
|
/***
|
2018-06-12 17:15:23 +02:00
|
|
|
Copyright © 2014-2015 Intel Corporation. All rights reserved.
|
2014-06-19 14:39:20 +02:00
|
|
|
***/
|
|
|
|
|
|
|
|
#include <errno.h>
|
2015-11-16 22:09:36 +01:00
|
|
|
#include <netinet/in.h>
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2016-01-20 14:44:28 +01:00
|
|
|
#include "sd-dhcp6-client.h"
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2014-06-19 14:39:20 +02:00
|
|
|
#include "dhcp6-internal.h"
|
2018-01-04 14:11:42 +01:00
|
|
|
#include "dhcp6-lease-internal.h"
|
2014-06-19 14:39:20 +02:00
|
|
|
#include "dhcp6-protocol.h"
|
2015-05-04 12:23:46 +02:00
|
|
|
#include "dns-domain.h"
|
2019-03-13 12:02:21 +01:00
|
|
|
#include "memory-util.h"
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "sparse-endian.h"
|
|
|
|
#include "strv.h"
|
|
|
|
#include "unaligned.h"
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2018-01-04 14:11:42 +01:00
|
|
|
typedef struct DHCP6StatusOption {
|
|
|
|
struct DHCP6Option option;
|
|
|
|
be16_t status;
|
|
|
|
char msg[];
|
|
|
|
} _packed_ DHCP6StatusOption;
|
|
|
|
|
2018-01-04 14:11:43 +01:00
|
|
|
typedef struct DHCP6AddressOption {
|
|
|
|
struct DHCP6Option option;
|
|
|
|
struct iaaddr iaaddr;
|
|
|
|
uint8_t options[];
|
|
|
|
} _packed_ DHCP6AddressOption;
|
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
typedef struct DHCP6PDPrefixOption {
|
|
|
|
struct DHCP6Option option;
|
|
|
|
struct iapdprefix iapdprefix;
|
|
|
|
uint8_t options[];
|
|
|
|
} _packed_ DHCP6PDPrefixOption;
|
|
|
|
|
2018-10-19 12:11:59 +02:00
|
|
|
#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
|
|
|
|
#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
|
|
|
|
#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
|
|
|
|
size_t optlen) {
|
2014-11-04 20:19:07 +01:00
|
|
|
DHCP6Option *option = (DHCP6Option*) *buf;
|
2014-11-01 14:32:28 +01:00
|
|
|
|
2014-06-19 14:39:20 +02:00
|
|
|
assert_return(buf, -EINVAL);
|
|
|
|
assert_return(*buf, -EINVAL);
|
|
|
|
assert_return(buflen, -EINVAL);
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
|
2014-06-19 14:39:20 +02:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
2014-11-04 20:19:07 +01:00
|
|
|
option->code = htobe16(optcode);
|
|
|
|
option->len = htobe16(optlen);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
*buf += offsetof(DHCP6Option, data);
|
|
|
|
*buflen -= offsetof(DHCP6Option, data);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
|
|
|
|
size_t optlen, const void *optval) {
|
|
|
|
int r;
|
|
|
|
|
2014-06-25 15:54:30 +02:00
|
|
|
assert_return(optval || optlen == 0, -EINVAL);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
r = option_append_hdr(buf, buflen, code, optlen);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2016-02-02 03:57:41 +01:00
|
|
|
memcpy_safe(*buf, optval, optlen);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
*buf += optlen;
|
|
|
|
*buflen -= optlen;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-19 12:11:29 +02:00
|
|
|
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
|
2014-06-19 14:39:20 +02:00
|
|
|
uint16_t len;
|
|
|
|
uint8_t *ia_hdr;
|
2018-01-16 17:53:00 +01:00
|
|
|
size_t iaid_offset, ia_buflen, ia_addrlen = 0;
|
2014-06-19 14:39:20 +02:00
|
|
|
DHCP6Address *addr;
|
|
|
|
int r;
|
|
|
|
|
2018-10-19 12:11:48 +02:00
|
|
|
assert_return(buf, -EINVAL);
|
|
|
|
assert_return(*buf, -EINVAL);
|
|
|
|
assert_return(buflen, -EINVAL);
|
|
|
|
assert_return(ia, -EINVAL);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
switch (ia->type) {
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_IA_NA:
|
2014-06-19 14:39:20 +02:00
|
|
|
len = DHCP6_OPTION_IA_NA_LEN;
|
2018-01-16 17:53:00 +01:00
|
|
|
iaid_offset = offsetof(DHCP6IA, ia_na);
|
2014-06-19 14:39:20 +02:00
|
|
|
break;
|
|
|
|
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_IA_TA:
|
2014-06-19 14:39:20 +02:00
|
|
|
len = DHCP6_OPTION_IA_TA_LEN;
|
2018-01-16 17:53:00 +01:00
|
|
|
iaid_offset = offsetof(DHCP6IA, ia_ta);
|
2014-06-19 14:39:20 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2018-10-19 12:12:33 +02:00
|
|
|
if (*buflen < offsetof(DHCP6Option, data) + len)
|
2014-06-19 14:39:20 +02:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
ia_hdr = *buf;
|
|
|
|
ia_buflen = *buflen;
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
*buf += offsetof(DHCP6Option, data);
|
|
|
|
*buflen -= offsetof(DHCP6Option, data);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2018-01-16 17:53:00 +01:00
|
|
|
memcpy(*buf, (char*) ia + iaid_offset, len);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
*buf += len;
|
|
|
|
*buflen -= len;
|
|
|
|
|
|
|
|
LIST_FOREACH(addresses, addr, ia->addresses) {
|
2016-01-20 14:44:28 +01:00
|
|
|
r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
|
2014-10-08 10:00:07 +02:00
|
|
|
sizeof(addr->iaaddr));
|
2014-06-19 14:39:20 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2014-10-08 10:00:07 +02:00
|
|
|
memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2014-10-08 10:00:07 +02:00
|
|
|
*buf += sizeof(addr->iaaddr);
|
|
|
|
*buflen -= sizeof(addr->iaaddr);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
|
2014-06-19 14:39:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-11-16 10:07:07 +01:00
|
|
|
int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
|
2018-06-08 16:05:18 +02:00
|
|
|
uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
|
2017-11-16 10:07:07 +01:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert_return(buf && *buf && buflen && fqdn, -EINVAL);
|
|
|
|
|
|
|
|
buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
|
|
|
|
|
|
|
|
/* Store domain name after flags field */
|
|
|
|
r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
|
|
|
|
if (r <= 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to RFC 4704, chapter 4.2 only add terminating zero-length
|
|
|
|
* label in case a FQDN is provided. Since dns_name_to_wire_format
|
|
|
|
* always adds terminating zero-length label remove if only a hostname
|
|
|
|
* is provided.
|
|
|
|
*/
|
|
|
|
if (dns_name_is_single_label(fqdn))
|
|
|
|
r--;
|
|
|
|
|
|
|
|
r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2019-09-25 05:14:12 +02:00
|
|
|
int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) {
|
2018-01-04 14:11:49 +01:00
|
|
|
DHCP6Option *option = (DHCP6Option *)buf;
|
|
|
|
size_t i = sizeof(*option) + sizeof(pd->ia_pd);
|
2019-09-25 05:14:12 +02:00
|
|
|
DHCP6PDPrefixOption *prefix_opt;
|
2018-01-04 14:11:49 +01:00
|
|
|
DHCP6Address *prefix;
|
|
|
|
|
|
|
|
assert_return(buf, -EINVAL);
|
|
|
|
assert_return(pd, -EINVAL);
|
|
|
|
assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
|
|
|
|
|
|
|
|
if (len < i)
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
|
|
|
|
|
|
|
|
memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
|
|
|
|
LIST_FOREACH(addresses, prefix, pd->addresses) {
|
|
|
|
if (len < i + sizeof(*prefix_opt))
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
|
|
|
|
prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
|
|
|
|
prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
|
|
|
|
|
2019-09-25 05:14:12 +02:00
|
|
|
memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, sizeof(struct iapdprefix));
|
|
|
|
i += sizeof(*prefix_opt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
|
|
|
|
if (len < i + sizeof(*prefix_opt))
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
|
|
|
|
prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
|
|
|
|
prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
|
2018-01-04 14:11:49 +01:00
|
|
|
|
2019-09-25 05:14:12 +02:00
|
|
|
memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix));
|
2018-01-04 14:11:49 +01:00
|
|
|
i += sizeof(*prefix_opt);
|
|
|
|
}
|
|
|
|
|
|
|
|
option->len = htobe16(i - sizeof(*option));
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2014-11-04 20:19:07 +01:00
|
|
|
static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
|
|
|
|
DHCP6Option *option = (DHCP6Option*) *buf;
|
2014-06-19 14:39:39 +02:00
|
|
|
uint16_t len;
|
|
|
|
|
|
|
|
assert_return(buf, -EINVAL);
|
2014-11-01 14:32:28 +01:00
|
|
|
assert_return(optcode, -EINVAL);
|
2014-06-19 14:39:39 +02:00
|
|
|
assert_return(optlen, -EINVAL);
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (*buflen < offsetof(DHCP6Option, data))
|
2014-06-19 14:39:39 +02:00
|
|
|
return -ENOMSG;
|
|
|
|
|
2014-11-04 20:19:07 +01:00
|
|
|
len = be16toh(option->len);
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
if (len > *buflen)
|
|
|
|
return -ENOMSG;
|
|
|
|
|
2014-11-04 20:19:07 +01:00
|
|
|
*optcode = be16toh(option->code);
|
2014-06-19 14:39:39 +02:00
|
|
|
*optlen = len;
|
|
|
|
|
|
|
|
*buf += 4;
|
|
|
|
*buflen -= 4;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-06-19 14:39:20 +02:00
|
|
|
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
|
|
|
|
size_t *optlen, uint8_t **optvalue) {
|
2014-06-19 14:39:39 +02:00
|
|
|
int r;
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2014-06-19 14:39:39 +02:00
|
|
|
assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2014-06-19 14:39:39 +02:00
|
|
|
r = option_parse_hdr(buf, buflen, optcode, optlen);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2014-06-19 14:39:20 +02:00
|
|
|
|
2014-06-19 14:39:39 +02:00
|
|
|
if (*optlen > *buflen)
|
2014-06-19 14:39:20 +02:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
2014-06-19 14:39:39 +02:00
|
|
|
*optvalue = *buf;
|
|
|
|
*buflen -= *optlen;
|
|
|
|
*buf += *optlen;
|
2014-06-19 14:39:20 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-09-30 13:23:58 +02:00
|
|
|
int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
|
2018-01-04 14:11:42 +01:00
|
|
|
DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
|
|
|
|
|
2018-09-30 13:23:58 +02:00
|
|
|
if (len < sizeof(DHCP6StatusOption) ||
|
2018-10-19 12:14:22 +02:00
|
|
|
be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
|
2018-01-04 14:11:42 +01:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
return be16toh(statusopt->status);
|
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:43 +01:00
|
|
|
static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
|
|
|
|
uint32_t *lifetime_valid) {
|
|
|
|
DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
|
|
|
|
DHCP6Address *addr;
|
|
|
|
uint32_t lt_valid, lt_pref;
|
|
|
|
int r;
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
|
2018-01-04 14:11:43 +01:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
|
|
|
|
lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
|
|
|
|
|
|
|
|
if (lt_valid == 0 || lt_pref > lt_valid) {
|
|
|
|
log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
|
|
|
|
lt_pref, lt_valid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
|
|
|
|
r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
|
2018-01-04 14:11:43 +01:00
|
|
|
if (r != 0)
|
|
|
|
return r < 0 ? r: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
addr = new0(DHCP6Address, 1);
|
|
|
|
if (!addr)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
LIST_INIT(addresses, addr);
|
|
|
|
memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
|
|
|
|
|
|
|
|
LIST_PREPEND(addresses, ia->addresses, addr);
|
|
|
|
|
|
|
|
*lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
|
|
|
|
uint32_t *lifetime_valid) {
|
|
|
|
DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
|
|
|
|
DHCP6Address *prefix;
|
|
|
|
uint32_t lt_valid, lt_pref;
|
|
|
|
int r;
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
|
2018-01-04 14:11:46 +01:00
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
|
|
|
|
lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
|
|
|
|
|
|
|
|
if (lt_valid == 0 || lt_pref > lt_valid) {
|
|
|
|
log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
|
|
|
|
lt_pref, lt_valid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
|
|
|
|
r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
|
2018-01-04 14:11:46 +01:00
|
|
|
if (r != 0)
|
|
|
|
return r < 0 ? r: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
prefix = new0(DHCP6Address, 1);
|
|
|
|
if (!prefix)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
LIST_INIT(addresses, prefix);
|
|
|
|
memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
|
|
|
|
|
|
|
|
LIST_PREPEND(addresses, ia->addresses, prefix);
|
|
|
|
|
|
|
|
*lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:41 +01:00
|
|
|
int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
|
|
|
|
uint16_t iatype, optlen;
|
|
|
|
size_t i, len;
|
|
|
|
int r = 0, status;
|
|
|
|
uint16_t opt;
|
2014-06-19 14:39:39 +02:00
|
|
|
size_t iaaddr_offset;
|
2018-01-15 16:15:13 +01:00
|
|
|
uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
assert_return(ia, -EINVAL);
|
|
|
|
assert_return(!ia->addresses, -EINVAL);
|
|
|
|
|
2018-01-04 14:11:41 +01:00
|
|
|
iatype = be16toh(iaoption->code);
|
|
|
|
len = be16toh(iaoption->len);
|
|
|
|
|
2014-06-19 14:39:39 +02:00
|
|
|
switch (iatype) {
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_IA_NA:
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-10-18 23:11:49 +02:00
|
|
|
if (len < DHCP6_OPTION_IA_NA_LEN)
|
|
|
|
return -ENOBUFS;
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
|
2018-01-04 14:11:41 +01:00
|
|
|
memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-01-04 14:11:40 +01:00
|
|
|
lt_t1 = be32toh(ia->ia_na.lifetime_t1);
|
|
|
|
lt_t2 = be32toh(ia->ia_na.lifetime_t2);
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
|
2018-01-04 14:11:46 +01:00
|
|
|
log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
|
|
|
|
lt_t1, lt_t2);
|
2018-10-18 23:11:49 +02:00
|
|
|
return -EINVAL;
|
2018-01-04 14:11:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SD_DHCP6_OPTION_IA_PD:
|
|
|
|
|
2018-10-18 23:11:49 +02:00
|
|
|
if (len < sizeof(ia->ia_pd))
|
|
|
|
return -ENOBUFS;
|
2018-01-04 14:11:46 +01:00
|
|
|
|
|
|
|
iaaddr_offset = sizeof(ia->ia_pd);
|
|
|
|
memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
|
|
|
|
|
|
|
|
lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
|
|
|
|
lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
|
|
|
|
|
|
|
|
if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
|
|
|
|
log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
|
2014-06-19 14:39:39 +02:00
|
|
|
lt_t1, lt_t2);
|
2018-10-18 23:11:49 +02:00
|
|
|
return -EINVAL;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_IA_TA:
|
2018-10-18 23:11:49 +02:00
|
|
|
if (len < DHCP6_OPTION_IA_TA_LEN)
|
|
|
|
return -ENOBUFS;
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
|
2018-01-04 14:11:41 +01:00
|
|
|
memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2018-10-18 23:11:49 +02:00
|
|
|
return -ENOMSG;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ia->type = iatype;
|
2018-01-04 14:11:41 +01:00
|
|
|
i = iaaddr_offset;
|
|
|
|
|
|
|
|
while (i < len) {
|
|
|
|
DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-10-18 23:11:49 +02:00
|
|
|
if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
|
|
|
|
return -ENOBUFS;
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-01-04 14:11:41 +01:00
|
|
|
opt = be16toh(option->code);
|
|
|
|
optlen = be16toh(option->len);
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
switch (opt) {
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_IAADDR:
|
2018-01-04 14:11:43 +01:00
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
|
|
|
|
log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
|
2018-10-18 23:11:49 +02:00
|
|
|
return -EINVAL;
|
2018-01-04 14:11:46 +01:00
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:43 +01:00
|
|
|
r = dhcp6_option_parse_address(option, ia, <_valid);
|
|
|
|
if (r < 0)
|
2018-10-18 23:11:49 +02:00
|
|
|
return r;
|
2018-01-04 14:11:43 +01:00
|
|
|
|
|
|
|
if (lt_valid < lt_min)
|
|
|
|
lt_min = lt_valid;
|
2014-06-19 14:39:39 +02:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
case SD_DHCP6_OPTION_IA_PD_PREFIX:
|
|
|
|
|
|
|
|
if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
|
|
|
|
log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
|
2018-10-18 23:11:49 +02:00
|
|
|
return -EINVAL;
|
2018-01-04 14:11:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
r = dhcp6_option_parse_pdprefix(option, ia, <_valid);
|
|
|
|
if (r < 0)
|
2018-10-18 23:11:49 +02:00
|
|
|
return r;
|
2018-01-04 14:11:46 +01:00
|
|
|
|
|
|
|
if (lt_valid < lt_min)
|
|
|
|
lt_min = lt_valid;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2016-01-20 14:44:28 +01:00
|
|
|
case SD_DHCP6_OPTION_STATUS_CODE:
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-10-19 12:14:22 +02:00
|
|
|
status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
|
2018-10-18 23:11:49 +02:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
2018-10-18 20:44:56 +02:00
|
|
|
if (status > 0) {
|
2014-06-19 14:39:39 +02:00
|
|
|
log_dhcp6_client(client, "IA status %d",
|
|
|
|
status);
|
2018-01-04 14:11:42 +01:00
|
|
|
|
2018-10-18 23:11:49 +02:00
|
|
|
return -EINVAL;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
log_dhcp6_client(client, "Unknown IA option %d", opt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:41 +01:00
|
|
|
i += sizeof(*option) + optlen;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
switch(iatype) {
|
|
|
|
case SD_DHCP6_OPTION_IA_NA:
|
|
|
|
if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
|
|
|
|
lt_t1 = lt_min / 2;
|
|
|
|
lt_t2 = lt_min / 10 * 8;
|
|
|
|
ia->ia_na.lifetime_t1 = htobe32(lt_t1);
|
|
|
|
ia->ia_na.lifetime_t2 = htobe32(lt_t2);
|
|
|
|
|
|
|
|
log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
|
|
|
|
lt_t1, lt_t2);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SD_DHCP6_OPTION_IA_PD:
|
|
|
|
if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
|
|
|
|
lt_t1 = lt_min / 2;
|
|
|
|
lt_t2 = lt_min / 10 * 8;
|
|
|
|
ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
|
|
|
|
ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
|
|
|
|
|
|
|
|
log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
|
|
|
|
lt_t1, lt_t2);
|
|
|
|
}
|
2014-06-19 14:39:39 +02:00
|
|
|
|
2018-01-04 14:11:46 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
|
|
|
|
2018-10-18 23:11:49 +02:00
|
|
|
return 0;
|
2014-06-19 14:39:39 +02:00
|
|
|
}
|
2015-04-02 09:35:30 +02:00
|
|
|
|
|
|
|
int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
|
|
|
|
struct in6_addr **addrs, size_t count,
|
|
|
|
size_t *allocated) {
|
|
|
|
|
|
|
|
if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!GREEDY_REALLOC(*addrs, *allocated,
|
|
|
|
count * sizeof(struct in6_addr) + optlen))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
memcpy(*addrs + count, optval, optlen);
|
|
|
|
|
|
|
|
count += optlen / sizeof(struct in6_addr);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
2015-05-04 12:23:46 +02:00
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
|
2015-05-04 12:23:46 +02:00
|
|
|
size_t pos = 0, idx = 0;
|
sd-network: fix memleak in dhcp6_option_parse_domainname (#5114)
The simplest way to reproduce:
```diff
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index bd289fa..7b0a5ef 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -168,7 +168,7 @@ static uint8_t msg_advertise[198] = {
0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8,
0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b,
- 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x01, 0x6c, 0x01, 0x62, 0x00, 0x0a, 0x6e, 0x74,
0x72, 0x61, 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20,
0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
@@ -338,9 +338,7 @@ static int test_advertise_option(sd_event *e) {
assert_se(!memcmp(addrs, &msg_advertise[124], r * 16));
r = sd_dhcp6_lease_get_domains(lease, &domains);
- assert_se(r == 1);
- assert_se(!strcmp("lab.intra", domains[0]));
- assert_se(domains[1] == NULL);
+ assert_se(r == -ENOENT);
r = sd_dhcp6_lease_get_ntp_addrs(lease, &addrs);
assert_se(r == 1);
```
Fixes:
```
=================================================================
==15043==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x7f13c8564160 in strdup (/lib64/libasan.so.3+0x5a160)
#1 0x7f13c7caaf69 in strv_extend src/basic/strv.c:552
#2 0x55f775787230 in dhcp6_option_parse_domainname src/libsystemd-network/dhcp6-option.c:399
#3 0x55f775788b96 in dhcp6_lease_set_domains src/libsystemd-network/sd-dhcp6-lease.c:225
#4 0x55f775774b95 in test_advertise_option src/libsystemd-network/test-dhcp6-client.c:287
#5 0x55f77577883e in main src/libsystemd-network/test-dhcp6-client.c:759
#6 0x7f13c7589400 in __libc_start_main (/lib64/libc.so.6+0x20400)
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x7f13c8564160 in strdup (/lib64/libasan.so.3+0x5a160)
#1 0x7f13c7caaf69 in strv_extend src/basic/strv.c:552
#2 0x55f775787230 in dhcp6_option_parse_domainname src/libsystemd-network/dhcp6-option.c:399
#3 0x55f775788b96 in dhcp6_lease_set_domains src/libsystemd-network/sd-dhcp6-lease.c:225
#4 0x55f775781348 in client_parse_message src/libsystemd-network/sd-dhcp6-client.c:807
#5 0x55f775781ba2 in client_receive_advertise src/libsystemd-network/sd-dhcp6-client.c:895
#6 0x55f775782453 in client_receive_message src/libsystemd-network/sd-dhcp6-client.c:994
#7 0x7f13c7e447f4 in source_dispatch src/libsystemd/sd-event/sd-event.c:2268
#8 0x7f13c7e471b0 in sd_event_dispatch src/libsystemd/sd-event/sd-event.c:2627
#9 0x7f13c7e47ab3 in sd_event_run src/libsystemd/sd-event/sd-event.c:2686
#10 0x7f13c7e47c21 in sd_event_loop src/libsystemd/sd-event/sd-event.c:2706
#11 0x55f77577863c in test_client_solicit src/libsystemd-network/test-dhcp6-client.c:737
#12 0x55f77577884b in main src/libsystemd-network/test-dhcp6-client.c:760
#13 0x7f13c7589400 in __libc_start_main (/lib64/libc.so.6+0x20400)
SUMMARY: AddressSanitizer: 8 byte(s) leaked in 2 allocation(s).
```
2017-01-24 03:11:59 +01:00
|
|
|
_cleanup_strv_free_ char **names = NULL;
|
2015-05-04 12:23:46 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert_return(optlen > 1, -ENODATA);
|
2015-10-26 13:07:30 +01:00
|
|
|
assert_return(optval[optlen - 1] == '\0', -EINVAL);
|
2015-05-04 12:23:46 +02:00
|
|
|
|
|
|
|
while (pos < optlen) {
|
|
|
|
_cleanup_free_ char *ret = NULL;
|
|
|
|
size_t n = 0, allocated = 0;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
for (;;) {
|
2018-10-18 20:42:10 +02:00
|
|
|
const char *label;
|
2015-05-04 12:23:46 +02:00
|
|
|
uint8_t c;
|
|
|
|
|
|
|
|
c = optval[pos++];
|
|
|
|
|
|
|
|
if (c == 0)
|
|
|
|
/* End of name */
|
|
|
|
break;
|
2018-10-18 20:42:10 +02:00
|
|
|
if (c > 63)
|
|
|
|
return -EBADMSG;
|
|
|
|
|
|
|
|
/* Literal label */
|
|
|
|
label = (const char *)&optval[pos];
|
|
|
|
pos += c;
|
|
|
|
if (pos >= optlen)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
ret[n++] = '.';
|
|
|
|
|
|
|
|
r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2015-05-04 12:23:46 +02:00
|
|
|
|
2018-10-18 20:42:10 +02:00
|
|
|
n += r;
|
2015-05-04 12:23:46 +02:00
|
|
|
}
|
|
|
|
|
2018-10-18 20:42:10 +02:00
|
|
|
if (n == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!GREEDY_REALLOC(ret, allocated, n + 1))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2015-05-04 12:23:46 +02:00
|
|
|
ret[n] = 0;
|
|
|
|
|
|
|
|
r = strv_extend(&names, ret);
|
|
|
|
if (r < 0)
|
2018-10-18 20:42:10 +02:00
|
|
|
return r;
|
2015-05-04 12:23:46 +02:00
|
|
|
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
|
2018-03-22 16:53:26 +01:00
|
|
|
*str_arr = TAKE_PTR(names);
|
2015-05-04 12:23:46 +02:00
|
|
|
|
|
|
|
return idx;
|
|
|
|
}
|