[
https://issues.apache.org/jira/browse/SLING-13353?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Rishabh Daim updated SLING-13353:
---------------------------------
Description:
h4. Problem
{{SlingAuthenticator}} recurses without bound between {{handleLoginFailure}}
and {{getAnonymousResolver}} when acquiring the anonymous {{ResourceResolver}}
keeps failing with {{LoginException}} (e.g. the backing repository is
unavailable). The stack grows until {{StackOverflowError}}, and since each
failed request logs the full repeating trace, a sustained failure floods the
error log (observed in the field growing until the disk filled).
h4. Cause
In {{org.apache.sling.auth.core.impl.SlingAuthenticator}}, the two methods call
each other with no termination guard:
* {{getAnonymousResolver}}: on {{LoginException}} from {{getResourceResolver}},
calls {{handleLoginFailure}};
* {{handleLoginFailure}}: for a {{LoginException}} with anonymous access still
allowed, calls {{getAnonymousResolver}}.
So {{getAnonymousResolver -> handleLoginFailure -> getAnonymousResolver ->
...}} repeats as long as the anonymous login keeps failing. The anonymous
fallback is never marked as already-attempted.
h4. Reproduce
Anonymous access allowed; make {{getResourceResolver}} throw
{{LoginException}}; issue a plain request to an anonymous path. Expected: one
anonymous attempt, then terminate. Actual: recursion to {{StackOverflowError}}.
h4. Fix
Per-request re-entrancy guard: {{getAnonymousResolver}} marks (request
attribute) that anonymous resolution was attempted; {{handleLoginFailure}} only
falls back when the mark is absent - at most one attempt per request,
terminating normally on repeated failure. All currently-successful paths
(including a single legitimate anonymous fallback) are unchanged. Regression
test drives repeated {{LoginException}} and asserts no {{StackOverflowError}}
and exactly one {{getResourceResolver}} call; it fails without the guard.
Affects current trunk (auth-core bundle 66) and earlier releases with the same
structure.
was:
h4. Summary
{{SlingAuthenticator}} can recurse indefinitely between
{{handleLoginFailure(...)}} and {{getAnonymousResolver(...)}} when acquiring
the anonymous {{ResourceResolver}} keeps failing with a {{LoginException}}
(e.g. the backing repository is temporarily unavailable). The mutual recursion
grows the call stack until a {{StackOverflowError}} is thrown, and because each
failed request logs the full repeating stack trace, a sustained failure
produces a very large volume of error logging (observed in the field as an
error log growing until the disk filled).
h4. Where
{{org.apache.sling.auth.core.impl.SlingAuthenticator}} (auth-core bundle). Two
methods call each other with no termination guard:
* {{{}getAnonymousResolver(...){}}}: on {{{}isAnonAllowed(request){}}}, it
calls {{{}resourceResolverFactory.getResourceResolver(authInfo){}}}; if that
throws {{{}LoginException{}}}, the {{catch}} block calls
{{{}handleLoginFailure(request, response, new AuthenticationInfo(null,
"anonymous user"), re){}}}.
* {{{}handleLoginFailure(...){}}}: for a {{{}LoginException{}}}, when
{{isAnonAllowed(request)}} is still true (and not an auth-handler / validate
request), it calls {{{}getAnonymousResolver(request, response, new
AuthenticationInfo(null)){}}}.
So {{getAnonymousResolver -> handleLoginFailure -> getAnonymousResolver ->
...}} repeats without bound as long as the anonymous {{getResourceResolver}}
keeps throwing {{{}LoginException{}}}.
h4. Steps to reproduce
# Anonymous access is allowed for the requested path (default configuration).
# Make {{ResourceResolverFactory.getResourceResolver(...)}} fail with
{{LoginException}} for the anonymous credentials (e.g. the repository is
unavailable).
# Issue a plain (non-validate, non-auth-handler) request to an anonymously
accessible path.
Expected: the anonymous login is attempted once; on failure the request is
terminated (credentials requested / error response).
Actual: {{handleLoginFailure}} and {{getAnonymousResolver}} recurse until
{{{}StackOverflowError{}}}; the failure (with its large stack trace) is logged
for every affected request.
h4. Root cause
The anonymous fallback assumes that if primary authentication fails, falling
back to anonymous will either succeed or fail terminally. It does not handle
the case where the anonymous acquisition itself fails repeatedly:
{{{}getAnonymousResolver{}}}'s failure path re-enters
{{{}handleLoginFailure{}}}, which re-enters {{{}getAnonymousResolver{}}}. There
is no "anonymous already attempted" guard and no depth bound.
h4. Proposed fix
Add a per-request re-entrancy guard: {{getAnonymousResolver}} marks (via a
request attribute) that anonymous resolution has been attempted;
{{handleLoginFailure}} only falls back to {{getAnonymousResolver}} when that
mark is absent. This attempts the anonymous fallback at most once per request
and, on repeated failure, terminates normally instead of recursing. Behaviour
for all currently-successful paths (including a single legitimate anonymous
fallback after a primary-auth failure) is unchanged.
A regression test drives {{getAnonymousResolver}} with a
{{ResourceResolverFactory}} that always throws {{LoginException}} and asserts
that (a) no {{StackOverflowError}} occurs and (b) {{getResourceResolver}} is
invoked exactly once. Without the guard the test fails with a
{{StackOverflowError}} (thousands of recursive invocations); with the guard it
passes.
> SlingAuthenticator: unbounded handleLoginFailure <-> getAnonymousResolver
> recursion when anonymous login repeatedly fails
> -------------------------------------------------------------------------------------------------------------------------
>
> Key: SLING-13353
> URL: https://issues.apache.org/jira/browse/SLING-13353
> Project: Sling
> Issue Type: Bug
> Affects Versions: Auth Core 2.0.2
> Reporter: Rishabh Daim
> Priority: Major
>
> h4. Problem
> {{SlingAuthenticator}} recurses without bound between {{handleLoginFailure}}
> and {{getAnonymousResolver}} when acquiring the anonymous
> {{ResourceResolver}} keeps failing with {{LoginException}} (e.g. the backing
> repository is unavailable). The stack grows until {{StackOverflowError}}, and
> since each failed request logs the full repeating trace, a sustained failure
> floods the error log (observed in the field growing until the disk filled).
> h4. Cause
> In {{org.apache.sling.auth.core.impl.SlingAuthenticator}}, the two methods
> call each other with no termination guard:
> * {{getAnonymousResolver}}: on {{LoginException}} from
> {{getResourceResolver}}, calls {{handleLoginFailure}};
> * {{handleLoginFailure}}: for a {{LoginException}} with anonymous access
> still allowed, calls {{getAnonymousResolver}}.
> So {{getAnonymousResolver -> handleLoginFailure -> getAnonymousResolver ->
> ...}} repeats as long as the anonymous login keeps failing. The anonymous
> fallback is never marked as already-attempted.
> h4. Reproduce
> Anonymous access allowed; make {{getResourceResolver}} throw
> {{LoginException}}; issue a plain request to an anonymous path. Expected: one
> anonymous attempt, then terminate. Actual: recursion to
> {{StackOverflowError}}.
> h4. Fix
> Per-request re-entrancy guard: {{getAnonymousResolver}} marks (request
> attribute) that anonymous resolution was attempted; {{handleLoginFailure}}
> only falls back when the mark is absent - at most one attempt per request,
> terminating normally on repeated failure. All currently-successful paths
> (including a single legitimate anonymous fallback) are unchanged. Regression
> test drives repeated {{LoginException}} and asserts no {{StackOverflowError}}
> and exactly one {{getResourceResolver}} call; it fails without the guard.
> Affects current trunk (auth-core bundle 66) and earlier releases with the
> same structure.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)