about summary refs log tree commit diff
path: root/src/libnm-glib-aux
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2021-08-25 15:24:42 +0200
committerSebastien Bacher <seb128@ubuntu.com>2021-08-25 15:24:42 +0200
commitdbb91282fa488964fb20595f9494a9f0e4f36a58 (patch)
tree142bc942e5320b35514cdf03a5f9f47a9b89df0e /src/libnm-glib-aux
parent5f2ede3a2813b0e9204befdcfc67509d34be71c6 (diff)
parentcfb80376641fa49137b9996130352697e7f8b436 (diff)
Update upstream source from tag 'upstream/1.32.10'
Update to upstream version '1.32.10'
with Debian dir fcf2778b50b013ede3e7375bc1d829e984175658
Diffstat (limited to 'src/libnm-glib-aux')
-rw-r--r--src/libnm-glib-aux/nm-dedup-multi.h46
-rw-r--r--src/libnm-glib-aux/nm-errno.c14
-rw-r--r--src/libnm-glib-aux/nm-io-utils.c64
-rw-r--r--src/libnm-glib-aux/nm-io-utils.h2
-rw-r--r--src/libnm-glib-aux/nm-keyfile-aux.c118
-rw-r--r--src/libnm-glib-aux/nm-keyfile-aux.h6
-rw-r--r--src/libnm-glib-aux/nm-macros-internal.h8
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c297
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c102
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h47
-rw-r--r--src/libnm-glib-aux/nm-str-buf.h7
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c44
12 files changed, 643 insertions, 112 deletions
diff --git a/src/libnm-glib-aux/nm-dedup-multi.h b/src/libnm-glib-aux/nm-dedup-multi.h
index 9a995ec5..fb1cb863 100644
--- a/src/libnm-glib-aux/nm-dedup-multi.h
+++ b/src/libnm-glib-aux/nm-dedup-multi.h
@@ -309,8 +309,11 @@ guint nm_dedup_multi_index_dirty_remove_idx(NMDedupMultiIndex *  self,
 /*****************************************************************************/
 
 typedef struct _NMDedupMultiIter {
-    const CList *            _head;
-    const CList *            _next;
+    const CList *_head;
+    union {
+        const CList *_next;
+        const CList *_prev;
+    };
     const NMDedupMultiEntry *current;
 } NMDedupMultiIter;
 
@@ -329,13 +332,30 @@ nm_dedup_multi_iter_init(NMDedupMultiIter *iter, const NMDedupMultiHeadEntry *he
     iter->current = NULL;
 }
 
+static inline void
+nm_dedup_multi_iter_init_reverse(NMDedupMultiIter *iter, const NMDedupMultiHeadEntry *head)
+{
+    g_return_if_fail(iter);
+
+    if (head && !c_list_is_empty(&head->lst_entries_head)) {
+        iter->_head = &head->lst_entries_head;
+        iter->_prev = head->lst_entries_head.prev;
+    } else {
+        iter->_head = NULL;
+        iter->_prev = NULL;
+    }
+    iter->current = NULL;
+}
+
 static inline gboolean
 nm_dedup_multi_iter_next(NMDedupMultiIter *iter)
 {
     g_return_val_if_fail(iter, FALSE);
 
-    if (!iter->_next)
+    if (!iter->_next) {
+        iter->current = NULL;
         return FALSE;
+    }
 
     /* we always look ahead for the next. This way, the user
      * may delete the current entry (but no other entries). */
@@ -347,6 +367,26 @@ nm_dedup_multi_iter_next(NMDedupMultiIter *iter)
     return TRUE;
 }
 
+static inline gboolean
+nm_dedup_multi_iter_prev(NMDedupMultiIter *iter)
+{
+    g_return_val_if_fail(iter, FALSE);
+
+    if (!iter->_prev) {
+        iter->current = NULL;
+        return FALSE;
+    }
+
+    /* we always look ahead for the prev. This way, the user
+     * may delete the current entry (but no other entries). */
+    iter->current = c_list_entry(iter->_prev, NMDedupMultiEntry, lst_entries);
+    if (iter->_prev->prev == iter->_head)
+        iter->_prev = NULL;
+    else
+        iter->_prev = iter->_prev->prev;
+    return TRUE;
+}
+
 #define nm_dedup_multi_iter_for_each(iter, head_entry) \
     for (nm_dedup_multi_iter_init((iter), (head_entry)); nm_dedup_multi_iter_next((iter));)
 
diff --git a/src/libnm-glib-aux/nm-errno.c b/src/libnm-glib-aux/nm-errno.c
index 283173e3..0426a21d 100644
--- a/src/libnm-glib-aux/nm-errno.c
+++ b/src/libnm-glib-aux/nm-errno.c
@@ -7,8 +7,6 @@
 
 #include "nm-errno.h"
 
-#include <pthread.h>
-
 /*****************************************************************************/
 
 static NM_UTILS_LOOKUP_STR_DEFINE(
@@ -162,19 +160,9 @@ nm_strerror_native(int errsv)
 
     buf = buf_static;
     if (G_UNLIKELY(!buf)) {
-        int           errno_saved = errno;
-        pthread_key_t key;
-
         buf        = g_malloc(NM_STRERROR_BUFSIZE);
         buf_static = buf;
-
-        if (pthread_key_create(&key, g_free) != 0 || pthread_setspecific(key, buf) != 0) {
-            /* Failure. We will leak the buffer when the thread exits.
-             *
-             * Nothing we can do about it really. For Debug builds we fail with an assertion. */
-            nm_assert_not_reached();
-        }
-        errno = errno_saved;
+        nm_utils_thread_local_register_destroy(buf, g_free);
     }
 
     return nm_strerror_native_r(errsv, buf, NM_STRERROR_BUFSIZE);
diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c
index 894c8726..87478a2d 100644
--- a/src/libnm-glib-aux/nm-io-utils.c
+++ b/src/libnm-glib-aux/nm-io-utils.c
@@ -564,3 +564,67 @@ nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_mse
                                                    NULL),
                            main_context);
 }
