about summary refs log tree commit diff
path: root/src/nm-core-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-core-utils.c')
-rw-r--r--src/nm-core-utils.c526
1 files changed, 461 insertions, 65 deletions
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index e9365c0c..6dfbb4ce 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -26,6 +26,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
+#include <poll.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
@@ -54,7 +55,7 @@
 #endif
 
 G_STATIC_ASSERT (sizeof (NMUtilsTestFlags) <= sizeof (int));
-int _nm_utils_testing = 0;
+static int _nm_utils_testing = 0;
 
 gboolean
 nm_utils_get_testing_initialized ()
@@ -199,6 +200,27 @@ nm_ethernet_address_is_valid (gconstpointer addr, gssize len)
 	return TRUE;
 }
 
+gconstpointer
+nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer src, guint8 plen)
+{
+	g_return_val_if_fail (src, NULL);
+	g_return_val_if_fail (dst, NULL);
+
+	switch (family) {
+	case AF_INET:
+		g_return_val_if_fail (plen <= 32, NULL);
+		*((guint32 *) dst) = nm_utils_ip4_address_clear_host_address (*((guint32 *) src), plen);
+		break;
+	case AF_INET6:
+		g_return_val_if_fail (plen <= 128, NULL);
+		nm_utils_ip6_address_clear_host_address (dst, src, plen);
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+	return dst;
+}
+
 /* nm_utils_ip4_address_clear_host_address:
  * @addr: source ip6 address
  * @plen: prefix length of network
@@ -445,8 +467,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 	gs_strfreev gchar **tokens = NULL;
 	guint num_tokens;
 	gchar *p;
-	gchar *endp;
-	char state = '\0';
+	char state = ' ';
 	gint64 ppid = 0;
 
 	start_time = 0;
@@ -457,7 +478,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 	nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid);
 
 	if (!g_file_get_contents (filename, &contents, &length, NULL))
-		goto out;
+		goto fail;
 
 	/* start time is the token at index 19 after the '(process name)' entry - since only this
 	 * field can contain the ')' character, search backwards for this to avoid malicious
@@ -465,10 +486,10 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 	 */
 	p = strrchr (contents, ')');
 	if (p == NULL)
-		goto out;
+		goto fail;
 	p += 2; /* skip ') ' */
 	if (p - contents >= (int) length)
-		goto out;
+		goto fail;
 
 	state = p[0];
 
@@ -477,23 +498,26 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 	num_tokens = g_strv_length (tokens);
 
 	if (num_tokens < 20)
-		goto out;
+		goto fail;
 
-	if (out_ppid)
+	if (out_ppid) {
 		ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0);
+		if (ppid == 0)
+			goto fail;
+	}
 
-	errno = 0;
-	start_time = strtoull (tokens[19], &endp, 10);
-	if (*endp != '\0' || errno != 0)
-		start_time = 0;
-
-out:
-	if (out_state)
-		*out_state = state;
-	if (out_ppid)
-		*out_ppid = ppid;
+	start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0);
+	if (start_time == 0)
+		goto fail;
 
+	NM_SET_OUT (out_state, state);
+	NM_SET_OUT (out_ppid, ppid);
 	return start_time;
+
+fail:
+	NM_SET_OUT (out_state, ' ');
+	NM_SET_OUT (out_ppid, 0);
+	return 0;
 }
 
 /******************************************************************************************/
@@ -1248,8 +1272,10 @@ nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr)
 {
 	const GSList *iter;
 	NMMatchSpecMatchType match = NM_MATCH_SPEC_NO_MATCH;
+	guint hwaddr_len = 0;
+	guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX];
 
-	g_return_val_if_fail (hwaddr != NULL, NM_MATCH_SPEC_NO_MATCH);
+	nm_assert (nm_utils_hwaddr_valid (hwaddr, -1));
 
 	for (iter = specs; iter; iter = g_slist_next (iter)) {
 		const char *spec_str = iter->data;
@@ -1270,7 +1296,15 @@ nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr)
 		else if (except)
 			continue;
 
-		if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr, -1)) {
+		if (G_UNLIKELY (hwaddr_len == 0)) {
+			hwaddr_len = _nm_utils_hwaddr_length (hwaddr);
+			if (!hwaddr_len)
+				g_return_val_if_reached (NM_MATCH_SPEC_NO_MATCH);
+			if (!nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, hwaddr_len))
+				nm_assert_not_reached ();
+		}
+
+		if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr_bin, hwaddr_len)) {
 			if (except)
 				return NM_MATCH_SPEC_NEG_MATCH;
 			match = NM_MATCH_SPEC_MATCH;
@@ -1900,6 +1934,33 @@ nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id)
 	return ifname;
 }
 
