diff options
| author | Sebastien Bacher <seb128@ubuntu.com> | 2021-08-25 15:24:42 +0200 |
|---|---|---|
| committer | Sebastien Bacher <seb128@ubuntu.com> | 2021-08-25 15:24:42 +0200 |
| commit | dbb91282fa488964fb20595f9494a9f0e4f36a58 (patch) | |
| tree | 142bc942e5320b35514cdf03a5f9f47a9b89df0e /src/libnm-glib-aux | |
| parent | 5f2ede3a2813b0e9204befdcfc67509d34be71c6 (diff) | |
| parent | cfb80376641fa49137b9996130352697e7f8b436 (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.h | 46 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-errno.c | 14 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-io-utils.c | 64 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-io-utils.h | 2 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-keyfile-aux.c | 118 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-keyfile-aux.h | 6 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-macros-internal.h | 8 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-random-utils.c | 297 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.c | 102 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.h | 47 | ||||
| -rw-r--r-- | src/libnm-glib-aux/nm-str-buf.h | 7 | ||||
| -rw-r--r-- | src/libnm-glib-aux/tests/test-shared-general.c | 44 |
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(); } |