+
+/*****************************************************************************/
+
+char **
+nm_utils_find_mkstemp_files(const char *dirname, const char *filename)
+{
+    static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+    DIR *             dir;
+    struct dirent *   entry;
+    GPtrArray *       arr = NULL;
+    gsize             l;
+
+    /* We write files with g_file_set_contents() and nm_utils_file_set_contents().
+     * These create temporary files using g_mkstemp_full(), with a random .XXXXXX suffix.
+     *
+     * If NetworkManager crashes while writing the file, then those temporary files are
+     * left over. We might want to find and delete such files.
+     *
+     * Beware: only delete such files if you are in full control about which files are
+     * supposed to be in the directory. For example, NetworkManager controls
+     * /var/lib/NetworkManager/timestamps files, and it thus takes the right to delete
+     * all files /var/lib/NetworkManager/timestamps.XXXXXX. That may not be appropriate
+     * in other cases! */
+
+    if (!dirname || !filename || !filename[0])
+        return NULL;
+
+    dir = opendir(dirname);
+    if (!dir)
+        return NULL;
+
+    l = strlen(filename);
+
+    while ((entry = readdir(dir))) {
+        const char *f = entry->d_name;
+        guint       i;
+
+        if (strncmp(f, filename, l) != 0)
+            goto next;
+        if (f[l] != '.')
+            goto next;
+        for (i = 1; i <= 6; i++) {
+            /* @letters is also what g_mkstemp_full() does! */
+            if (!memchr(letters, f[l + i], G_N_ELEMENTS(letters)))
+                goto next;
+        }
+        if (f[l + 7] != '\0')
+            goto next;
+
+        if (!arr)
+            arr = g_ptr_array_new();
+
+        g_ptr_array_add(arr, g_strdup(f));
+next:;
+    }
+
+    closedir(dir);
+
+    if (!arr)
+        return NULL;
+
+    g_ptr_array_add(arr, NULL);
+    return (char **) g_ptr_array_free(arr, FALSE);
+}
diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h
index 98e63ac0..31ff6d05 100644
--- a/src/libnm-glib-aux/nm-io-utils.h
+++ b/src/libnm-glib-aux/nm-io-utils.h
@@ -58,4 +58,6 @@ int nm_utils_file_stat(const char *filename, struct stat *out_st);
 
 void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_msec_before_kill);
 
+char **nm_utils_find_mkstemp_files(const char *dirname, const char *filename);
+
 #endif /* __NM_IO_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-keyfile-aux.c b/src/libnm-glib-aux/nm-keyfile-aux.c
index 75abe538..9cda1cf7 100644
--- a/src/libnm-glib-aux/nm-keyfile-aux.c
+++ b/src/libnm-glib-aux/nm-keyfile-aux.c
@@ -27,6 +27,8 @@ struct _NMKeyFileDB {
     bool dirty : 1;
     bool destroyed : 1;
 
+    bool groups_pruned : 1;
+
     char filename[];
 };
 
@@ -62,6 +64,16 @@ _IS_KEY_FILE_DB(NMKeyFileDB *self, gboolean require_is_started, gboolean allow_d
     return TRUE;
 }
 
+static GKeyFile *
+_key_file_new(void)
+{
+    GKeyFile *kf;
+
+    kf = g_key_file_new();
+    g_key_file_set_list_separator(kf, ',');
+    return kf;
+}
+
 /*****************************************************************************/
 
 NMKeyFileDB *