+/* nm_utils_new_infiniband_name:
+ * @name: the output-buffer where the value will be written. Must be
+ *   not %NULL and point to a string buffer of at least IFNAMSIZ bytes.
+ * @parent_name: the parent interface name
+ * @p_key: the partition key.
+ *
+ * Returns: the infiniband name will be written to @name and @name
+ *   is returned.
+ */
+const char *
+nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key)
+{
+	g_return_val_if_fail (name, NULL);
+	g_return_val_if_fail (parent_name && parent_name[0], NULL);
+	g_return_val_if_fail (strlen (parent_name) < IFNAMSIZ, NULL);
+
+	/* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't
+	 * want to assert against that in nm_utils_new_infiniband_name(). So be more
+	 * resilient here, and accept those. */
+	g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, NULL);
+
+	/* If parent+suffix is too long, kernel would just truncate
+	 * the name. We do the same. See ipoib_vlan_add().  */
+	g_snprintf (name, IFNAMSIZ, "%s.%04x", parent_name, p_key);
+	return name;
+}
+
 /**
  * nm_utils_read_resolv_conf_nameservers():
  * @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf
@@ -2651,6 +2712,123 @@ nm_utils_machine_id_read (void)
 
 /*****************************************************************************/
 
+/* taken from systemd's fd_wait_for_event(). Note that the timeout
+ * is here in nano-seconds, not micro-seconds. */
+int
+nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
+{
+	struct pollfd pollfd = {
+		.fd = fd,
+		.events = event,
+	};
+	struct timespec ts, *pts;
+	int r;
+
+	if (timeout_ns < 0)
+		pts = NULL;
+	else {
+		ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND);
+		ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND);
+		pts = &ts;
+	}
+
+	r = ppoll (&pollfd, 1, pts, NULL);
+	if (r < 0)
+		return -errno;
+	if (r == 0)
+		return 0;
+	return pollfd.revents;
+}
+
+/* taken from systemd's loop_read() */
+ssize_t
+nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
+{
+	uint8_t *p = buf;
+	ssize_t n = 0;
+
+	g_return_val_if_fail (fd >= 0, -EINVAL);
+	g_return_val_if_fail (buf, -EINVAL);
+
+	/* If called with nbytes == 0, let's call read() at least
+	 * once, to validate the operation */
+
+	if (nbytes > (size_t) SSIZE_MAX)
+		return -EINVAL;
+
+	do {
+		ssize_t k;
+
+		k = read (fd, p, nbytes);
+		if (k < 0) {
+			if (errno == EINTR)
+				continue;
+
+			if (errno == EAGAIN && do_poll) {
+
+				/* We knowingly ignore any return value here,
+				 * and expect that any error/EOF is reported
+				 * via read() */
+
+				(void) nm_utils_fd_wait_for_event (fd, POLLIN, -1);
+				continue;
+			}
+
+			return n > 0 ? n : -errno;
+		}
+
+		if (k == 0)
+			return n;
+
+		g_assert ((size_t) k <= nbytes);
+
+		p += k;
+		nbytes -= k;
+		n += k;
+	} while (nbytes > 0);
+
+	return n;
+}
+
+/* taken from systemd's loop_read_exact() */
+int
+nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
+{
+	ssize_t n;
+
+	n = nm_utils_fd_read_loop (fd, buf, nbytes, do_poll);
+	if (n < 0)
+		return (int) n;
+	if ((size_t) n != nbytes)
+		return -EIO;
+
+	return 0;
+}
+
+/* taken from systemd's dev_urandom(). */
+int
+nm_utils_read_urandom (void *p, size_t nbytes)
+{
+	int fd = -1;
+	int r;
+
+again:
+	fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+	if (fd < 0) {
+		r = errno;
+		if (r == EINTR)
+			goto again;
+		return r == ENOENT ? -ENOSYS : -r;
+	}
+
+	r = nm_utils_fd_read_loop_exact (fd, p, nbytes, TRUE);
+	close (fd);
+
+	return r;
+}
+
+/*****************************************************************************/
+
 guint8 *
 nm_utils_secret_key_read (gsize *out_key_len, GError **error)
 {
@@ -2669,34 +2847,28 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error)
 			key_len = 0;
 		}
 	} else {
-		int urandom = open ("/dev/urandom", O_RDONLY);
+		int r;
 		mode_t key_mask;
 
-		if (urandom == -1) {
-			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "Can't open /dev/urandom: %s", strerror (errno));
-			key_len = 0;
-			goto out;
-		}
-
 		/* RFC7217 mandates the key SHOULD be at least 128 bits.
 		 * Let's use twice as much. */
 		key_len = 32;
 		secret_key = g_malloc (key_len);
 
