Commit 999313b8d9 for frr

commit 999313b8d9f217e70f33c34bf7797223f5525a50
Author: Minghan Chan <mhchann082@gmail.com>
Date:   Fri Nov 6 08:21:02 2026 +0800

    bfdd: stop sbfd reflector discriminator ranges from looping forever

    "sbfd reflector ... discriminator N-M" adds reflectors N to M with a
    uint32_t loop counter, so a range that ends at 4294967295 never ends:
    the counter wraps to 0 and bfdd keeps adding one reflector per pass
    until it runs out of memory, without serving vty or BFD packets again.
    The "from >= to" check only printed a warning, so the
    "4294967295-4294967295" range from the issue ran the same loop.

    The parser had more holes: atol() turned 4294967296 into reflector 0,
    a word such as "-50" reused the end of the previous range, "150-150"
    was called illegal but still added 150, and an illegal word printed
    the cmd_token pointer as a string.

    Parse each value with strtoull() and accept only 1-4294967295, which
    is what the initiator's remote-discr and "no sbfd reflector" accept.
    Reject reversed and empty ranges, and check every argument before
    adding anything, so that a bad command changes nothing. Count with a
    64-bit counter, and cap one command at 65536 discriminators: a valid
    range such as 1-4294967294 would still make 4 billion allocations.

    Fixes #24202

    Signed-off-by: Minghan Chan <mhchann082@gmail.com>

diff --git a/bfdd/bfdd_cli.c b/bfdd/bfdd_cli.c
index 2f98fdcf03..d2b50f5c55 100644
--- a/bfdd/bfdd_cli.c
+++ b/bfdd/bfdd_cli.c
@@ -1263,6 +1263,68 @@ DEFPY_YANG(bfd_peer_profile, bfd_peer_profile_cmd,
 	return nb_cli_apply_changes(vty, NULL);
 }

+/*
+ * One "sbfd reflector" command adds at most this many discriminators. Each
+ * one is a separate reflector, so this bounds how long the command keeps
+ * bfdd busy and how much memory it takes.
+ */
+#define SBFD_REFLECTOR_DISCR_MAX 65536
+
+static bool sbfd_reflector_parse_value(const char *str, const char **end, uint32_t *discr)
+{
+	unsigned long long value;
+	char *endp;
+
+	/* strtoull() would also take blanks, a sign or a 0x prefix. */
+	if (!isdigit((unsigned char)str[0]))
+		return false;
+
+	errno = 0;
+	value = strtoull(str, &endp, 10);
+	if (errno != 0 || value == 0 || value > UINT32_MAX)
+		return false;
+
+	*discr = value;
+	*end = endp;
+	return true;
+}
+
+/*
+ * Parse one discriminator argument: a value "N" or a range "N-M" with N < M,
+ * all in 1-4294967295.
+ */
+static bool sbfd_reflector_parse_discr(struct vty *vty, const char *arg, uint32_t *from,
+				       uint32_t *to)
+{
+	const char *end;
+
+	if (!sbfd_reflector_parse_value(arg, &end, from))
+		goto illegal;
+
+	if (*end == '\0') {
+		*to = *from;
+		return true;
+	}
+
+	if (*end != '-' || !sbfd_reflector_parse_value(end + 1, &end, to) || *end != '\0')
+		goto illegal;
+
+	if (*from >= *to) {
+		vty_out(vty,
+			"%% Discriminator range %s is illegal, the first value must be lower than the last\n",
+			arg);
+		return false;
+	}
+
+	return true;
+
+illegal:
+	vty_out(vty,
+		"%% Discriminator %s is illegal, use a value or a range of values in 1-4294967295\n",
+		arg);
+	return false;
+}
+
 DEFPY(
 	sbfd_reflector, sbfd_reflector_cmd,
 	"sbfd reflector source-address X:X::X:X$srcip discriminator WORD...",
@@ -1275,42 +1337,32 @@ DEFPY(
 {
 	int idx_discr = 5;
 	int i;
-	uint32_t j;
-	uint32_t discr = 0;
-	uint32_t discr_from = 0;
-	uint32_t discr_to = 0;
+	uint32_t from, to;
+	uint64_t discr, count = 0;

+	/*
+	 * Check every argument before adding anything, so that a bad one
+	 * leaves the configuration unchanged.
+	 */
 	for (i = idx_discr; i < argc; i++) {
-		/* check validity*/
-		char *pstr = argv[i]->arg;
-
-		/*single discr*/
-		if (strspn(pstr, "0123456789") == strlen(pstr)) {
-			discr = atol(pstr);
-			sbfd_reflector_new(discr, &srcip);
-		}
-		/*discr segment*/
-		else if (strspn(pstr, "0123456789-") == strlen(pstr)) {
-			char *token = strtok(argv[i]->arg, "-");
+		if (!sbfd_reflector_parse_discr(vty, argv[i]->arg, &from, &to))
+			return CMD_WARNING_CONFIG_FAILED;

-			if (token)
-				discr_from = atol(token);
+		count += (uint64_t)to - from + 1;
+	}

-			token = strtok(NULL, "-");
-			if (token)
-				discr_to = atol(token);
+	if (count > SBFD_REFLECTOR_DISCR_MAX) {
+		vty_out(vty, "%% Too many discriminators (%" PRIu64 "), the limit is %u\n", count,
+			SBFD_REFLECTOR_DISCR_MAX);
+		return CMD_WARNING_CONFIG_FAILED;
+	}

-			if (discr_from >= discr_to) {
-				vty_out(vty, "input discriminator range %u-%u is illegal\n",
-					discr_from, discr_to);
-			}
+	for (i = idx_discr; i < argc; i++) {
+		(void)sbfd_reflector_parse_discr(vty, argv[i]->arg, &from, &to);

-			for (j = discr_from; j <= discr_to; j++)
-				sbfd_reflector_new(j, &srcip);
-		}
-		/*illegal input*/
-		else
-			vty_out(vty, "input discriminator %s is illegal\n", (char *)argv[i]);
+		/* 64 bits wide, so that a range that ends at UINT32_MAX ends. */
+		for (discr = from; discr <= to; discr++)
+			sbfd_reflector_new((uint32_t)discr, &srcip);
 	}

 	return CMD_SUCCESS;