@@ -86,8 +98,7 @@ nm_key_file_db_new(const char *           filename,
     self->log_fcn       = log_fcn;
     self->got_dirty_fcn = got_dirty_fcn;
     self->user_data     = user_data;
-    self->kf            = g_key_file_new();
-    g_key_file_set_list_separator(self->kf, ',');
+    self->kf            = _key_file_new();
     memcpy(self->filename, filename, l_filename + 1);
     self->group_name = &self->filename[l_filename + 1];
     memcpy((char *) self->group_name, group_name, l_group + 1);
@@ -371,3 +382,106 @@ nm_key_file_db_to_file(NMKeyFileDB *self, gboolean force)
     } else
         _LOGD("write keyfile: \"%s\"", self->filename);
 }
+
+/*****************************************************************************/
+
+void
+nm_key_file_db_prune_tmp_files(NMKeyFileDB *self)
+{
+    gs_free char *     n_file   = NULL;
+    gs_free char *     n_dir    = NULL;
+    gs_strfreev char **tmpfiles = NULL;
+    gsize              i;
+
+    n_file = g_path_get_basename(self->filename);
+    n_dir  = g_path_get_dirname(self->filename);
+
+    tmpfiles = nm_utils_find_mkstemp_files(n_dir, n_file);
+    if (!tmpfiles)
+        return;
+
+    for (i = 0; tmpfiles[i]; i++) {
+        const char *  tmpfile   = tmpfiles[i];
+        gs_free char *full_file = NULL;
+        int           r;
+
+        full_file = g_strdup_printf("%s/%s", n_dir, tmpfile);
+
+        r = unlink(full_file);
+        if (r != 0) {
+            int errsv = errno;
+
+            if (errsv != ENOENT) {
+                _LOGD("prune left over temp file %s failed: %s",
+                      full_file,
+                      nm_strerror_native(errsv));
+            }
+            continue;
+        }
+
+        _LOGD("prune left over temp file %s", full_file);
+    }
+}
+
+/*****************************************************************************/
+
+void
+nm_key_file_db_prune(NMKeyFileDB *self,
+                     gboolean (*predicate)(const char *key, gpointer user_data),
+                     gpointer user_data)
+{
+    gs_strfreev char **   keys                 = NULL;
+    nm_auto_unref_keyfile GKeyFile *kf_to_free = NULL;
+    GKeyFile *                      kf_src     = NULL;
+    GKeyFile *                      kf_dst     = NULL;
+    guint                           k;
+
+    g_return_if_fail(_IS_KEY_FILE_DB(self, TRUE, FALSE));
+    nm_assert(predicate);
+
+    _LOGD("prune keyfile of old entries: \"%s\"", self->filename);
+
+    if (!self->groups_pruned) {
+        /* When we prune the first time, we swap the GKeyfile instance.
+         * The instance loaded from disk might have unrelated groups and
+         * comments. Let's get rid of them by creating a new instance.
+         *
+         * Otherwise, we know that self->kf only contains good keys,
+         * and at most we need to remove some of them. */
+        kf_to_free          = g_steal_pointer(&self->kf);
+        self->kf            = _key_file_new();
+        kf_src              = kf_to_free;
+        self->groups_pruned = TRUE;
+        self->dirty         = TRUE;
+    } else
+        kf_src = self->kf;
+    kf_dst = self->kf;
+
+    keys = g_key_file_get_keys(kf_src, self->group_name, NULL, NULL);
+    if (keys) {
+        for (k = 0; keys[k]; k++) {
+            const char *key = keys[k];
+            gboolean    keep;
+
+            keep = predicate(key, user_data);
+
+            if (!keep) {
+                if (kf_dst == kf_src) {
+                    g_key_file_remove_key(kf_dst, self->group_name, key, NULL);
+                    self->dirty = TRUE;
+                }
+                continue;
+            }
+
+            if (kf_dst != kf_src) {
+                gs_free char *value = NULL;
+
+                value = g_key_file_get_value(kf_src, self->group_name, key, NULL);
+                if (value)
+                    g_key_file_set_value(kf_dst, self->group_name, key, value);
+                else
+                    self->dirty = TRUE;
+            }
+        }
+    }
+}
diff --git a/src/libnm-glib-aux/nm-keyfile-aux.h b/src/libnm-glib-aux/nm-keyfile-aux.h
index 72d2f418..e756c57a 100644
--- a/src/libnm-glib-aux/nm-keyfile-aux.h
+++ b/src/libnm-glib-aux/nm-keyfile-aux.h
@@ -50,6 +50,12 @@ void nm_key_file_db_set_string_list(NMKeyFileDB *      self,
 
 void nm_key_file_db_to_file(NMKeyFileDB *self, gboolean force);
 
+void nm_key_file_db_prune_tmp_files(NMKeyFileDB *self);
+
+void nm_key_file_db_prune(NMKeyFileDB *self,
+                          gboolean (*predicate)(const char *key, gpointer user_data),
+                          gpointer user_data);
+
 /*****************************************************************************/
 
 #endif /* __NM_KEYFILE_AUX_H__ */
diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h
index 39197ecf..f2d81e1c 100644
--- a/src/libnm-glib-aux/nm-macros-internal.h
+++ b/src/libnm-glib-aux/nm-macros-internal.h
@@ -121,14 +121,6 @@ _nm_auto_free_gstring(GString **str)
 }
 #define nm_auto_free_gstring nm_auto(_nm_auto_free_gstring)
 
