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