diff options
author | Thomas Haller <thaller@redhat.com> | 2018-10-11 14:57:38 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2018-10-13 17:11:52 +0200 |
commit | 27be3e03382deb3da29e0c31dc6cb871bd6be0e8 (patch) | |
tree | 2cece978b76c3c5a8388dbdec13184fc4e361554 | |
parent | b086535cb7f28a495a742c64ed4e212175f19860 (diff) | |
download | NetworkManager-27be3e03382deb3da29e0c31dc6cb871bd6be0e8.tar.gz |
ndisc: fix updating address lifetime on Router Announcement according to RFC4862
This is a denial-of-service protection, where a malicious router
advertisement can expire the addresses.
See-also: https://github.com/systemd/systemd/commit/6554550f35a7976f9110aff94743d3576d5f02dd
See-also: https://tools.ietf.org/search/rfc4862#section-5.5.3
https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1796622
-rw-r--r-- | src/ndisc/nm-fake-ndisc.c | 2 | ||||
-rw-r--r-- | src/ndisc/nm-lndp-ndisc.c | 2 | ||||
-rw-r--r-- | src/ndisc/nm-ndisc-private.h | 2 | ||||
-rw-r--r-- | src/ndisc/nm-ndisc.c | 65 | ||||
-rw-r--r-- | src/ndisc/tests/test-ndisc-fake.c | 7 |
5 files changed, 64 insertions, 14 deletions
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c index 15abee88f1..6e9a72c6f4 100644 --- a/src/ndisc/nm-fake-ndisc.c +++ b/src/ndisc/nm-fake-ndisc.c @@ -296,7 +296,7 @@ receive_ra (gpointer user_data) .dad_counter = 0, }; - if (nm_ndisc_complete_and_add_address (ndisc, &address)) + if (nm_ndisc_complete_and_add_address (ndisc, &address, now)) changed |= NM_NDISC_CONFIG_ADDRESSES; } } diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c index 48a17bdf4f..9f427d4d59 100644 --- a/src/ndisc/nm-lndp-ndisc.c +++ b/src/ndisc/nm-lndp-ndisc.c @@ -222,7 +222,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) }; if (address.preferred <= address.lifetime) { - if (nm_ndisc_complete_and_add_address (ndisc, &address)) + if (nm_ndisc_complete_and_add_address (ndisc, &address, now)) changed |= NM_NDISC_CONFIG_ADDRESSES; } } diff --git a/src/ndisc/nm-ndisc-private.h b/src/ndisc/nm-ndisc-private.h index cdf8dc282a..ae03c0ee43 100644 --- a/src/ndisc/nm-ndisc-private.h +++ b/src/ndisc/nm-ndisc-private.h @@ -40,7 +40,7 @@ void nm_ndisc_ra_received (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed) void nm_ndisc_rs_received (NMNDisc *ndisc); gboolean nm_ndisc_add_gateway (NMNDisc *ndisc, const NMNDiscGateway *new); -gboolean nm_ndisc_complete_and_add_address (NMNDisc *ndisc, const NMNDiscAddress *new); +gboolean nm_ndisc_complete_and_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s); gboolean nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new); gboolean nm_ndisc_add_dns_server (NMNDisc *ndisc, const NMNDiscDNSServer *new); gboolean nm_ndisc_add_dns_domain (NMNDisc *ndisc, const NMNDiscDNSDomain *new); diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c index 6c619ad3ce..684fd78b4b 100644 --- a/src/ndisc/nm-ndisc.c +++ b/src/ndisc/nm-ndisc.c @@ -419,7 +419,10 @@ complete_address (NMNDisc *ndisc, NMNDiscAddress *addr) } static gboolean -nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_ra) +nm_ndisc_add_address (NMNDisc *ndisc, + const NMNDiscAddress *new, + gint32 now_s, + gboolean from_ra) { NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc); NMNDiscDataInternal *rdata = &priv->rdata; @@ -432,6 +435,7 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_r nm_assert (!IN6_IS_ADDR_UNSPECIFIED (&new->address)); nm_assert (!IN6_IS_ADDR_LINKLOCAL (&new->address)); nm_assert (new->preferred <= new->lifetime); + nm_assert (!from_ra || now_s > 0); for (i = 0; i < rdata->addresses->len; i++) { NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i); @@ -452,6 +456,51 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_r } if (existing) { + if (from_ra) { + const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */ + gint64 old_expiry_lifetime, old_expiry_preferred; + + old_expiry_lifetime = get_expiry (existing); + old_expiry_preferred = get_expiry_preferred (existing); + + if (new->lifetime == NM_NDISC_INFINITY) + existing->lifetime = NM_NDISC_INFINITY; + else { + gint64 new_lifetime, remaining_lifetime; + + /* see RFC4862 5.5.3.e */ + if (existing->lifetime == NM_NDISC_INFINITY) + remaining_lifetime = G_MAXINT64; + else + remaining_lifetime = ((gint64) existing->timestamp) + ((gint64) existing->lifetime) - ((gint64) now_s); + new_lifetime = ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s); + + if ( new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN + || new_lifetime > remaining_lifetime) { + existing->timestamp = now_s; + existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1)); + } else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) { + /* keep the current lifetime. */ + } else { + existing->timestamp = now_s; + existing->lifetime = NM_NDISC_PREFIX_LFT_MIN; + } + } + + if (new->preferred == NM_NDISC_INFINITY) { + nm_assert (existing->lifetime == NM_NDISC_INFINITY); + existing->preferred = new->preferred; + } else { + existing->preferred = NM_CLAMP (((gint64) new->timestamp) + ((gint64) new->preferred) - ((gint64) existing->timestamp), + 0, G_MAXUINT32 - 1); + if (existing->lifetime != NM_NDISC_INFINITY) + existing->preferred = MIN (existing->preferred, existing->lifetime); + } + + return old_expiry_lifetime != get_expiry (existing) + || old_expiry_preferred != get_expiry_preferred (existing); + } + if (new->lifetime == 0) { g_array_remove_index (rdata->addresses, i); return TRUE; @@ -459,12 +508,10 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_r if ( get_expiry (existing) == get_expiry (new) && get_expiry_preferred (existing) == get_expiry_preferred (new) - && ( from_ra - || existing->dad_counter == new->dad_counter)) + && existing->dad_counter == new->dad_counter) return FALSE; - if (!from_ra) - existing->dad_counter = new->dad_counter; + existing->dad_counter = new->dad_counter; existing->timestamp = new->timestamp; existing->lifetime = new->lifetime; existing->preferred = new->preferred; @@ -495,9 +542,11 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_r } gboolean -nm_ndisc_complete_and_add_address (NMNDisc *ndisc, const NMNDiscAddress *new) +nm_ndisc_complete_and_add_address (NMNDisc *ndisc, + const NMNDiscAddress *new, + gint32 now_s) { - return nm_ndisc_add_address (ndisc, new, TRUE); + return nm_ndisc_add_address (ndisc, new, now_s, TRUE); } gboolean @@ -795,7 +844,7 @@ nm_ndisc_set_config (NMNDisc *ndisc, guint i; for (i = 0; i < addresses->len; i++) { - if (nm_ndisc_add_address (ndisc, &g_array_index (addresses, NMNDiscAddress, i), FALSE)) + if (nm_ndisc_add_address (ndisc, &g_array_index (addresses, NMNDiscAddress, i), 0, FALSE)) changed = TRUE; } diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c index e99d2fc55e..268f3b49b4 100644 --- a/src/ndisc/tests/test-ndisc-fake.c +++ b/src/ndisc/tests/test-ndisc-fake.c @@ -233,8 +233,9 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed g_assert_cmpint (rdata->gateways_n, ==, 1); match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); - g_assert_cmpint (rdata->addresses_n, ==, 1); - match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10); + g_assert_cmpint (rdata->addresses_n, ==, 2); + match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0); + match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10); g_assert_cmpint (rdata->routes_n, ==, 1); match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10); g_assert_cmpint (rdata->dns_servers_n, ==, 1); @@ -384,7 +385,7 @@ test_preference_changed_cb (NMNDisc *ndisc, const NMNDiscData *rdata, guint chan match_gateway (rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_ICMPV6_ROUTER_PREF_HIGH); match_gateway (rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); g_assert_cmpint (rdata->addresses_n, ==, 2); - match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 2, 10, 10); + match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 3, 9, 9); match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10); g_assert_cmpint (rdata->routes_n, ==, 2); match_route (rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15); |