From 4af39cb8ecdf55fc0238bf3149696946f6f28fca Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Sep 2021 16:30:40 +0900 Subject: sd-dhcp6-client: make dhcp6_option_parse_status() also parse error message This also introduce dhcp6_option_parse_ia_options(). Currently, it is assumed that each IA address or PD prefix may contain a status sub-option. But it is not prohibited that other sub-options or multiple status options are contained. --- src/libsystemd-network/dhcp6-internal.h | 2 +- src/libsystemd-network/dhcp6-option.c | 111 +++++++++++++++++++---------- src/libsystemd-network/sd-dhcp6-client.c | 22 +++--- src/libsystemd-network/test-dhcp6-client.c | 61 +++++++++------- 4 files changed, 122 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 8bcc826f42..dfb0734520 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -109,7 +109,7 @@ int dhcp6_option_parse( uint16_t *ret_option_code, size_t *ret_option_data_len, const uint8_t **ret_option_data); -int dhcp6_option_parse_status(DHCP6Option *option, size_t len); +int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message); int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, be32_t iaid, DHCP6IA *ia, uint16_t *ret_status_code); int dhcp6_option_parse_ip6addrs(const uint8_t *optval, uint16_t optlen, struct in6_addr **addrs, size_t count); 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; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 298a89b086..8955aedc1f 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -1126,7 +1126,6 @@ static int client_parse_message( while (pos < len) { DHCP6Option *option = (DHCP6Option *) &message->options[pos]; uint16_t optcode, optlen; - int status; uint8_t *optval; if (len < pos + offsetof(DHCP6Option, data)) @@ -1176,18 +1175,21 @@ static int client_parse_message( break; - case SD_DHCP6_OPTION_STATUS_CODE: - status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option)); - if (status < 0) - return status; + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; - if (status > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s Status %s", - dhcp6_message_type_to_string(message->type), - dhcp6_message_status_to_string(status)); + r = dhcp6_option_parse_status(optval, optlen, &msg); + if (r < 0) + return r; + if (r > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received %s message with non-zero status: %s%s%s", + dhcp6_message_type_to_string(message->type), + strempty(msg), isempty(msg) ? "" : ": ", + dhcp6_message_status_to_string(r)); break; - + } case SD_DHCP6_OPTION_IA_NA: if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode."); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 35aaa2a997..fb948ea8a9 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -249,7 +249,7 @@ static int test_option_status(sd_event *e) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, - /* status option */ + /* IA address status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01, }; static const uint8_t option3[] = { @@ -261,7 +261,7 @@ static int test_option_status(sd_event *e) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, - /* status option */ + /* IA address status option */ 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', }; @@ -275,7 +275,7 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* status option */ + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; static const uint8_t option5[] = { @@ -288,7 +288,7 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* status option */ + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, /* IA PD Prefix #2 */ 0x00, 0x1a, 0x00, 0x1f, @@ -296,11 +296,13 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x0l, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; DHCP6Option *option; DHCP6IA ia, pd; be32_t iaid; + uint16_t status; int r = 0; log_debug("/* %s */", __func__); @@ -308,62 +310,67 @@ static int test_option_status(sd_event *e) { memcpy(&iaid, option1 + 4, sizeof(iaid)); zero(ia); - option = (DHCP6Option *)option1; + option = (DHCP6Option*) option1; assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len)); r = dhcp6_option_parse_ia(NULL, option, 0, &ia, NULL); assert_se(r == -ENOANO); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status); assert_se(r == 0); - assert_se(ia.addresses == NULL); + assert_se(status == 1); + assert_se(!ia.addresses); option->len = htobe16(17); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status); assert_se(r == -ENOBUFS); - assert_se(ia.addresses == NULL); + assert_se(!ia.addresses); option->len = htobe16(sizeof(DHCP6Option)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status); assert_se(r == -ENOBUFS); - assert_se(ia.addresses == NULL); + assert_se(!ia.addresses); zero(ia); - option = (DHCP6Option *)option2; + option = (DHCP6Option*) option2; assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); - assert_se(r >= 0); - assert_se(ia.addresses == NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status); + assert_se(r > 0); + assert_se(status == 0); + assert_se(!ia.addresses); zero(ia); - option = (DHCP6Option *)option3; + option = (DHCP6Option*) option3; assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); - assert_se(r >= 0); - assert_se(ia.addresses != NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, &status); + assert_se(r > 0); + assert_se(status == 0); + assert_se(ia.addresses); dhcp6_lease_free_ia(&ia); zero(pd); - option = (DHCP6Option *)option4; + option = (DHCP6Option*) option4; assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL); - assert_se(r >= 0); - assert_se(pd.addresses != NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status); + assert_se(r > 0); + assert_se(status == 0); + assert_se(pd.addresses); assert_se(memcmp(&pd.ia_pd.id, &option4[4], 4) == 0); assert_se(memcmp(&pd.ia_pd.lifetime_t1, &option4[8], 4) == 0); assert_se(memcmp(&pd.ia_pd.lifetime_t2, &option4[12], 4) == 0); dhcp6_lease_free_ia(&pd); zero(pd); - option = (DHCP6Option *)option5; + option = (DHCP6Option*) option5; assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL); - assert_se(r >= 0); - assert_se(pd.addresses != NULL); + r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status); + assert_se(r > 0); + assert_se(status == 0); + assert_se(pd.addresses); dhcp6_lease_free_ia(&pd); return 0; -- cgit v1.2.1