-static inline void
-_nm_auto_protect_errno(const int *p_saved_errno)
-{
-    errno = *p_saved_errno;
-}
-#define NM_AUTO_PROTECT_ERRNO(errsv_saved) \
-    nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno)
-
 NM_AUTO_DEFINE_FCN0(GSource *, _nm_auto_unref_gsource, g_source_unref);
 #define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource)
 
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 56b99d5e..b055bc3f 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -8,6 +8,8 @@
 #include "nm-random-utils.h"
 
 #include <fcntl.h>
+#include <sys/auxv.h>
+#include <sys/syscall.h>
 
 #if USE_SYS_RANDOM_H
     #include <sys/random.h>
@@ -16,9 +18,185 @@
 #endif
 
 #include "nm-shared-utils.h"
+#include "nm-time-utils.h"
 
 /*****************************************************************************/
 
+#if !defined(SYS_getrandom) && defined(__NR_getrandom)
+    #define SYS_getrandom __NR_getrandom
+#endif
+
+#ifndef GRND_NONBLOCK
+    #define GRND_NONBLOCK 0x01
+#endif
+
+#ifndef GRND_INSECURE
+    #define GRND_INSECURE 0x04
+#endif
+
+#if !HAVE_GETRANDOM && defined(SYS_getrandom)
+static int
+getrandom(void *buf, size_t buflen, unsigned flags)
+{
+    return syscall(SYS_getrandom, buf, buflen, flags);
+}
+    #undef HAVE_GETRANDOM
+    #define HAVE_GETRANDOM 1
+#endif
+
+/*****************************************************************************/
+
+typedef struct _nm_packed {
+    uintptr_t heap_ptr;
+    uintptr_t stack_ptr;
+    gint64    now_bootime;
+    gint64    now_real;
+    pid_t     pid;
+    pid_t     ppid;
+    pid_t     tid;
+    guint32   grand[16];
+    guint8    auxval[16];
+    guint8    getrandom_buf[20];
+} BadRandSeed;
+
+typedef struct _nm_packed {
+    guint64 counter;
+    union {
+        guint8 full[NM_UTILS_CHECKSUM_LENGTH_SHA256];
+        struct {
+            guint8 half_1[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
+            guint8 half_2[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
+        };
+    } sha_digest;
+    union {
+        guint8  u8[NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2];
+        guint32 u32[((NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2) + 3) / 4];
+    } rand_vals;
+    GRand *rand;
+} BadRandState;
+
+static void
+_bad_random_init_seed(BadRandSeed *seed)
+{
+    const guint8 *p_at_random;
+    int           seed_idx;
+    GRand *       rand;
+
+    /* g_rand_new() reads /dev/urandom, but we already noticed that
+     * /dev/urandom fails to give us good randomness (which is why
+     * we hit the "bad randomness" code path). So this may not be as
+     * good as we wish, but let's hope that it it does something smart
+     * to give some extra entropy... */
+    rand = g_rand_new();
+
+    /* Get some seed material from a GRand. */
+    for (seed_idx = 0; seed_idx < (int) G_N_ELEMENTS(seed->grand); seed_idx++)
+        seed->grand[seed_idx] = g_rand_int(rand);
+
+    /* Add an address from the heap and stack, maybe ASLR helps a bit? */
+    seed->heap_ptr  = (uintptr_t) ((gpointer) rand);
+    seed->stack_ptr = (uintptr_t) ((gpointer) &rand);
+
+    g_rand_free(rand);
+
+    /* Add the per-process, random number. */
+    p_at_random = ((gpointer) getauxval(AT_RANDOM));
+    if (p_at_random) {
+        G_STATIC_ASSERT(sizeof(seed->auxval) == 16);
+        memcpy(&seed->auxval, p_at_random, 16);
+    }
+
+#if HAVE_GETRANDOM
+    {
+        ssize_t r;
+
+        /* This is likely to fail, because we already failed a moment earlier. Still, give
+         * it a try. */
+        r = getrandom(seed->getrandom_buf,
+                      sizeof(seed->getrandom_buf),
+                      GRND_INSECURE | GRND_NONBLOCK);
+        (void) r;
+    }
+#endif
+
+    seed->now_bootime = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME);
+    seed->now_real    = g_get_real_time();
+    seed->pid         = getpid();
+    seed->ppid        = getppid();
+    seed->tid         = nm_utils_gettid();
+}
+
+static void
+_bad_random_bytes(guint8 *buf, gsize n)
+{
+    nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256);
+
+    nm_assert(n > 0);
+
+    /* We are in the fallback code path, where getrandom() (and /dev/urandom) failed
+     * to give us good randomness. Try our best.
+     *
+     * Our ability to get entropy for the CPRNG is very limited and thus the overall
+     * result will not be good randomness. See _bad_random_init_seed().
+     *
+     * Once we have some seed material, we combine GRand (which is not a cryptographically
+     * secure PRNG) with some iterative sha256 hashing. It would be nice if we had
+     * easy access to chacha20, but it's probably more cumbersome to fork those
+     * implementations than hack a bad CPRNG by using sha256 hashing. After all, this
+     * is fallback code to get *some* randomness. And with the inability to get a good
+     * seed, the CPRNG is not going to give us truly good randomness. */
+
+    {
+        static BadRandState gl_state;
+        static GRand *      gl_rand;
+        static GMutex       gl_mutex;
+        NM_G_MUTEX_LOCKED(&gl_mutex);
+
+        if (G_UNLIKELY(!gl_rand)) {
+            union {
+                BadRandSeed d_seed;
+                guint32     d_u32[(sizeof(BadRandSeed) + 3) / 4];
+            } data = {
+                .d_u32 = {0},
+            };
+
+            _bad_random_init_seed(&data.d_seed);
+
+            gl_rand = g_rand_new_with_seed_array(data.d_u32, G_N_ELEMENTS(data.d_u32));
+
+            g_checksum_update(sum, (const guchar *) &data, sizeof(data));
+            nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
+        }
+
+        while (TRUE) {
+            int i;
+
+            gl_state.counter++;
+            for (i = 0; i < G_N_ELEMENTS(gl_state.rand_vals.u32); i++)
+                gl_state.rand_vals.u32[i] = g_rand_int(gl_rand);
+            g_checksum_reset(sum);
+            g_checksum_update(sum, (const guchar *) &gl_state, sizeof(gl_state));
+            nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
+
+            /* gl_state.sha_digest.full and gl_state.rand_vals contain now our
+             * random values, but they are also the state for the next iteration.
+             * We must not directly expose that state to the caller, so XOR the values.
+             *
+             * That means, per iteration we can generate 16 bytes of randomness. That
+             * is for example required to generate a random UUID. */
+            for (i = 0; i < (int) (NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2); i++) {
+                nm_assert(n > 0);
+                buf[0] = gl_state.sha_digest.half_1[i] ^ gl_state.sha_digest.half_2[i]
+                         ^ gl_state.rand_vals.u8[i];
+                buf++;
+                n--;
+                if (n == 0)
+                    return;
+            }
+        }
+    }
+}
+
 /**
  * nm_utils_random_bytes:
  * @p: the buffer to fill
@@ -46,9 +224,7 @@ nm_utils_random_bytes(void *p, size_t n)
     int      fd;
     int      r;
     gboolean has_high_quality = TRUE;
-    gboolean urandom_success;
-    guint8 * buf           = p;
-    gboolean avoid_urandom = FALSE;
+    guint8 * buf              = p;
 
     g_return_val_if_fail(p, FALSE);
     g_return_val_if_fail(n > 0, FALSE);
@@ -58,91 +234,66 @@ nm_utils_random_bytes(void *p, size_t n)
         static gboolean have_syscall = TRUE;
 
         if (have_syscall) {
-            r = getrandom(buf, n, GRND_NONBLOCK);
-            if (r > 0) {
-                if ((size_t) r == n)
+            ssize_t r2;
+            int     errsv;
+
+            r2 = getrandom(buf, n, GRND_NONBLOCK);
+            if (r2 >= 0) {
+                if ((size_t) r2 == n)
                     return TRUE;
 
                 /* no or partial read. There is not enough entropy.
-                 * Fill the rest reading from urandom, and remember that
-                 * some bits are not high quality. */
-                nm_assert(r < n);
-                buf += r;
-                n -= r;
-                has_high_quality = FALSE;
+                 * Fill the rest reading with the fallback code and remember
+                 * that some bits are not high quality. */
+                nm_assert((size_t) r2 < n);
+                buf += r2;
+                n -= r2;
 
                 /* At this point, we don't want to read /dev/urandom, because
                  * the entropy pool is low (early boot?), and asking for more
                  * entropy causes kernel messages to be logged.
                  *
-                 * We use our fallback via GRand. Note that g_rand_new() also
-                 * tries to seed itself with data from /dev/urandom, but since
-                 * we reuse the instance, it shouldn't matter. */
-                avoid_urandom = TRUE;
+                 * Note that we fall back to _bad_random_bytes(), which (among others) seeds
+                 * itself with g_rand_new(). That also will read /dev/urandom, but as
+                 * we do that only once, we don't care. But in general, we are here in
+                 * a situation where we want to avoid reading /dev/urandom too much. */
+                goto out_bad_random;
+            }
+            errsv = errno;
+            if (errsv == ENOSYS) {
+                /* no support for getrandom(). We don't know whether
+                 * we /dev/urandom will give us good quality. Assume yes. */
+                have_syscall = FALSE;
+            } else if (errsv == EAGAIN) {
+                /* No entropy. We avoid reading /dev/urandom. */
+                goto out_bad_random;
             } else {
-                if (errno == ENOSYS) {
-                    /* no support for getrandom(). We don't know whether
-                     * we urandom will give us good quality. Assume yes. */
-                    have_syscall = FALSE;
-                } else {
-                    /* unknown error. We'll read urandom below, but we don't have
-                     * high-quality randomness. */
-                    has_high_quality = FALSE;
-                }
+                /* Unknown error, likely no entropy. We'll read /dev/urandom below, but we don't
+                 * have high-quality randomness. */
+                has_high_quality = FALSE;
             }
         }
     }
 #endif
 
