summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-10-11 14:57:38 +0200
committerThomas Haller <thaller@redhat.com>2018-10-12 13:20:51 +0200
commitb132ab7c7578a174b92d6a1b895651279ba61929 (patch)
tree44f32bd1f298dc72e4c66e48703cfaaff6a90f13
parent2346fd7878bd888eecdbe08b55ed3f42fa46f54e (diff)
downloadNetworkManager-th/ndisc-addr-lifetime.tar.gz
ndisc: fix updating address lifetime on Router Announcement according to RFC4862th/ndisc-addr-lifetime
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.c2
-rw-r--r--src/ndisc/nm-lndp-ndisc.c2
-rw-r--r--src/ndisc/nm-ndisc-private.h2
-rw-r--r--src/ndisc/nm-ndisc.c65
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c7
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);