From: Karel Zak Date: Tue, 23 Jun 2026 14:21:39 +0000 (+0200) Subject: libsmartcols: reject pathological regex patterns in filter X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=622b2f3f5c548f54f36e56e00e7cde3540c5e7f2;p=thirdparty%2Futil-linux.git libsmartcols: reject pathological regex patterns in filter Consecutive ERE quantifiers (e.g. a++, a**) and nested group repetitions (e.g. (a+)+) cause glibc regcomp() to allocate gigabytes for the NFA, triggering OOM on small inputs. Reject such patterns in filter_compile_param() before they reach regcomp(), and limit regex pattern size to SCOLS_FILTER_MAX_REGSZ (256 bytes). Document all filter expression limits in scols-filter(5). Addresses: https://github.com/util-linux/util-linux/pull/4436 Signed-off-by: Karel Zak --- diff --git a/libsmartcols/scols-filter.5.adoc b/libsmartcols/scols-filter.5.adoc index 79921a307..5822ac00a 100644 --- a/libsmartcols/scols-filter.5.adoc +++ b/libsmartcols/scols-filter.5.adoc @@ -111,6 +111,14 @@ long double. The `integer` may be followed by the multiplicative suffixes KiB, GiB, TiB, PiB, EiB, ZiB, and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"). +The filter expression string is limited to 1024 bytes. The number of nodes +(parameters and operators) in a single expression is limited to 256. + +Regular expression patterns used with the `=~` and `!~` operators are limited to +256 bytes. Patterns with consecutive quantifiers (e.g., `a++`, `a**`) or nested +group repetitions (e.g., `(a+)+`) are rejected because they can cause excessive +memory consumption in the POSIX regex compiler. + == AUTHORS mailto:kzak@redhat.com[Karel Zak] diff --git a/libsmartcols/src/filter-param.c b/libsmartcols/src/filter-param.c index b5fbc7ca7..b7bff6968 100644 --- a/libsmartcols/src/filter-param.c +++ b/libsmartcols/src/filter-param.c @@ -131,6 +131,32 @@ struct filter_node *filter_new_param( return (struct filter_node *) n; } +/* Consecutive ERE quantifiers (a++, a**) and nested group repetitions + * ((a+)+, (a*)*) cause glibc regcomp() to allocate gigabytes for the NFA. */ +static int is_unsafe_regex(const char *pattern) +{ + size_t i, len = strlen(pattern); + + for (i = 0; i + 1 < len; i++) { + if ((pattern[i] == '+' || pattern[i] == '*') + && (pattern[i + 1] == '+' || pattern[i + 1] == '*')) + return 1; + + if (pattern[i] == ')' + && (pattern[i + 1] == '+' || pattern[i + 1] == '*')) { + int j; + + for (j = (int) i - 1; j >= 0; j--) { + if (pattern[j] == '(') + break; + if (pattern[j] == '+' || pattern[j] == '*') + return 1; + } + } + } + return 0; +} + int filter_compile_param(struct libscols_filter *fltr, struct filter_param *n) { int rc; @@ -141,6 +167,10 @@ int filter_compile_param(struct libscols_filter *fltr, struct filter_param *n) return 0; if (n->type != SCOLS_DATA_STRING || !n->val.str) return -EINVAL; + if (strlen(n->val.str) > SCOLS_FILTER_MAX_REGSZ) + return -EINVAL; + if (is_unsafe_regex(n->val.str)) + return -EINVAL; n->re = calloc(1, sizeof(regex_t)); if (!n->re) diff --git a/libsmartcols/src/fuzz.c b/libsmartcols/src/fuzz.c index e06724df6..0ecfa2efc 100644 --- a/libsmartcols/src/fuzz.c +++ b/libsmartcols/src/fuzz.c @@ -8,65 +8,60 @@ static void process_string(const char *str) { - /* Parse the input as a libsmartcols filter expression. This drives the - flex scanner, the bison grammar, parameter/holder construction and the - regcomp() done for the =~ operator. */ - struct libscols_filter *fltr = scols_new_filter(str); + struct libscols_filter *fltr = scols_new_filter(str); - scols_unref_filter(fltr); + scols_unref_filter(fltr); } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - char *str; + char *str; - /* Larger than the library's 1024-byte limit to exercise the - * rejection path, small enough to avoid OOM in sanitizer builds */ - if (size > 4096) - return 0; + if (size > 4096) + return 0; - str = malloc(size + 1); - if (!str) - return 0; + str = malloc(size + 1); + if (!str) + return 0; - memcpy(str, data, size); - str[size] = '\0'; + memcpy(str, data, size); + str[size] = '\0'; - process_string(str); + process_string(str); - free(str); - return 0; + free(str); + return 0; } #ifndef FUZZ_TARGET int main(int argc, char **argv) { - for (int i = 1; i < argc; i++) { - FILE *f = fopen(argv[i], "r"); - char *buf; - long len; - size_t n; + for (int i = 1; i < argc; i++) { + FILE *f = fopen(argv[i], "r"); + char *buf; + long len; + size_t n; - if (!f) - continue; - fseek(f, 0, SEEK_END); - len = ftell(f); - fseek(f, 0, SEEK_SET); - if (len < 0) { - fclose(f); - continue; - } - buf = malloc(len + 1); - if (!buf) { - fclose(f); - continue; - } - n = fread(buf, 1, len, f); - fclose(f); - buf[n] = '\0'; - process_string(buf); - free(buf); - } - return 0; + if (!f) + continue; + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + if (len < 0) { + fclose(f); + continue; + } + buf = malloc(len + 1); + if (!buf) { + fclose(f); + continue; + } + n = fread(buf, 1, len, f); + fclose(f); + buf[n] = '\0'; + process_string(buf); + free(buf); + } + return 0; } #endif diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index fe5987795..f7504830d 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -566,6 +566,7 @@ struct libscols_filter { #define SCOLS_FILTER_MAX_EXPRSZ 1024 #define SCOLS_FILTER_MAX_NODES 256 +#define SCOLS_FILTER_MAX_REGSZ 256 struct filter_node *__filter_new_node(enum filter_ntype type, size_t sz); void filter_ref_node(struct filter_node *n);