Skip to content

Conversation

@cfriedt
Copy link
Member

@cfriedt cfriedt commented Nov 15, 2025

A regression in 936d027 introduced a subtle bug in the way that escaped expressions were handled.

The regression originated with the assumption that test data (originally adapted from a 3rd-party testsuite) was correct when it was in fact flawed.

Specifically, fnmatch("[[?*\\]", "\\", 0) should fail (FNM_NOMATCH), since the "\" sequence (a single backslash after compilation) escapes the following ']' character, thus leaving the bracket expression incomplete.

As @keith-packard has pointed out, pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01, a bracket expression is only interpreted as a bracket expression, when a proper bracket expression is formed.

Therefore, the pattern is interpreted as the sequence '[', '[', '?', '*', ']' and the call should return FNM_NOMATCH to indicate failure rather than 0 to indicate success.

Added new test cases from #98827 and some commentary for subsequent reviewers.

This change does not completely fix #55186 but is related to it.

The remaining changes are fixed in #97400

@cfriedt cfriedt force-pushed the fix-fnmatch-regression branch from 6f09b19 to 462e5af Compare November 15, 2025 17:28
@cfriedt cfriedt assigned jukkar and cfriedt and unassigned jukkar Nov 15, 2025
@cfriedt cfriedt added bug The issue is a bug, or the PR is fixing a bug backport v4.3-branch Request backport to the v4.3-branch Regression Something, which was working, does not anymore area: POSIX POSIX API Library area: Tests Issues related to a particular existing or missing test backport v4.2-branch Request backport to the v4.2-branch labels Nov 15, 2025
@cfriedt cfriedt marked this pull request as ready for review November 15, 2025 18:12
@cfriedt cfriedt requested a review from ycsin November 15, 2025 18:12
@zephyrbot zephyrbot requested a review from nashif November 15, 2025 18:13
@keith-packard
Copy link
Contributor

Specifically, fnmatch("[[?*\\]", "\\", 0) should fail, since the "" sequence (a single backslash after compilation) escapes the following ']' character, thus leaving the bracket expression incomplete. Since the bracket expression (and therefore the whole pattern) is erroneous, the call should return FNM_NOMATCH to indicate failure rather than 0 to indicate success.

The pattern is not erroneous (which would cause the function to return -FNM_NOMATCH). It is a legitimate pattern equivalent to "[[?*]" and not a bracket expression at all. We might want to add a test to verify that the pattern works in this way.

Note that Musl has the same bug, which is why the test was flawed.

@cfriedt
Copy link
Member Author

cfriedt commented Nov 16, 2025

The pattern is not erroneous (which would cause the function to return -FNM_NOMATCH). It is a legitimate pattern equivalent to "[[?*]" and not a bracket expression at all. We might want to add a test to verify that the pattern works in this way.

Ok - sorry - I was proceeding based on your comment here, where you wrote that ] is escaped, which makes sense to me.
#98827 (comment)

Maybe I was reading-in to your comment too far though, and assumed that if the final ] were escaped, that it would not form a proper bracket expression. To me, it would seem that interpreting an escaped ] without an additional non-escaped ] following it would be more of an opportunistic interpretation to force \] to close the bracket expression rather than being a character to match (assuming a following ] would properly close the bracket expression). I have not found any part of the specification that supports that interpretation though, and the specification also states that escaping an ordinary character outside of a bracket expression is undefined.

In either case, when the pattern is invalid or when it is "[[?*]", fnmatch() should return FNM_NOMATCH; in the former interpretation, to indicate an error of some kind, and in the latter interpretation, the \\ in the input would not be matched.

I can update the PR description and / or commentary as you like, but it would be nice to have a reference to the specification to support it.

I've confirmed that Zephyr's implementation recognizes "[[?*\\]" (with 0 flags) as an unterminated (i.e. invalid) bracket expression.

@cfriedt
Copy link
Member Author

cfriedt commented Nov 16, 2025

It would seem that even the glibc agrees with the former interpretation (that there is an incomplete bracket expression).

