diff options
Diffstat (limited to 'src/libsystemd-network/dhcp6-option.c')
-rw-r--r-- | src/libsystemd-network/dhcp6-option.c | 111 |
1 files changed, 75 insertions, 36 deletions
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 781d391c0c..35950386b0 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -14,17 +14,12 @@ #include "dhcp6-lease-internal.h" #include "dhcp6-protocol.h" #include "dns-domain.h" +#include "escape.h" #include "memory-util.h" #include "sparse-endian.h" #include "strv.h" #include "unaligned.h" -typedef struct DHCP6StatusOption { - struct DHCP6Option option; - be16_t status; - char msg[]; -} _packed_ DHCP6StatusOption; - typedef struct DHCP6AddressOption { struct DHCP6Option option; struct iaaddr iaaddr; @@ -407,14 +402,65 @@ int dhcp6_option_parse( return 0; } -int dhcp6_option_parse_status(DHCP6Option *option, size_t len) { - DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; +int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) { + assert(data); - if (len < sizeof(DHCP6StatusOption) || - be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption)) - return -ENOBUFS; + if (data_len < sizeof(uint16_t)) + return -EBADMSG; + + if (ret_status_message) { + char *msg; - return be16toh(statusopt->status); + /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415. + * Let's escape unsafe characters for safety. */ + msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t)); + if (!msg) + return -ENOMEM; + + *ret_status_message = msg; + } + + return unaligned_read_be16(data); +} + +static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) { + int r; + + assert(buf); + + for(size_t offset = 0; offset < buflen;) { + const uint8_t *data; + size_t data_len; + uint16_t code; + + r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data); + if (r < 0) + return r; + + switch(code) { + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; + + r = dhcp6_option_parse_status(data, data_len, &msg); + if (r == -ENOMEM) + return r; + if (r < 0) + /* Let's log but ignore the invalid status option. */ + log_dhcp6_client_errno(client, r, + "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m"); + else if (r > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an IA address or PD prefix option with non-zero status: %s%s%s", + strempty(msg), isempty(msg) ? "" : ": ", + dhcp6_message_status_to_string(r)); + break; + } + default: + log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code); + } + } + + return 0; } static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) { @@ -435,14 +481,10 @@ static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *opti "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, lt_pref, lt_valid); - 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)); + if (be16toh(option->len) + offsetof(DHCP6Option, data) > offsetof(DHCP6AddressOption, options)) { + r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iaaddr), be16toh(option->len) - sizeof(struct iaaddr)); if (r < 0) return r; - if (r > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Non-zero status code '%s' for address is received", - dhcp6_message_status_to_string(r)); } addr = new0(DHCP6Address, 1); @@ -477,14 +519,10 @@ static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *opt "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, lt_pref, lt_valid); - 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)); + if (be16toh(option->len) + offsetof(DHCP6Option, data) > offsetof(DHCP6PDPrefixOption, options)) { + r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iapdprefix), be16toh(option->len) - sizeof(struct iapdprefix)); if (r < 0) return r; - if (r > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Non-zero status code '%s' for PD prefix is received", - dhcp6_message_status_to_string(r)); } prefix = new0(DHCP6Address, 1); @@ -511,9 +549,9 @@ int dhcp6_option_parse_ia( uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX; uint16_t iatype, optlen; size_t iaaddr_offset; - int r = 0, status; size_t i, len; uint16_t opt; + int r; assert_return(ia, -EINVAL); assert_return(!ia->addresses, -EINVAL); @@ -636,24 +674,25 @@ int dhcp6_option_parse_ia( break; - case SD_DHCP6_OPTION_STATUS_CODE: - - status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data)); - if (status < 0) - return status; + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; - if (status > 0) { + r = dhcp6_option_parse_status(option->data, optlen, &msg); + if (r < 0) + return r; + if (r > 0) { if (ret_status_code) - *ret_status_code = status; + *ret_status_code = r; - log_dhcp6_client(client, "IA status %s", - dhcp6_message_status_to_string(status)); + log_dhcp6_client(client, + "Received an IA option with non-zero status: %s%s%s", + strempty(msg), isempty(msg) ? "" : ": ", + dhcp6_message_status_to_string(r)); return 0; } - break; - + } default: log_dhcp6_client(client, "Unknown IA option %d", opt); break; |