+		r = nm_utils_read_urandom (secret_key, key_len);
+		if (r < 0) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "Can't read /dev/urandom: %s", strerror (-r));
+			key_len = 0;
+			goto out;
+		}
+
 		key_mask = umask (0077);
-		if (read (urandom, secret_key, key_len) == key_len) {
-			if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) {
-				g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: ");
-				key_len = 0;
-			}
-		} else {
-			g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			                     "Could not obtain a secret");
+		if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) {
+			g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: ");
 			key_len = 0;
 		}
 		umask (key_mask);
-		close (urandom);
 	}
 
 out:
@@ -2816,24 +2988,97 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
 	}
 	return FALSE;
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_ipv6_addr_set_interface_identifier:
+ * @addr: output token encoded as %in6_addr
+ * @iid: %NMUtilsIPv6IfaceId interface identifier
+ *
+ * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use
+ * with Linux platform). This only copies the lower 8 bytes, ignoring
+ * the /64 network prefix which is expected to be all-zero for a valid
+ * token.
+ */
 void
-nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
+nm_utils_ipv6_addr_set_interface_identifier (struct in6_addr *addr,
                                             const NMUtilsIPv6IfaceId iid)
 {
 	memcpy (addr->s6_addr + 8, &iid.id_u8, 8);
 }
 
+/**
+ * nm_utils_ipv6_interface_identifier_get_from_addr:
+ * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token
+ * @addr: token encoded as %in6_addr
+ *
+ * Converts the %in6_addr encoded token (as used by Linux platform) to
+ * the interface identifier.
+ */
 void
-nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
+nm_utils_ipv6_interface_identifier_get_from_addr (NMUtilsIPv6IfaceId *iid,
                                                  const struct in6_addr *addr)
 {
 	memcpy (iid, addr->s6_addr + 8, 8);
 }
 
+/**
+ * nm_utils_ipv6_interface_identifier_get_from_token:
+ * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token
+ * @token: token encoded as string
+ *
+ * Converts the %in6_addr encoded token (as used in ip6 settings) to
+ * the interface identifier.
+ *
+ * Returns: %TRUE if the @token is a valid token, %FALSE otherwise
+ */
+gboolean
+nm_utils_ipv6_interface_identifier_get_from_token (NMUtilsIPv6IfaceId *iid,
+                                                  const char *token)
+{
+	struct in6_addr i6_token;
+
+	g_return_val_if_fail (token, FALSE);
+
+	if (!inet_pton (AF_INET6, token, &i6_token))
+		return FALSE;
+
+	if (!_nm_utils_inet6_is_token (&i6_token))
+		return FALSE;
+
+	nm_utils_ipv6_interface_identifier_get_from_addr (iid, &i6_token);
+	return TRUE;
+}
+
+/**
+ * nm_utils_inet6_interface_identifier_to_token:
+ * @iid: %NMUtilsIPv6IfaceId interface identifier
+ * @buf: the destination buffer or %NULL
+ *
+ * Converts the interface identifier to a string token.
+ * If the destination buffer it set, set it is used to store the
+ * resulting token, otherwise an internal static buffer is used.
+ * The buffer needs to be %NM_UTILS_INET_ADDRSTRLEN characters long.
+ *
+ * Returns: a statically allocated array. Do not g_free().
+ */
+const char *
+nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf)
+{
+	struct in6_addr i6_token = { .s6_addr = { 0, } };
+
+	nm_utils_ipv6_addr_set_interface_identifier (&i6_token, iid);
+	return nm_utils_inet6_ntop (&i6_token, buf);
+}
+
+/*****************************************************************************/
+
 static gboolean
