CVE-2024-24479: Insufficient Information (NVD-CWE-noinfo)
value: A Buffer Overflow in Wireshark before 4.2.0 allows a remote attacker to cause a denial of service via the wsutil/to_str.c, and format_fractional_part_nsecs components. NOTE: this is disputed by the vendor because neither release 4.2.0 nor any other release was affected.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-21T19:15Z
Repository: https://github.com/wireshark/wireshark
Language: C
Files changed: wsutil/to_str.c, wsutil/to_str.h
Commit date: 2023-09-12 01:29:04 -0700
Commit message: Various enhancements and fixes to format_fractional_part_nsecs().

Have format_fractional_part_nsecs() return the number of bytes
formatted.  This matches what snprintf() and strlcpy() do, for the
benefit of routines constructing a string in a fixed-length buffer with
multiple calls, one of which is format_fractional_part_nsecs().

Have format_fractional_part_nsecs() check for a count of nanoseconds
greatre than or equal to 1 billion, and report that specially.

Fix places wher it used the bufer pointer and size of the buffer, rather
than the current position in the buffer and the remaining space in the
buffer, to append to the buffer and make sure doing so doesn't overrun
the buffer.

Rename some variable names (rename buf_remaining to just remaining,
rename num_chars to num_bytes as it counts bytes).

Fix some comments.

