[ 
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)

Reply via email to