spring-projects / spring-projects/spring-security
ACL parent cycle causes infinite recursion in BasicLookupStrategy.convert()
@jzheaux is already working on this.
Since Aug 7, 2026.
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 6.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 52
Description
AclImpl.setParent() only rejects direct self-parenting (newParent.equals(this)); it does not detect indirect cycles (A → B → A, or longer chains). Once such a cycle is persisted via JdbcMutableAclService.updateAcl(), every subsequent lookup of any ACL in the cycle recurses forever.
Where:
AclImpl.setParent(Acl)(acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java) only guardsnewParent.equals(this)— no ancestor walk.JdbcMutableAclService.updateObjectIdentity(MutableAcl)(acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java) writesparentIdwith no cycle check.BasicLookupStrategy.convert(...)(acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java, ~lines 386-431) recurses onStubAclParentids with no visited-set, so a stored cycle causes unconditional infinite recursion /StackOverflowErroron every future read.JdbcMutableAclService.clearCacheIncludingChildren(...)has the same unguarded recursive shape.
Repro sketch:
- User owns (or has
CHANGE_GENERALon) two ACL-protected objects A and B. aclA.setParent(aclB); aclService.updateAcl(aclA);aclB.setParent(aclA); aclService.updateAcl(aclB);- Any subsequent
aclService.readAclById(...)for A or B recurses forever.
Suggested fix shape:
- In
AclImpl.setParent(Acl), walknewParent's ancestor chain viagetParentAcl()and reject (IllegalArgumentException, same unchecked pattern as the existing self-parentAssert.isTrue) ifthisis reachable. - In
JdbcMutableAclService.updateObjectIdentity(MutableAcl), walk the proposed parent's stored ancestry before writingparentIdand reject on a match, for the case where the in-memory check is bypassed (e.g. a customMutableAclimplementation, or direct DB manipulation). - Add a visited-
Setguard inBasicLookupStrategy.convert(...)andclearCacheIncludingChildren(...)so that already-corrupt data (e.g. from before this fix, or written through a path that skips the new checks) fails fast with a clear exception instead of aStackOverflowError.
This should be additive/non-breaking: a stored parent cycle is already guaranteed to crash every future read of the affected ACLs, so no working application depends on being able to create one. Rejecting the write earlier, with a clear exception, doesn't remove any capability that currently functions.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.