Diff: diff --git a/wsutil/to_str.c b/wsutil/to_str.c
index ddffb7c409b..d721e63e292 100644
--- a/wsutil/to_str.c
+++ b/wsutil/to_str.c
@@ -651,16 +651,21 @@ eui64_to_str(wmem_allocator_t *scope, const guint64 ad) {
  * not counting the decimal point.
  */
 #define CHARS_NANOSECONDS	9	/* 000000001 */
 
 /*
- * Format the fractonal part of a time, with the specified precision)
+ * Format the fractional part of a time, with the specified precision.
+ * Returns the number of bytes formatted.
  */
-void
+int
 format_fractional_part_nsecs(gchar *buf, size_t buflen, guint32 nsecs, const char *decimal_point, int precision)
 {
+	gchar *ptr;
+	size_t remaining;
+	int num_bytes;
 	gsize decimal_point_len;
+	guint32 frac_part;
 	gint8 num_buf[CHARS_NANOSECONDS];
 	gint8 *num_end = &num_buf[CHARS_NANOSECONDS];
 	gint8 *num_ptr;
 	size_t num_len;
 
@@ -669,26 +674,54 @@ format_fractional_part_nsecs(gchar *buf, size_t buflen, guint32 nsecs, const cha
 	if (buflen == 0) {
 		/*
 		 * No room in the buffer for anything, including
 		 * a terminating '\0'.
 		 */
-		return;
+		return 0;
+	}
+
+	/*
+	 * If the fractional part is >= 1, don't show it as a
+	 * fractional part.
+	 */
+	if (nsecs >= 1000000000U) {
+		num_bytes = snprintf(buf, buflen, "%s(%u nanoseconds)",
+		    decimal_point, nsecs);
+		if ((unsigned int)num_bytes >= buflen) {
+			/*
+			 * That filled up or would have overflowed
+			 * the buffer.  Nothing more to do; return
+			 * the remaining space in the buffer, minus
+			 * one byte for the terminating '\0',* as
+			 * that's the number of bytes we copied.
+			 */
+			return (int)(buflen - 1);
+		}
+		return num_bytes;
 	}
 
+	ptr = buf;
+	remaining = buflen;
+	num_bytes = 0;
+
 	/*
 	 * Copy the decimal point.
 	 */
 	decimal_point_len = g_strlcpy(buf, decimal_point, buflen);
 	if (decimal_point_len >= buflen) {
 		/*
 		 * The decimal point didn't fit in the buffer
-		 * and was truncated.  Nothing more to do.
+		 * and was truncated.  Nothing more to do;
+		 * return the remaining space in the buffer,
+		 * minus one byte for the terminating '\0',
+		 * as that's the number of bytes we copied.
 		 */
-		return;
+		return (int)(buflen - 1);
 	}
-	buf += decimal_point_len;
-	buflen -= decimal_point_len;
+	ptr += decimal_point_len;
+	remaining -= decimal_point_len;
+	num_bytes += decimal_point_len;
 
 	/*
 	 * Fill in num_buf with the nanoseconds value, padded with
 	 * leading zeroes, to the specified precision.
 	 *
@@ -707,108 +740,105 @@ format_fractional_part_nsecs(gchar *buf, size_t buflen, guint32 nsecs, const cha
 
 	case 1:
 		/*
 		 * Scale down to units of 1/10 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 100000000, precision);
+		frac_part = nsecs / 100000000U;
 		break;
 
 	case 2:
 		/*
 		 * Scale down to units of 1/100 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 10000000, precision);
+		frac_part = nsecs / 10000000U;
 		break;
 
 	case 3:
 		/*
 		 * Scale down to units of 1/1000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 1000000, precision);
+		frac_part = nsecs / 1000000U;
 		break;
 
 	case 4:
 		/*
 		 * Scale down to units of 1/10000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 100000, precision);
+		frac_part = nsecs / 100000U;
 		break;
 
 	case 5:
 		/*
 		 * Scale down to units of 1/100000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 10000, precision);
+		frac_part = nsecs / 10000U;
 		break;
 
 	case 6:
 		/*
 		 * Scale down to units of 1/1000000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 1000, precision);
+		frac_part = nsecs / 1000U;
 		break;
 
 	case 7:
 		/*
 		 * Scale down to units of 1/10000000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 100, precision);
+		frac_part = nsecs / 100U;
 		break;
 
 	case 8:
 		/*
 		 * Scale down to units of 1/100000000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end,
-		    nsecs / 10, precision);
+		frac_part = nsecs / 10U;
 		break;
 
 	case 9:
 		/*
 		 * We're already in units of 1/1000000000 second.
 		 */
-		num_ptr = uint_to_str_back_len(num_end, nsecs,
-		    precision);
+		frac_part = nsecs;
 		break;
 
 	default:
 		ws_assert_not_reached();
 		break;
 	}
 
+	num_ptr = uint_to_str_back_len(num_end, frac_part, precision);
+
 	/*
 	 * The length of the string that we want to copy to the buffer
 	 * is the minimum of:
 	 *
 	 *    the length of the digit string;
-	 *    the size of the buffer, minus 1 for the terminating
-	 *      '\0'.
+	 *    the remaining space in the buffer, minus 1 for the
+	 *      terminating '\0'.
 	 */
-	num_len = MIN((size_t)(num_end - num_ptr), buflen - 1);
+	num_len = MIN((size_t)(num_end - num_ptr), remaining - 1);
 	if (num_len == 0) {
 		/*
 		 * Not enough room to copy anything.
+		 * Return the number of bytes we've generated.
 		 */
-		return;
+		return num_bytes;
 	}
 
 	/*
 	 * Copy over the fractional part.
 	 */
-	memcpy(buf, num_ptr, num_len);
+	memcpy(ptr, num_ptr, num_len);
+	ptr += num_len;
+	num_bytes += num_len;
 
 	/*
 	 * '\0'-terminate it.
 	 */
-	*(buf + num_len) = '\0';
+	*ptr = '\0';
+	return num_bytes;
 }
 
 void
 display_epoch_time(gchar *buf, size_t buflen, const nstime_t *ns, int precision)
 {
@@ -890,61 +920,57 @@ void
 format_nstime_as_iso8601(gchar *buf, size_t buflen, const nstime_t *ns,
     char *decimal_point, gboolean local, int precision)
 {
 	struct tm tm, *tmp;
 	gchar *ptr;
-	size_t buf_remaining;
-	int num_chars;
+	size_t remaining;
+	int num_bytes;
 
 	if (local)
 		tmp = ws_localtime_r(&ns->secs, &tm);
 	else
 		tmp = ws_gmtime_r(&ns->secs, &tm);
 	if (tmp == NULL) {
 		snprintf(buf, buflen, "Not representable");
 		return;
 	}
 	ptr = buf;
-	buf_remaining = buflen;
-	num_chars = snprintf(ptr, buf_remaining,
+	remaining = buflen;
+	num_bytes = snprintf(ptr, remaining,
 	    "%04d-%02d-%02d %02d:%02d:%02d",
 	    tmp->tm_year + 1900,
 	    tmp->tm_mon + 1,
 	    tmp->tm_mday,
 	    tmp->tm_hour,
 	    tmp->tm_min,
 	    tmp->tm_sec);
-	if (num_chars < 0) {
+	if (num_bytes < 0) {
 		/*
+		 * That got an error.
 		 * Not much else we can do.
 		 */
 		snprintf(buf, buflen, "snprintf() failed");
 		return;
 	}
-	if ((unsigned int)num_chars >= buf_remaining) {
+	if ((unsigned int)num_bytes >= remaining) {
 		/*
-		 * Either that got an error (num_chars < 0) or it
-		 * filled up or would have overflowed the buffer
-		 * (num_chars >= buf_remaining).
+		 * That filled up or would have overflowed the buffer.
 		 * Nothing more we can do.
 		 */
 		return;
 	}
-	ptr += num_chars;
-	buf_remaining -= num_chars;
+	ptr += num_bytes;
+	remaining -= num_bytes;
 
-	if (precision == 0) {
+	if (precision != 0) {
 		/*
-		 * Seconds precision, so no nanosecond.
+		 * Append the fractional part.
+		 * Get the nsecs as a 32-bit unsigned value, as it should
+		 * never be negative, so we treat it as unsigned.
 		 */
-		return;
+		format_fractional_part_nsecs(ptr, remaining, (guint32)ns->nsecs, decimal_point, precision);
 	}
-
-	/*
-	 * Append the fractional part.
-	 */
-	format_fractional_part_nsecs(ptr, buf_remaining, (guint32)ns->nsecs, decimal_point, precision);
 }
 
 /*
  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
  *
diff --git a/wsutil/to_str.h b/wsutil/to_str.h
index 354cfecb6f2..c56b54e8f1c 100644
--- a/wsutil/to_str.h
+++ b/wsutil/to_str.h
@@ -297,11 +297,11 @@ WS_DLL_PUBLIC char *ip6_to_str(wmem_allocator_t *scope, const ws_in6_addr *ad);
 
 WS_DLL_PUBLIC gchar *ipxnet_to_str_punct(wmem_allocator_t *scope, const guint32 ad, const char punct);
 
 WS_DLL_PUBLIC gchar *eui64_to_str(wmem_allocator_t *scope, const guint64 ad);
 
-WS_DLL_PUBLIC void format_fractional_part_nsecs(gchar *, size_t, guint32, const char *, int);
+WS_DLL_PUBLIC int format_fractional_part_nsecs(gchar *, size_t, guint32, const char *, int);
 
 WS_DLL_PUBLIC void display_epoch_time(gchar *, size_t, const nstime_t *, int);
 
 WS_DLL_PUBLIC void display_signed_time(gchar *, size_t, const nstime_t *, int);
 

Vulnerable code:  * Format the fractonal part of a time, with the specified precision)
void
		return;
		 * and was truncated.  Nothing more to do.
		return;
	buf += decimal_point_len;
	buflen -= decimal_point_len;
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 100000000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 10000000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 1000000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 100000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 10000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 1000, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 100, precision);
		num_ptr = uint_to_str_back_len(num_end,
		    nsecs / 10, precision);
		num_ptr = uint_to_str_back_len(num_end, nsecs,
		    precision);
	 *    the size of the buffer, minus 1 for the terminating
	 *      '\0'.
	num_len = MIN((size_t)(num_end - num_ptr), buflen - 1);
		return;
	memcpy(buf, num_ptr, num_len);
	*(buf + num_len) = '\0';
	size_t buf_remaining;
	int num_chars;
	buf_remaining = buflen;
	num_chars = snprintf(ptr, buf_remaining,
	if (num_chars < 0) {
	if ((unsigned int)num_chars >= buf_remaining) {
		 * Either that got an error (num_chars < 0) or it
		 * filled up or would have overflowed the buffer
		 * (num_chars >= buf_remaining).
	ptr += num_chars;
	buf_remaining -= num_chars;
	if (precision == 0) {
		 * Seconds precision, so no nanosecond.
		return;

	/*
	 * Append the fractional part.
	 */
	format_fractional_part_nsecs(ptr, buf_remaining, (guint32)ns->nsecs, decimal_point, precision);
WS_DLL_PUBLIC void format_fractional_part_nsecs(gchar *, size_t, guint32, const char *, int);
Fixed code:  * Format the fractional part of a time, with the specified precision.
 * Returns the number of bytes formatted.
int
	gchar *ptr;
	size_t remaining;
	int num_bytes;
	guint32 frac_part;
		return 0;
	}

	/*
	 * If the fractional part is >= 1, don't show it as a
	 * fractional part.
	 */
	if (nsecs >= 1000000000U) {
		num_bytes = snprintf(buf, buflen, "%s(%u nanoseconds)",
		    decimal_point, nsecs);
		if ((unsigned int)num_bytes >= buflen) {
			/*
			 * That filled up or would have overflowed
			 * the buffer.  Nothing more to do; return
			 * the remaining space in the buffer, minus
			 * one byte for the terminating '\0',* as
			 * that's the number of bytes we copied.
			 */
			return (int)(buflen - 1);
		}
		return num_bytes;
	ptr = buf;
	remaining = buflen;
	num_bytes = 0;

		 * and was truncated.  Nothing more to do;
		 * return the remaining space in the buffer,
		 * minus one byte for the terminating '\0',
		 * as that's the number of bytes we copied.
		return (int)(buflen - 1);
	ptr += decimal_point_len;
	remaining -= decimal_point_len;
	num_bytes += decimal_point_len;
		frac_part = nsecs / 100000000U;
		frac_part = nsecs / 10000000U;
		frac_part = nsecs / 1000000U;
		frac_part = nsecs / 100000U;
		frac_part = nsecs / 10000U;
		frac_part = nsecs / 1000U;
		frac_part = nsecs / 100U;
		frac_part = nsecs / 10U;
		frac_part = nsecs;
	num_ptr = uint_to_str_back_len(num_end, frac_part, precision);

	 *    the remaining space in the buffer, minus 1 for the
	 *      terminating '\0'.
	num_len = MIN((size_t)(num_end - num_ptr), remaining - 1);
		 * Return the number of bytes we've generated.
		return num_bytes;
	memcpy(ptr, num_ptr, num_len);
	ptr += num_len;
	num_bytes += num_len;
	*ptr = '\0';
	return num_bytes;
	size_t remaining;
	int num_bytes;
	remaining = buflen;
	num_bytes = snprintf(ptr, remaining,
	if (num_bytes < 0) {
		 * That got an error.
	if ((unsigned int)num_bytes >= remaining) {
		 * That filled up or would have overflowed the buffer.
	ptr += num_bytes;
	remaining -= num_bytes;
	if (precision != 0) {
		 * Append the fractional part.
		 * Get the nsecs as a 32-bit unsigned value, as it should
		 * never be negative, so we treat it as unsigned.
		format_fractional_part_nsecs(ptr, remaining, (guint32)ns->nsecs, decimal_point, precision);
WS_DLL_PUBLIC int format_fractional_part_nsecs(gchar *, size_t, guint32, const char *, int);