-    urandom_success = FALSE;
-    if (!avoid_urandom) {
 fd_open:
-        fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
-        if (fd < 0) {
-            r = errno;
-            if (r == EINTR)
-                goto fd_open;
-        } else {
-            r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE);
-            nm_close(fd);
-            if (r >= 0)
-                urandom_success = TRUE;
-        }
-    }
-
-    if (!urandom_success) {
-        static _nm_thread_local GRand *rand = NULL;
-        gsize                          i;
-        int                            j;
-
-        /* we failed to fill the bytes reading from urandom.
-         * Fill the bits using GRand pseudo random numbers.
-         *
-         * We don't have good quality.
-         */
-        has_high_quality = FALSE;
-
-        if (G_UNLIKELY(!rand))
-            rand = g_rand_new();
-
-        nm_assert(n > 0);
-        i = 0;
-        for (;;) {
-            const union {
-                guint32 v32;
-                guint8  v8[4];
-            } v = {
-                .v32 = g_rand_int(rand),
-            };
-
-            for (j = 0; j < 4;) {
-                buf[i++] = v.v8[j++];
-                if (i >= n)
-                    goto done;
-            }
-        }
-done:;
+    fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
+    if (fd < 0) {
+        if (errno == EINTR)
+            goto fd_open;
+        goto out_bad_random;
     }
+    r = nm_utils_fd_read_loop_exact(fd, buf, n, TRUE);
+    nm_close(fd);
+    if (r >= 0)
+        return has_high_quality;
 
-    return has_high_quality;
+out_bad_random:
+    /* we failed to fill the bytes reading from /dev/urandom.
+     * Fill the bits using our pseudo random numbers.
+     *
+     * We don't have good quality.
+     */
+    _bad_random_bytes(buf, n);
+    return FALSE;
 }
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index e42e0fe5..81852aea 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -15,7 +15,9 @@
 #include <glib-unix.h>
 #include <net/if.h>
 #include <net/ethernet.h>