-_set_stable_privacy (struct in6_addr *addr,
+_set_stable_privacy (guint8 stable_type,
+                     struct in6_addr *addr,
                      const char *ifname,
-                     const char *uuid,
+                     const char *network_id,
                      guint dad_counter,
                      guint8 *secret_key,
                      gsize key_len,
@@ -2857,11 +3102,24 @@ _set_stable_privacy (struct in6_addr *addr,
 
 	key_len = MIN (key_len, G_MAXUINT32);
 
+	if (stable_type != NM_UTILS_STABLE_TYPE_UUID) {
+		/* Preferably, we would always like to include the stable-type,
+		 * but for backward compatibility reasons, we cannot for UUID.
+		 *
+		 * That is no real problem and it is still impossible to
+		 * force a collision here, because of how the remaining
+		 * fields are hashed. That is, as we also hash @key_len
+		 * and the terminating '\0' of @network_id, it is unambigiously
+		 * possible to revert the process and deduce the @stable_type.
+		 */
+		g_checksum_update (sum, &stable_type, sizeof (stable_type));
+	}
+
 	g_checksum_update (sum, addr->s6_addr, 8);
 	g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1);
-	if (!uuid)
-		uuid = "";
-	g_checksum_update (sum, (const guchar *) uuid, strlen (uuid) + 1);
+	if (!network_id)
+		network_id = "";
+	g_checksum_update (sum, (const guchar *) network_id, strlen (network_id) + 1);
 	tmp[0] = htonl (dad_counter);
 	tmp[1] = htonl (key_len);
 	g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp));
@@ -2887,15 +3145,20 @@ _set_stable_privacy (struct in6_addr *addr,
  * Returns: %TRUE on success, %FALSE if the address could not be generated.
  */
 gboolean
-nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr,
+nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
+                                       struct in6_addr *addr,
                                        const char *ifname,
-                                       const char *uuid,
+                                       const char *network_id,
                                        guint dad_counter,
                                        GError **error)
 {
 	gs_free guint8 *secret_key = NULL;
 	gsize key_len = 0;
 
+	nm_assert (NM_IN_SET (stable_type,
+	                      NM_UTILS_STABLE_TYPE_UUID,
+	                      NM_UTILS_STABLE_TYPE_STABLE_ID));
+
 	if (dad_counter >= RFC7217_IDGEN_RETRIES) {
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		                     "Too many DAD collisions");
@@ -2906,10 +3169,151 @@ nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr,
 	if (!secret_key)
 		return FALSE;
 
-	return _set_stable_privacy (addr, ifname, uuid, dad_counter,
+	return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter,
 	                            secret_key, key_len, error);
 }
 
