Repository: systemd/systemd (C)

Issue:
networkd: "State changed: configured -> failed" on receiving malformed DNSSL RA options
```sh
eived Router Advertisement: flags none preference medium lifetime 30min
host0: NDISC: Invoking callback for 'router' event.
host0: Requesting NDisc route (configuring,configured): dst: n/a, src: n/a, gw: fe80::741a:afff:fe88:1910, prefsrc: n/a, table: main(254), priority: 1024, proto: ra, scope: global, type: unicast, flags: n/a
host0: Failed to get DNSSL addresses: Invalid argument
host0: Failed
host0: State changed: configured -> failed
Sent message type
```
The DNSSL option was
```python
>>> p[ICMPv6NDOptDNSSL].show()
###[ ICMPv6 Neighbor Discovery Option - DNS Search List Option ]###
  type      = 31
  len       = 14
  res       = 61032
  lifetime  = 2968794681
  searchlist= ['\\xbc\x0b\\xbc\\xa9\\x97q7\\xad\\x86\\x80\x14.X\\xaa\\x8a\\x.7\\xa1\\xbe\\x91Y\x00\\xc4\\xe8\\xdd\\xd8m\\xe5JzqBtE\\x9e.\\xfd\\x9dq\x1d\\xd0\\xc0T\x0cM\x1f\\xbf\\x90\\xd9yX\\xc0\x1d\\xa39ϸ\\xec.xd2\\xe4Ͷ\x13/\\xc0F\\xe8\x07?\\xaa(\\xa5#\\xf1\\xf0\\xca\\xd3\x19?\\xfal|\\xec\x1b\\xcfq\\xeb\\xbah\x1b\\x8e}\\x93~\x0b\\x9f\\x.b\x12\\x9cu"_\x12\x00.']
```
```python
>>> hexdump(p[ICMPv6NDOptDNSSL])
0000  1F 0E EE 68 B0 F4 36 39 2C BC 0B BC A9 97 71 37  ...h..69,.....q7
0010  AD 86 80 14 2E 58 AA 8A B7 A1 BE 91 59 00 C4 E8  .....X......Y...
0020  DD D8 6D E5 4A 7A 71 42 74 45 9E 2E FD 9D 71 1D  ..m.JzqBtE....q.
0030  D0 C0 54 0C 4D 1F BF 90 D9 79 58 C0 1D A3 39 CF  ..T.M....yX...9.
0040  B8 EC D2 E4 CD B6 13 2F C0 46 E8 07 3F AA 28 A5  ......./.F..?.(.
0050  23 F1 F0 CA D3 19 3F FA 6C 7C EC 1B CF 71 EB BA  #.....?.l|...q..
0060  68 1B 8E 7D 93 7E 0B 9F DB 12 9C 75 22 5F 12 00  h..}.~.....u"_..
```
Currently I have to restart networkd every 10 seconds to keep the fuzzer going.


Discussion:
@evverx Thank you for the detailed reproducer. Fix is waiting in #31171.
What's weird is that it seems the idea behind c2eb7753dd47ec04ae0d66400e70bc87fbf1adcc was to fix things automatically (at least when failures aren't caused deliberately very often) but it doesn't seem to always work with transient one-off failures. I'll try to figure out what's going on there.
> the idea behind https://github.com/systemd/systemd/commit/c2eb7753dd47ec04ae0d66400e70bc87fbf1adcc was to fix things automatically ... it doesn't seem to always work with transient one-off failures