+#include <pthread.h>
 
+#include "c-list/src/c-list.h"
 #include "nm-errno.h"
 #include "nm-str-buf.h"
 
@@ -2990,13 +2992,13 @@ nm_utils_buf_utf8safe_escape(gconstpointer           buf,
     if (g_utf8_validate(str, buflen, &p) && nul_terminated) {
         /* note that g_utf8_validate() does not allow NUL character inside @str. Good.
          * We can treat @str like a NUL terminated string. */
-        if (!NM_STRCHAR_ANY(
-                str,
-                ch,
-                (ch == '\\'
-                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ')
-                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                     && ((guchar) ch) >= 127))))
+        if (!NM_STRCHAR_ANY(str,
+                            ch,
+                            (ch == '\\'
+                             || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
+                                 && nm_ascii_is_ctrl_or_del(ch))
+                             || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
+                                 && nm_ascii_is_non_ascii(ch)))))
             return str;
     }
 
@@ -3013,9 +3015,10 @@ nm_utils_buf_utf8safe_escape(gconstpointer           buf,
             nm_assert(ch);
             if (ch == '\\')
                 nm_str_buf_append_c(&strbuf, '\\', '\\');
-            else if ((NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ')
+            else if ((NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
+                      && nm_ascii_is_ctrl_or_del(ch))
                      || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                         && ((guchar) ch) >= 127))
+                         && nm_ascii_is_non_ascii(ch)))
                 _str_buf_append_c_escape_octal(&strbuf, ch);
             else
                 nm_str_buf_append_c(&strbuf, ch);
