On Thu, 22 Oct 2026 20:23:48 +0800
Wei Chen <[email protected]> wrote:
> The _strip() function passes a char directly to isspace(), which is
> undefined behavior. The ctype functions require an int argument that is
> either an unsigned char value or EOF, but char is signed by default on
> x86 and ARM, so any byte >= 0x80 in the input is promoted to a negative
> int and indexes the ctype table out of bounds.
>
> Such bytes are reachable from a user-provided config file containing
> UTF-8 or other non-ASCII content.
>
> Cast the argument to unsigned char, as is already done in
> app/test-bbdev/test_bbdev_vector.c and elsewhere.
>
> Fixes: eaafbad419bf ("cfgfile: library to interpret config files")
> Cc: [email protected]
>
> Signed-off-by: Wei Chen <[email protected]>
> ---
Looks good, there seems to be lots of places with same issue.
Perhaps putting helpers in rte_string_fns.h which handle the
common ctype case and use comparisons to avoid pitfalls.
static inline bool
rte_isspace(char c)
{
return c == ' ' || c == '\t' || c == '\n' ||
c == '\r' || c == '\f' || c == '\v';
}
Quick AI doodle.
From 653b2204ef72a45e460ba557c6b765b1965154c5 Mon Sep 17 00:00:00 2001
From: Stephen Hemminger <[email protected]>
Date: Thu, 22 Oct 2026 08:52:58 -0700
Subject: [PATCH] eal: add ASCII character classification functions
The <ctype.h> functions take an int that must be EOF or an
unsigned char value. Passing a plain char is undefined behavior
for bytes >= 0x80 where char is signed. Most of the ctype calls
in the tree do exactly that, including rte_str_skip_leading_spaces().
The ctype functions are also locale dependent, which is not
wanted when parsing devargs, config files and addresses.
Add rte_isspace(), rte_isdigit(), rte_tolower() and friends.
They take a char, use ASCII only, and never match bytes >= 0x80.
Use rte_isspace() in rte_str_skip_leading_spaces().
Signed-off-by: Stephen Hemminger <[email protected]>
---
app/test/test_string_fns.c | 44 +++++++++++++
doc/guides/rel_notes/release_26_11.rst | 6 ++
lib/eal/include/rte_string_fns.h | 89 +++++++++++++++++++++++++-
3 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/app/test/test_string_fns.c b/app/test/test_string_fns.c
index 213a9312ea..ba6b6fa8b2 100644
--- a/app/test/test_string_fns.c
+++ b/app/test/test_string_fns.c
@@ -2,6 +2,7 @@
* Copyright(c) 2010-2014 Intel Corporation
*/
+#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>
@@ -378,6 +379,47 @@ test_rte_str_to_size(void)
return 0;
}
+static int
+test_rte_ctype(void)
+{
+ unsigned int i;
+
+ /* Must match <ctype.h> in the C locale for 7-bit ASCII */
+ for (i = 0; i < 0x80; i++) {
+ char c = (char)i;
+
+ if (rte_isspace(c) != !!isspace(i) ||
+ rte_isblank(c) != !!isblank(i) ||
+ rte_isdigit(c) != !!isdigit(i) ||
+ rte_isupper(c) != !!isupper(i) ||
+ rte_islower(c) != !!islower(i) ||
+ rte_isalpha(c) != !!isalpha(i) ||
+ rte_isalnum(c) != !!isalnum(i) ||
+ rte_isxdigit(c) != !!isxdigit(i) ||
+ rte_isprint(c) != !!isprint(i) ||
+ rte_tolower(c) != (char)tolower(i) ||
+ rte_toupper(c) != (char)toupper(i)) {
+ LOG("FAIL: mismatch with ctype for 0x%02x\n", i);
+ return -1;
+ }
+ }
+
+ /* No byte outside 7-bit ASCII matches any class */
+ for (i = 0x80; i < 0x100; i++) {
+ char c = (char)i;
+
+ if (rte_isspace(c) || rte_isblank(c) || rte_isdigit(c) ||
+ rte_isalpha(c) || rte_isxdigit(c) || rte_isprint(c) ||
+ rte_tolower(c) != c || rte_toupper(c) != c) {
+ LOG("FAIL: non-ASCII byte 0x%02x matched\n", i);
+ return -1;
+ }
+ }
+
+ LOG("%s - PASSED\n", __func__);
+ return 0;
+}
+
static int
test_string_fns(void)
{
@@ -391,6 +433,8 @@ test_string_fns(void)
return -1;
if (test_rte_str_to_size() < 0)
return -1;
+ if (test_rte_ctype() < 0)
+ return -1;
return 0;
}
diff --git a/doc/guides/rel_notes/release_26_11.rst
b/doc/guides/rel_notes/release_26_11.rst
index be758a0c02..c6ccf6ae2d 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -60,6 +60,12 @@ New Features
Added the ``RTE_DIV_ROUND_UP`` macro in ``rte_common.h``
to divide dividend by divisor, rounding up to the nearest integer.
+* **Added ASCII character classification functions.**
+
+ Added ``rte_isspace()``, ``rte_isdigit()``, ``rte_tolower()`` and related
+ functions in ``rte_string_fns.h``. They take a ``char``, ignore locale,
+ and are safe for bytes >= 0x80, unlike the ``<ctype.h>`` functions.
+
* **Improved pointer arithmetic macros.**
Updated the macros ``RTE_PTR_ADD``, ``RTE_PTR_SUB``,
diff --git a/lib/eal/include/rte_string_fns.h b/lib/eal/include/rte_string_fns.h
index b3b5bdf275..1e3b2df9a1 100644
--- a/lib/eal/include/rte_string_fns.h
+++ b/lib/eal/include/rte_string_fns.h
@@ -12,6 +12,7 @@
#define _RTE_STRING_FNS_H_
#include <ctype.h>
+#include <stdbool.h>
#include <stdio.h>
#include <string.h>
@@ -115,6 +116,92 @@ extern "C" {
ssize_t
rte_strscpy(char *dst, const char *src, size_t dsize);
+/*
+ * ASCII character classification.
+ *
+ * These are locale independent replacements for the <ctype.h> functions.
+ * Unlike the standard versions they take a char, so they are safe to call
+ * with any byte from a string, including values >= 0x80 on targets where
+ * char is signed. Characters outside the 7-bit ASCII range never match.
+ */
+
+/** Return true if c is ' ', '\t', '\n', '\v', '\f' or '\r'. */
+static inline bool
+rte_isspace(char c)
+{
+ return c == ' ' || (c >= '\t' && c <= '\r');
+}
+
+/** Return true if c is ' ' or '\t'. */
+static inline bool
+rte_isblank(char c)
+{
+ return c == ' ' || c == '\t';
+}
+
+/** Return true if c is a decimal digit. */
+static inline bool
+rte_isdigit(char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+/** Return true if c is an ASCII uppercase letter. */
+static inline bool
+rte_isupper(char c)
+{
+ return c >= 'A' && c <= 'Z';
+}
+
+/** Return true if c is an ASCII lowercase letter. */
+static inline bool
+rte_islower(char c)
+{
+ return c >= 'a' && c <= 'z';
+}
+
+/** Return true if c is an ASCII letter. */
+static inline bool
+rte_isalpha(char c)
+{
+ return rte_isupper(c) || rte_islower(c);
+}
+
+/** Return true if c is an ASCII letter or decimal digit. */
+static inline bool
+rte_isalnum(char c)
+{
+ return rte_isalpha(c) || rte_isdigit(c);
+}
+
+/** Return true if c is a hexadecimal digit. */
+static inline bool
+rte_isxdigit(char c)
+{
+ return rte_isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <=
'F');
+}
+
+/** Return true if c is a printable ASCII character, including space. */
+static inline bool
+rte_isprint(char c)
+{
+ return c >= ' ' && c <= '~';
+}
+
+/** Convert an ASCII uppercase letter to lowercase, else return c. */
+static inline char
+rte_tolower(char c)
+{
+ return rte_isupper(c) ? c - 'A' + 'a' : c;
+}
+
+/** Convert an ASCII lowercase letter to uppercase, else return c. */
+static inline char
+rte_toupper(char c)
+{
+ return rte_islower(c) ? c - 'a' + 'A' : c;
+}
+
/**
* @warning
* @b EXPERIMENTAL: this API may change without prior notice.
@@ -133,7 +220,7 @@ rte_str_skip_leading_spaces(const char *src)
{
const char *p = src;
- while (isspace(*p))
+ while (rte_isspace(*p))
p++;
return p;
--
2.43.0