[ 
https://issues.apache.org/jira/browse/SLING-13353?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Rishabh Daim updated SLING-13353:
---------------------------------
    Affects Version/s: Auth Core 2.0.2

> 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. 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.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to