core: never fail reading host-id timestamp and never change it

The timestamp of the host-id is the timestamp of the secret_key file.
Under normal circumstances, reading the timestamp should never fail,
and reading it multiple times should always yield the same result.

If we unexpectedly fail to read the timestamp from the file we want:

- log a warning, so that the user can find out what's wrong. But
  do so only once.

- we don't want to handle errors or fail operation due to a missing
  timestamp. Remember, it's not supposed to ever fail, and if it does,
  just log a warning and proceed with a fake timestamp instead. In
  that case something is wrong, but using a non-stable, fake timestamp
  is the least of the problems here.
  We already have a stable identifier (the host-id) which we can use to
  generate a fake timestamp. Use it.

In case the user would replace the secret_key file, we also don't want
that accessing nm_utils_host_id_get_timestamp*() yields different
results. It's not implemented (nor necessary) to support reloading a
different timestamp. Hence, nm_utils_host_id_get_timestamp() should
memoize the value and ensure that it never changes.
This commit is contained in:
Thomas Haller
2018-12-12 11:04:12 +01:00
parent e9887d4816
commit a68d027ba4
3 changed files with 61 additions and 25 deletions

View File

@@ -8434,15 +8434,8 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
if (nm_streq (duid, "ll")) {
duid_out = generate_duid_ll (g_bytes_get_data (hwaddr, NULL));
} else {
gint64 time;
time = nm_utils_host_id_get_timestamp ();
if (!time) {
duid_error = "cannot retrieve the secret key timestamp";
goto out_fail;
}
duid_out = generate_duid_llt (g_bytes_get_data (hwaddr, NULL), time);
duid_out = generate_duid_llt (g_bytes_get_data (hwaddr, NULL),
nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND);
}
goto out_good;
@@ -8492,11 +8485,8 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
* before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
* subtract it from the host_id timestamp.
*/
time = nm_utils_host_id_get_timestamp ();
if (!time) {
duid_error = "cannot retrieve the secret key timestamp";
goto out_fail;
}
time = nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND;
/* don't use too old timestamps. They cannot be expressed in DUID-LLT and
* would all be truncated to zero. */
time = NM_MAX (time, EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS);

View File

@@ -2525,6 +2525,49 @@ nm_utils_machine_id_is_fake (void)
#define SECRET_KEY_V2_PREFIX "nm-v2:"
#define SECRET_KEY_FILE NMSTATEDIR"/secret_key"
static gboolean
_host_id_read_timestamp (gboolean use_secret_key_file,
const guint8 *host_id,
gsize host_id_len,
gint64 *out_timestamp_ns)
{
struct stat st;
gint64 now;
guint64 v;
if ( use_secret_key_file
&& stat (SECRET_KEY_FILE, &st) == 0) {
/* don't check for overflow or timestamps in the future. We get whatever
* (bogus) date is on the file. */
*out_timestamp_ns = (st.st_mtim.tv_sec * NM_UTILS_NS_PER_SECOND) + st.st_mtim.tv_nsec;
return TRUE;
}
/* generate a fake timestamp based on the host-id.
*
* This really should never happen under normal circumstances. We already
* are in a code path, where the system has a problem (unable to get good randomness
* and/or can't access the secret_key). In such a scenario, a fake timestamp is the
* least of our problems.
*
* At least, generate something sensible so we don't have to worry about the
* timestamp. It is wrong to worry about using a fake timestamp (which is tied to
* the secret_key) if we are unable to access the secret_key file in the first place.
*
* Pick a random timestamp from the past two years. Yes, this timestamp
* is not stable accross restarts, but apparently neither is the host-id
* nor the secret_key itself. */
#define EPOCH_TWO_YEARS (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NS_PER_SECOND)
v = nm_hash_siphash42 (1156657133u, host_id, host_id_len);
now = time (NULL);
*out_timestamp_ns = NM_MAX ((gint64) 1,
(now * NM_UTILS_NS_PER_SECOND) - ((gint64) (v % ((guint64) (EPOCH_TWO_YEARS)))));
return FALSE;
}
static const guint8 *
_host_id_hash_v2 (const guint8 *seed_arr,
gsize seed_len,
@@ -2672,7 +2715,9 @@ out:
typedef struct {
guint8 *host_id;
gsize host_id_len;
gint64 timestamp_ns;
bool is_good:1;
bool timestamp_is_good:1;
} HostIdData;
static const HostIdData *
@@ -2692,6 +2737,15 @@ again:
host_id_data.is_good = _host_id_read (&host_id_data.host_id,
&host_id_data.host_id_len);
host_id_data.timestamp_is_good = _host_id_read_timestamp (host_id_data.is_good,
host_id_data.host_id,
host_id_data.host_id_len,
&host_id_data.timestamp_ns);
if ( !host_id_data.timestamp_is_good
&& host_id_data.is_good)
nm_log_warn (LOGD_CORE, "secret-key: failure reading host timestamp (use fake one)");
host_id = &host_id_data;
g_atomic_pointer_set (&host_id_static, host_id);
g_once_init_leave (&init_value, 1);
@@ -2728,17 +2782,9 @@ nm_utils_host_id_get (const guint8 **out_host_id,
}
gint64
nm_utils_host_id_get_timestamp (void)
nm_utils_host_id_get_timestamp_ns (void)
{
struct stat stat_buf;
if (!_host_id_get ()->is_good)
return 0;
if (stat (SECRET_KEY_FILE, &stat_buf) != 0)
return 0;
return stat_buf.st_mtim.tv_sec;
return _host_id_get ()->timestamp_ns;
}
/*****************************************************************************/

View File

@@ -280,7 +280,7 @@ const struct _NMUuid *nm_utils_boot_id_bin (void);
gboolean nm_utils_host_id_get (const guint8 **out_host_id,
gsize *out_host_id_len);
gint64 nm_utils_host_id_get_timestamp (void);
gint64 nm_utils_host_id_get_timestamp_ns (void);
/* IPv6 Interface Identifier helpers */