A sample program I wrote to test

#include <fnmatch.h>
#include <stdio.h>

int main()
{
  const char *pat = "[abc\\]";
  const char *in = "a";
  int ret;

  ret = fnmatch(pat, in, 0);
  if (ret == 0) {
    printf("fnmatch(\"%s\", \"%s\", 0) -> success!\n", pat, in);
  } else {
    printf("fnmatch(\"%s\", \"%s\", 0) -> failed! (%d)\n", pat, in, ret);
  }

  return ret;
}

produces the output below.

$ /tmp/blah 
fnmatch("[abc\]", "a", 0) -> failed! (1)

It fails as well with FNM_NOMATCH when pat = "[[?*\\]" and in = "\\".

@keith-packard
Copy link
Contributor

In either case, when the pattern is invalid or when it is "[[?*]", fnmatch() should return FNM_NOMATCH; in the former interpretation, to indicate an error of some kind, and in the latter interpretation, the \\ in the input would not be matched.

I think you've mis-interpreted my comment -- yes, this is an invalid bracket expression, but https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 says that you only get a bracket expression when the bracket expression is valid, otherwise you interpret the bytes as not being part of a bracket expression at all. An valid expression which does not match the string returns FNM_NOMATCH, an erroneous expression returns some other non-zero value (glibc, picolibc and Zephyr all return -FNM_NOMATCH).

So, the difference is which return value is expected, in this case the expression is valid (although does not contain a bracket expression), it's just that it doesn't match the string, so it should return FNM_NOMATCH.

A regression in 936d027 introduced a
subtle bug in the way that escaped expressions were handled.

The regression originated with the assumption that test data (originally
adapted from a 3rd-party testsuite) was correct when it was in fact
flawed.

Specifically, `fnmatch("[[?*\\]", "\\", 0)` should fail (`FNM_NOMATCH`),
since the "\\" sequence (a single backslash after compilation) escapes
the following ']' character, thus leaving the bracket expression
incomplete.

As @keith-packard  has pointed out,
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/\
V3_chap02.html#tag_18_13_01 says that a bracket expression is only
interpreted as a bracket expression, when a proper bracket expression
is formed.

Therefore, the pattern is interpreted as the sequence
`'['`, `'['`, `'?'`, `*` (wildcard), `']'` and the call should return
`FNM_NOMATCH` to indicate failure rather than 0 to indicate success.

Added new test cases from zephyrproject-rtos#98827 and some commentary for subsequent
reviewers.

This change does not completely fix zephyrproject-rtos#55186 but is related to it.

Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
Signed-off-by: Keith Packard <keithp@keithp.com>
@cfriedt cfriedt force-pushed the fix-fnmatch-regression branch from 462e5af to 501511e Compare November 19, 2025 17:19
@cfriedt
Copy link
Member Author

cfriedt commented Nov 19, 2025

@keith-packard - thanks for clarifying. I've updated the PR description and comment.

@sonarqubecloud
Copy link

@cfriedt
Copy link
Member Author

cfriedt commented Nov 24, 2025

@keith-packard - just a gentle ping to re-review.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is incorrect. The first path component, "a", matches *[![:digit:]]* as 'a' is not a digit. The second path component, "b", matches [![:d-d] because 'b' is not [, not : and not any of the range d-d.

Copy link
Contributor

@keith-packard keith-packard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the remaining bugs in the implementation and update the tests to conform with the spec.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is also incorrect, again "a" matches *[![:digit:]]* while "[" matches one of [, : or the range d-d.

I'd strongly prefer that the bugs in the Zephyr implementation be fixed before this series is merged; there's no particular reason to commit known-buggy code, especially as this function doesn't appear to be urgently in need of updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: POSIX POSIX API Library area: Tests Issues related to a particular existing or missing test backport v4.2-branch Request backport to the v4.2-branch backport v4.3-branch Request backport to the v4.3-branch bug The issue is a bug, or the PR is fixing a bug Regression Something, which was working, does not anymore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

posix: fnmatch: fix known bugs

4 participants