@@ -4932,14 +4935,14 @@ _nm_g_source_sentinel_get_init(GSource **p_source)
     };
     GSource *source;
 
-again:
     source = g_source_new((GSourceFuncs *) &source_funcs, sizeof(GSource));
     g_source_set_priority(source, G_PRIORITY_DEFAULT_IDLE);
     g_source_set_name(source, "nm_g_source_sentinel");
 
     if (!g_atomic_pointer_compare_and_exchange(p_source, NULL, source)) {
         g_source_unref(source);
-        goto again;
+        source = g_atomic_pointer_get(p_source);
+        nm_assert(source);
     }
 
     return source;
@@ -6357,3 +6360,80 @@ nm_utils_get_process_exit_status_desc(int status)
     else
         return g_strdup_printf("exited with unknown status 0x%x", status);
 }
+
+/*****************************************************************************/
+
+typedef struct {
+    CList          lst;
+    gpointer       tls_data;
+    GDestroyNotify destroy_notify;
+} TlsRegData;
+
+static pthread_key_t _tls_reg_key;
+
+static void
+_tls_reg_destroy(gpointer data)
+{
+    CList *     lst_head = data;
+    TlsRegData *entry;
+
+    if (!lst_head)
+        return;
+
+    /* For no strong reason are we destroying the elements in reverse
+     * order than they were added. It seems a bit more sensible (but shouldn't
+     * matter nor should you rely on that). */
+    while ((entry = c_list_last_entry(lst_head, TlsRegData, lst))) {
+        c_list_unlink_stale(&entry->lst);
+        entry->destroy_notify(entry->tls_data);
+        nm_g_slice_free(entry);
+    }
+
+    nm_g_slice_free(lst_head);
+}
+
+static void
+_tls_reg_make_key(void)
+{
+    if (pthread_key_create(&_tls_reg_key, _tls_reg_destroy) != 0)
+        g_return_if_reached();
+}
+
+/**
+ * nm_utils_thread_local_register_destroy:
+ * @tls_data: the thread local storage data that should be destroyed when the thread
+ *   exits. This pointer will be "owned" by the current thread. There is no way
+ *   to un-register the destruction.
+ * @destroy_notify: the free function that will be called when the thread exits.
+ *
+ * If _nm_tread_local storage is heap allocated it requires freeing the pointer
+ * when the thread exits. Use this function to register the pointer to be
+ * released.
+ *
+ * This function does not change errno.
+ */
+void
+nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify)
+{
+    NM_AUTO_PROTECT_ERRNO(errsv);
+    static pthread_once_t key_once = PTHREAD_ONCE_INIT;
+    CList *               lst_head;
+    TlsRegData *          entry;
+
+    nm_assert(destroy_notify);
+
+    if (pthread_once(&key_once, _tls_reg_make_key) != 0)
+        g_return_if_reached();
+
+    if ((lst_head = pthread_getspecific(_tls_reg_key)) == NULL) {
+        lst_head = g_slice_new(CList);
+        c_list_init(lst_head);
+        if (pthread_setspecific(_tls_reg_key, lst_head) != 0)
+            g_return_if_reached();
+    }
+
+    entry                 = g_slice_new(TlsRegData);
+    entry->tls_data       = tls_data;
+    entry->destroy_notify = destroy_notify;
+    c_list_link_tail(lst_head, &entry->lst);
+}
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index f5b1d00c..dcf37cd3 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -1258,6 +1258,7 @@ typedef enum {
     NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
     NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
     NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+    NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED,
 
     NM_UTILS_ERROR_SETTING_MISSING,
 
@@ -1446,10 +1447,14 @@ GType nm_g_type_find_implementing_class_for_property(GType gtype, const char *pn
 typedef enum {
     NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0,
 
-    /* This flag only has an effect during escaping. */
+    /* This flag only has an effect during escaping.
+     *
+     * It will backslash escape ascii characters according to nm_ascii_is_ctrl_or_del(). */
     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001,
 
-    /* This flag only has an effect during escaping. */
+    /* This flag only has an effect during escaping.
+     *
+     * It will backslash escape ascii characters according to nm_ascii_is_non_ascii(). */
     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002,
 
     /* This flag only has an effect during escaping to ensure we
@@ -2438,6 +2443,40 @@ nm_hexchar(int x, gboolean upper_case)
     return upper_case ? _nm_hexchar_table_upper[x & 15] : _nm_hexchar_table_lower[x & 15];
 }
 
+static inline gboolean
+nm_ascii_is_ctrl(char ch)
+{
+    /* 0 to ' '-1 is the C0 range.
+     *
+     * Other ranges may also be considered control characters, but NOT
+     * CONSIDERED by this function. For example:
+     *   - DEL (127) is also a control character.
+     *   - SP (' ', 0x20) is also considered a control character.
+     *   - DEL+1 (0x80) to 0x9F is C1 range.
+     *   - NBSP (0xA0) and SHY (0xAD) are ISO 8859 special characters
+     */
+    return ((guchar) ch) < ' ';
+}
+
+static inline gboolean
+nm_ascii_is_ctrl_or_del(char ch)
+{
+    return ((guchar) ch) < ' ' || ch == 127;
+}
+
+static inline gboolean
+nm_ascii_is_non_ascii(char ch)
+{
+    return ((guchar) ch) > 127;
+}
+
+static inline gboolean
+nm_ascii_is_regular(char ch)
+{
+    /* same as(!nm_ascii_is_ctrl_or_del(ch) && !nm_ascii_is_non_ascii(ch)) */
+    return ch >= ' ' && ch < 127;
+}
+
 char *nm_utils_bin2hexstr_full(gconstpointer addr,
                                gsize         length,
                                char          delimiter,
@@ -2949,4 +2988,8 @@ void nm_crypto_md5_hash(const guint8 *salt,
 
 char *nm_utils_get_process_exit_status_desc(int status);
 
+/*****************************************************************************/
+
+void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify);
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h
index b43b206f..7a7f580c 100644
--- a/src/libnm-glib-aux/nm-str-buf.h
+++ b/src/libnm-glib-aux/nm-str-buf.h
@@ -504,4 +504,11 @@ nm_str_buf_destroy(NMStrBuf *strbuf)
 
 #define nm_auto_str_buf nm_auto(nm_str_buf_destroy)
 
+static inline gboolean
+nm_str_buf_utf8_validate(NMStrBuf *strbuf)
+{
+    _nm_str_buf_assert(strbuf);
+    return strbuf->_priv_len == 0 || g_utf8_validate(strbuf->_priv_str, strbuf->_priv_len, NULL);
+}
+
 #endif /* __NM_STR_BUF_H__ */
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 6c7ab488..4a7b6790 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -1345,6 +1345,49 @@ test_nm_g_source_sentinel(void)
 
 /*****************************************************************************/
 
+static void
+test_nm_ascii(void)
+{
+    int i;
+
+    for (i = 0; i < 256; i++) {
+        const char ch = i;
+        gboolean   is_space;
+
+        if (ch == 127) {
+            g_assert(nm_ascii_is_ctrl_or_del(ch));
+            g_assert(!nm_ascii_is_ctrl(ch));
+        } else
+            g_assert(nm_ascii_is_ctrl_or_del(ch) == nm_ascii_is_ctrl(ch));
+        g_assert(nm_ascii_is_ctrl_or_del(ch) == g_ascii_iscntrl(ch));
+
+        g_assert(nm_ascii_is_non_ascii(ch) == (i >= 128));
+
+        g_assert(!nm_ascii_is_ctrl_or_del(ch) || !nm_ascii_is_non_ascii(ch));
+
+        g_assert((nm_ascii_is_ctrl_or_del(ch) || nm_ascii_is_regular(ch))
+                 != nm_ascii_is_non_ascii(ch));
+
+        g_assert(nm_ascii_is_regular(ch)
+                 == (!nm_ascii_is_ctrl_or_del(ch) && !nm_ascii_is_non_ascii(ch)));
+
+        is_space = g_ascii_isspace(ch);
+        if (NM_IN_SET(ch, '\t', '\n', '\f', '\r')) {
+            /* hack is-space, so that the check below works to check for regular ASCII characters. */
+            g_assert(!nm_ascii_is_regular(ch));
+            g_assert(is_space);
+            is_space = FALSE;
+        }
+        g_assert(nm_ascii_is_regular(ch)
+                 == (g_ascii_isalnum(ch) || g_ascii_isalpha(ch) || g_ascii_isdigit(ch)
+                     || g_ascii_isgraph(ch) || g_ascii_islower(ch) || g_ascii_isprint(ch)
+                     || g_ascii_ispunct(ch) || is_space || g_ascii_isupper(ch)
+                     || g_ascii_isxdigit(ch)));
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -1376,6 +1419,7 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_strv_dup_packed", test_strv_dup_packed);
     g_test_add_func("/general/test_utils_hashtable_cmp", test_utils_hashtable_cmp);
     g_test_add_func("/general/test_nm_g_source_sentinel", test_nm_g_source_sentinel);
+    g_test_add_func("/general/test_nm_ascii", test_nm_ascii);
 
     return g_test_run();
 }