Looks like it was a false alarm. Turns out I sent 1000 packets instead of 1. When I actually sent 1 packet to emulate a transient failure networkd successfully reconfigured the interface.
> > the idea behind [c2eb775](https://github.com/systemd/systemd/commit/c2eb7753dd47ec04ae0d66400e70bc87fbf1adcc) was to fix things automatically ... it doesn't seem to always work with transient one-off failures
> 
> Looks like it was a false alarm. Turns out I sent 1000 packets instead of 1. When I actually sent 1 packet to emulate a transient failure networkd successfully reconfigured the interface.

Nice! Thank you!



Patch:
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
index 89681d00758b9..e50a0b966b627 100644
--- a/src/libsystemd-network/ndisc-router.c
+++ b/src/libsystemd-network/ndisc-router.c
@@ -9,6 +9,7 @@
 
 #include "alloc-util.h"
 #include "dns-domain.h"
+#include "escape.h"
 #include "hostname-util.h"
 #include "memory-util.h"
 #include "missing_network.h"
@@ -716,8 +717,12 @@ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
 
                                 e[n] = 0;
                                 r = dns_name_normalize(e, 0, &normalized);
-                                if (r < 0)
-                                        return r;
+                                if (r < 0) {
+                                        _cleanup_free_ char *escaped = cescape(e);
+                                        log_debug_errno(r, "Failed to normalize advertised domain name \"%s\": %m", strna(escaped));
+                                        /* Here, do not propagate error code from dns_name_normalize() except for ENOMEM. */
+                                        return r == -ENOMEM ? -ENOMEM : -EBADMSG;
+                                }
 
                                 /* Ignore the root domain name or "localhost" and friends */
                                 if (!is_localhost(normalized) &&
@@ -753,8 +758,12 @@ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
                         e[n++] = '.';
 
                 r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
-                if (r < 0)
-                        return r;
+                if (r < 0) {
+                        _cleanup_free_ char *escaped = cescape_length((const char*) p+1, *p);
+                        log_debug_errno(r, "Failed to escape advertised domain name \"%s\": %m", strna(escaped));
+                        /* Here, do not propagate error code from dns_label_escape() except for ENOMEM. */
+                        return r == -ENOMEM ? -ENOMEM : -EBADMSG;
+                }
 
                 n += r;
 


Test patch:
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
index d0648b95fabf1..cc00461839fe3 100644
--- a/src/libsystemd-network/test-ndisc-rs.c
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -150,6 +150,10 @@ static void router_dump(sd_ndisc_router *rt) {
                         int n, i;
 
                         n = sd_ndisc_router_dnssl_get_domains(rt, &l);
+                        if (n == -EBADMSG) {
+                                log_info("Invalid domain(s).");
+                                break;
+                        }
                         assert_se(n > 0);
 
                         for (i = 0; i < n; i++)
@@ -167,18 +171,23 @@ static void router_dump(sd_ndisc_router *rt) {
 
 static int send_ra(uint8_t flags) {
         uint8_t advertisement[] = {
+                /* struct nd_router_advert */
                 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */
                 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
                 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
                 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */
                 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
                 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+                /* type = 0x1f (SD_NDISC_OPTION_DNSSL), legnth = 24 */
                 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
                 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
                 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), legth = 8 */
                 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
         };
 
@@ -224,6 +233,7 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router
                 return;
         }
 
+        idx = 0;
         sd_event_exit(e, 0);
 }
 
@@ -261,6 +271,80 @@ TEST(rs) {
         test_fd[1] = safe_close(test_fd[1]);
 }
 
+static int send_ra_invalid_domain(uint8_t flags) {
+        uint8_t advertisement[] = {
+                /* struct nd_router_advert */
+                0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */
+                0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+                0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */
+                0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+                /* type = 0x1f (SD_NDISC_OPTION_DNSSL), legnth = 112 */
+                0x1f, 0x0e, 0xee, 0x68, 0xb0, 0xf4, 0x36, 0x39,
+                0x2c, 0xbc, 0x0b, 0xbc, 0xa9, 0x97, 0x71, 0x37,
+                0xad, 0x86, 0x80, 0x14, 0x2e, 0x58, 0xaa, 0x8a,
+                0xb7, 0xa1, 0xbe, 0x91, 0x59, 0x00, 0xc4, 0xe8,
+                0xdd, 0xd8, 0x6d, 0xe5, 0x4a, 0x7a, 0x71, 0x42,
+                0x74, 0x45, 0x9e, 0x2e, 0xfd, 0x9d, 0x71, 0x1d,
+                0xd0, 0xc0, 0x54, 0x0c, 0x4d, 0x1f, 0xbf, 0x90,
+                0xd9, 0x79, 0x58, 0xc0, 0x1d, 0xa3, 0x39, 0xcf,
+                0xb8, 0xec, 0xd2, 0xe4, 0xcd, 0xb6, 0x13, 0x2f,
+                0xc0, 0x46, 0xe8, 0x07, 0x3f, 0xaa, 0x28, 0xa5,
+                0x23, 0xf1, 0xf0, 0xca, 0xd3, 0x19, 0x3f, 0xfa,
+                0x6c, 0x7c, 0xec, 0x1b, 0xcf, 0x71, 0xeb, 0xba,
+                0x68, 0x1b, 0x8e, 0x7d, 0x93, 0x7e, 0x0b, 0x9f,
+                0xdb, 0x12, 0x9c, 0x75, 0x22, 0x5f, 0x12, 0x00,
+                /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), legth = 8 */
+                0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
+        };
+
+        advertisement[5] = flags;
+
+        printf("sizeof(nd_router_advert)=%zu\n", sizeof(struct nd_router_advert));
+
+        assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+                  sizeof(advertisement));
+
+        if (verbose)
+                printf("  sent RA with flag 0x%02x\n", flags);
+
+        return 0;
+}
+
+TEST(invalid_domain) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+        send_ra_function = send_ra_invalid_domain;
+
+        assert_se(sd_event_new(&e) >= 0);
+
+        assert_se(sd_ndisc_new(&nd) >= 0);
+        assert_se(nd);
+
+        assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
+
+        assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
+        assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
+        assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0);
+
+        assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+                                             30 * USEC_PER_SEC, 0,
+                                             NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+        assert_se(sd_ndisc_start(nd) >= 0);
+
+        assert_se(sd_event_loop(e) >= 0);
+
+        test_fd[1] = safe_close(test_fd[1]);
+}
+
 static int test_timeout_value(uint8_t flags) {
         static int count = 0;
         static usec_t last = 0;