+/*****************************************************************************/
+
+static void
+_hw_addr_eth_complete (struct ether_addr *addr,
+                       const char *current_mac_address,
+                       const char *generate_mac_address_mask)
+{
+	struct ether_addr mask;
+	struct ether_addr oui;
+	struct ether_addr *ouis;
+	gsize ouis_len;
+	guint i;
+
+	/* the second LSB of the first octet means
+	 * "globally unique, OUI enforced, BIA (burned-in-address)"
+	 * vs. "locally-administered". By default, set it to
+	 * generate locally-administered addresses.
+	 *
+	 * Maybe be overwritten by a mask below. */
+	addr->ether_addr_octet[0] |= 2;
+
+	if (!generate_mac_address_mask || !*generate_mac_address_mask)
+		goto out;
+	if (!_nm_utils_generate_mac_address_mask_parse (generate_mac_address_mask,
+	                                                &mask,
+	                                                &ouis,
+	                                                &ouis_len,
+	                                                NULL))
+		goto out;
+
+	nm_assert ((ouis == NULL) ^ (ouis_len != 0));
+	if (ouis) {
+		/* g_random_int() is good enough here. It uses a static GRand instance
+		 * that is seeded from /dev/urandom. */
+		oui = ouis[g_random_int () % ouis_len];
+		g_free (ouis);
+	} else {
+		if (!nm_utils_hwaddr_aton (current_mac_address, &oui, ETH_ALEN))
+			goto out;
+	}
+
+	for (i = 0; i < ETH_ALEN; i++) {
+		const guint8 a = addr->ether_addr_octet[i];
+		const guint8 o = oui.ether_addr_octet[i];
+		const guint8 m = mask.ether_addr_octet[i];
+
+		addr->ether_addr_octet[i] = (a & ~m) | (o & m);
+	}
+
+out:
+	/* The LSB of the first octet must always be cleared,
+	 * it means Unicast vs. Multicast */
+	addr->ether_addr_octet[0] &= ~1;
+}
+
+char *
+nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
+                                 const char *generate_mac_address_mask)
+{
+	struct ether_addr bin_addr;
+
+	if (nm_utils_read_urandom (&bin_addr, ETH_ALEN) < 0)
+		return NULL;
+	_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
+	return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN);
+}
+
+static char *
+_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
+                         const char *stable_id,
+                         const guint8 *secret_key,
+                         gsize key_len,
+                         const char *ifname,
+                         const char *current_mac_address,
+                         const char *generate_mac_address_mask)
+{
+	GChecksum *sum;
+	guint32 tmp;
+	guint8 digest[32];
+	gsize len = sizeof (digest);
+	struct ether_addr bin_addr;
+	guint8 stable_type_uint8;
+
+	nm_assert (stable_id);
+	nm_assert (NM_IN_SET (stable_type,
+	                      NM_UTILS_STABLE_TYPE_UUID,
+	                      NM_UTILS_STABLE_TYPE_STABLE_ID));
+	nm_assert (secret_key);
+
+	sum = g_checksum_new (G_CHECKSUM_SHA256);
+	if (!sum)
+		return NULL;
+
+	key_len = MIN (key_len, G_MAXUINT32);
+
+	stable_type_uint8 = stable_type;
+	g_checksum_update (sum, (const guchar *) &stable_type_uint8, sizeof (stable_type_uint8));
+
+	tmp = htonl ((guint32) key_len);
+	g_checksum_update (sum, (const guchar *) &tmp, sizeof (tmp));
+	g_checksum_update (sum, (const guchar *) secret_key, key_len);
+	g_checksum_update (sum, (const guchar *) (ifname ?: ""), ifname ? (strlen (ifname) + 1) : 1);
+	g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1);
+
+	g_checksum_get_digest (sum, digest, &len);
+	g_checksum_free (sum);
+
+	g_return_val_if_fail (len == 32, NULL);
+
+	memcpy (&bin_addr, digest, ETH_ALEN);
+	_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
+	return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN);
+}
+
+char *
+nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
+                                 const char *stable_id,
+                                 const char *ifname,
+                                 const char *current_mac_address,
+                                 const char *generate_mac_address_mask)
+{
+	gs_free guint8 *secret_key = NULL;
+	gsize key_len = 0;
+
+	g_return_val_if_fail (stable_id, NULL);
+
+	secret_key = nm_utils_secret_key_read (&key_len, NULL);
+	if (!secret_key)
+		return NULL;
+
+	return _hw_addr_gen_stable_eth (stable_type,
+	                                stable_id,
+	                                secret_key,
+	                                key_len,
+	                                ifname,
+	                                current_mac_address,
+	                                generate_mac_address_mask);
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_setpgid:
  * @unused: unused
@@ -3120,12 +3524,11 @@ nm_utils_lifetime_get (guint32 timestamp,
 }
 
 const char *
-nm_utils_dnsmasq_status_to_string (int status, char *dest, guint size)
+nm_utils_dnsmasq_status_to_string (int status, char *dest, gsize size)
 {
-	static char buffer[128];
-	char *msg, *ret;
-	gs_free char *msg_free = NULL;
-	int len;
+	const char *msg;
+
+	nm_utils_to_string_buffer_init (&dest, &size);
 
 	if (status == 0)
 		msg = "Success";
@@ -3139,22 +3542,15 @@ nm_utils_dnsmasq_status_to_string (int status, char *dest, guint size)
 		msg = "Memory allocation failure";
 	else if (status == 5)
 		msg = "Other problem";
-	else if (status >= 11)
-		msg = msg_free = g_strdup_printf ("Lease script failed with error %d", status - 10);
+	else if (status >= 11) {
+		g_snprintf (dest, size, "Lease script failed with error %d", status - 10);
+		return dest;
+	}
 	else
 		msg = "Unknown problem";
 
-	if (dest) {
-		ret = dest;
-		len = size;
-	} else {
-		ret = buffer;
-		len = sizeof (buffer);
-	}
-
-	g_snprintf (ret, len, "%s (%d)", msg, status);
-
-	return ret;
+	g_snprintf (dest, size, "%s (%d)", msg, status);
+	return dest;
 }
 
 /**