Skip to content

The Windows fnmatch shim backtracks across the whole path since hide patterns may cross separators #315

Description

@EdmondDantes

src/static/http_static_path.c carries a fnmatch shim for Windows, used only
for StaticHandler::hide() patterns. It is recursive with the classic
*-backtracking shape: each * re-invokes the matcher for every remaining
position.

Until #309 that recursion was bounded to one path segment, because * always
stopped at a separator. #309 made the shim honour FNM_PATHNAME, so a pattern
containing ** matches with the flag off and * walks the whole path. A
pattern of the a*a*a*a*a*b/ shape, matched against a long path that does not
match it, then costs exponential time in the number of stars.

Runs of stars are collapsed, which removes the **** case and the form the
documentation encourages. It does not remove the general one.

Bounds, so the shape of the risk is clear:

  • The pattern is written by the operator, not by the caller — this is a
    careless configuration plus attacker-chosen request paths, not an injection.
  • It is time, not stack: recursion depth stays inside the path length, about
    4 KiB of frames.
  • POSIX fnmatch uses a non-recursive matcher and does not have the cliff, so
    the exposure is Windows-only. A configuration tested on Linux would not show
    it.

Options

The standard fix is the iterative two-pointer glob matcher, which keeps one
saved star position and runs in linear time; it handles *, ? and literals,
which is everything this shim handles. Adapting it to stop * at a separator
when FNM_PATHNAME is set is the only part that needs care, and the existing
StaticHide unit table is the check.

The cheaper option is a step budget: count invocations and give up — but giving
up has to mean "hidden", not "served", or the fix opens what it was meant to
close.

Raised by the review of #310, marked there as hardening rather than a defect.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions