]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp6-client: make dhcp6_option_parse_status() also parse error message
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 23 Sep 2021 07:30:40 +0000 (16:30 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 29 Sep 2021 06:29:36 +0000 (15:29 +0900)
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
src/libsystemd-network/dhcp6-option.c
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd-network/test-dhcp6-client.c

index 8bcc826f420e927ccaa71339c3c1fee96051b39e..dfb0734520d73d4f6cfb72199d8c89162b5b26c7 100644 (file)
@@ -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);
index 781d391c0cadbd9fa462ae9ed66c2638eccb143e..35950386b0cec8f1aa69198d10f586efe3b2b0ac 100644 (file)
 #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;
index 298a89b086b8754576b311df72cf53d847602ae6..8955aedc1f04a6c92d4ebd1519963d1f90237976 100644 (file)
@@ -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.");
index 35aaa2a997597b059e2c46be7af7089316f00741..fb948ea8a9874127811ff2edcc48fefdfb79c14f 100644 (file)
@@ -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;