summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-10-11 14:36:35 +0200
committerThomas Haller <thaller@redhat.com>2018-10-11 14:46:18 +0200
commit2fb6891d1ec56fd96eb3e8faf57498b1f58a6171 (patch)
tree0d08726ea5d80eb48f238a2b5674d3f59dc99a27
parent4b0a750675d3a88989e8b389b3caac4403cc68b0 (diff)
downloadNetworkManager-th/ndisc-fixes.tar.gz
ndisc: fix updating address lifetime on Router Announcement according to RFC4862th/ndisc-fixes
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.c90
-rw-r--r--src/ndisc/nm-ndisc.h2
5 files changed, 76 insertions, 22 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 122fcbd34d..54409495a6 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -124,21 +124,42 @@ _preference_to_priority (NMIcmpv6RouterPref pref)
/*****************************************************************************/
+#define _NDISC_INFINITY_TIMESTAMP G_MAXINT32
+
+static int
+min_lifetime (gint32 t1, gint32 t2)
+{
+ if (t1 == _NDISC_INFINITY_TIMESTAMP)
+ return t2;
+ if (t2 == _NDISC_INFINITY_TIMESTAMP)
+ return t1;
+ return MIN (t1, t2);
+}
+
static gint32
-get_expiry_time (guint32 timestamp, guint32 lifetime)
+get_remaining_time (gint32 now_s, guint32 timestamp, guint32 lifetime)
{
gint64 t;
+ nm_assert (now_s >= 0 && now_s <= G_MAXINT32);
+
/* timestamp is supposed to come from nm_utils_get_monotonic_timestamp_s().
* It is expected to be within a certain range. */
nm_assert (timestamp > 0);
nm_assert (timestamp <= G_MAXINT32);
if (lifetime == NM_NDISC_INFINITY)
- return G_MAXINT32;
+ return _NDISC_INFINITY_TIMESTAMP;
- t = (gint64) timestamp + (gint64) lifetime;
- return CLAMP (t, 0, G_MAXINT32 - 1);
+ t = ((gint64) timestamp) + ((gint64) lifetime) - ((gint64) now_s);
+ return CLAMP (t, ((gint64) 0), ((gint64) (_NDISC_INFINITY_TIMESTAMP - 1)));
+}
+
+
+static gint32
+get_expiry_time (guint32 timestamp, guint32 lifetime)
+{
+ return get_remaining_time (0, timestamp, lifetime);
}
#define get_expiry(item) \
@@ -170,7 +191,7 @@ _get_exp (char *buf, gsize buf_size, gint64 now_ns, gint32 expiry_time)
{
int l;
- if (expiry_time == G_MAXINT32)
+ if (expiry_time == _NDISC_INFINITY_TIMESTAMP)
return "permanent";
l = g_snprintf (buf, buf_size,
"%.4f",
@@ -406,7 +427,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;
@@ -419,6 +443,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);
@@ -439,17 +464,44 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gboolean from_r
}
if (existing) {
- if (new->lifetime == 0) {
- g_array_remove_index (rdata->addresses, i);
- return TRUE;
- }
+ if (from_ra) {
+ const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
+ gint32 new_lifetime, remaining_lifetime;
+
+ /* see RFC4862 5.5.3.e */
+ remaining_lifetime = get_remaining_time (now_s, existing->timestamp, existing->lifetime);
+ new_lifetime = get_remaining_time (now_s, new->timestamp, new->lifetime);
+
+ if ( new_lifetime == _NDISC_INFINITY_TIMESTAMP
+ || new_lifetime > NM_NDISC_PREFIX_LFT_MIN
+ || ( remaining_lifetime != _NDISC_INFINITY_TIMESTAMP
+ && new_lifetime > remaining_lifetime)) {
+ existing->timestamp = now_s;
+ existing->lifetime = new_lifetime;
+ } else if ( remaining_lifetime == _NDISC_INFINITY_TIMESTAMP
+ || remaining_lifetime <= NM_NDISC_PREFIX_LFT_MIN) {
+ /* keep the current lifetime. */
+ } else {
+ existing->timestamp = now_s;
+ existing->lifetime = NM_NDISC_PREFIX_LFT_MIN;
+ }
- if ( existing->dad_counter == new->dad_counter
- && get_expiry (existing) == get_expiry (new)
- && get_expiry_preferred (existing) == get_expiry_preferred (new))
- return FALSE;
+ existing->preferred = min_lifetime (get_remaining_time (existing->timestamp, new->timestamp, new->preferred),
+ existing->lifetime);
+ } else {
+ if (new->lifetime == 0) {
+ g_array_remove_index (rdata->addresses, i);
+ return TRUE;
+ }
+
+ if ( existing->dad_counter == new->dad_counter
+ && get_expiry (existing) == get_expiry (new)
+ && get_expiry_preferred (existing) == get_expiry_preferred (new))
+ return FALSE;
+
+ *existing = *new;
+ }
- *existing = *new;
return TRUE;
}
@@ -476,9 +528,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
@@ -776,7 +830,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/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index fdc5615f54..bff4751c2b 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -58,7 +58,7 @@ typedef enum {
NM_NDISC_DHCP_LEVEL_MANAGED
} NMNDiscDHCPLevel;
-#define NM_NDISC_INFINITY G_MAXUINT32
+#define NM_NDISC_INFINITY G_MAXUINT32
struct _NMNDiscGateway {
struct in6_addr address;