diff options
author | Thomas Haller <thaller@redhat.com> | 2018-10-11 14:36:35 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2018-10-11 14:46:18 +0200 |
commit | 2fb6891d1ec56fd96eb3e8faf57498b1f58a6171 (patch) | |
tree | 0d08726ea5d80eb48f238a2b5674d3f59dc99a27 | |
parent | 4b0a750675d3a88989e8b389b3caac4403cc68b0 (diff) | |
download | NetworkManager-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.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 | 90 | ||||
-rw-r--r-- | src/ndisc/nm-ndisc.h | 2 |
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; |