From d6b0f0e3351bf14642e9010514e80ccfc6d489d9 Mon Sep 17 00:00:00 2001 From: BarakSrour <85163444+BarakSrour@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:54:44 -0400 Subject: [PATCH 1/3] Harden AuthXMLRequest.setPrincipal against arbitrary class instantiation Resolve the caller-named class with initialize=false and confirm it is a java.security.Principal before instantiating it. The (Principal) cast is evaluated only after newInstance() has already run the class's static initializer and constructor, so it is not a control. Same hardening as AuthXMLUtils.createCustomCallback in edcf968c (GHSA-wg5r-wc3x-39vc / CVE-2026-62379). setPrincipal has no callers in the tree, so this is defence in depth on a public method, not a fix for a reachable vulnerability. --- .../authentication/server/AuthXMLRequest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java b/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java index 76b0b9e7ee..5d6908782d 100644 --- a/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java +++ b/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java @@ -281,7 +281,20 @@ public void setIndexName(String indexName) { */ public void setPrincipal(String className,String principalValue) { try { - Class clName = Class.forName(className); + // className is read from the /authservice (PLL) request XML, so it must not be + // resolved with side effects. Load the class without running its static + // initializers and verify it is a Principal BEFORE instantiating it: the + // (Principal) cast below is evaluated only after newInstance() has already run + // the class's static initializer and constructor, so the cast is not a control. + // Same hardening as AuthXMLUtils.createCustomCallback + // (GHSA-wg5r-wc3x-39vc / CVE-2026-62379). + Class clName = Class.forName(className, false, + AuthXMLRequest.class.getClassLoader()); + if (!Principal.class.isAssignableFrom(clName)) { + debug.error("AuthXMLRequest.setPrincipal : class " + className + + " is not a java.security.Principal implementation"); + return; + } principal = (Principal) clName.newInstance(); } catch (ClassNotFoundException ce) { //debug.error("Error creating class instance " , e); From 5f05b67206f7ee5d6ea960a8d94d5b7a3e55f2d2 Mon Sep 17 00:00:00 2001 From: BarakSrour <85163444+BarakSrour@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:54:45 -0400 Subject: [PATCH 2/3] Add AuthXMLRequestSecurityTest for setPrincipal class resolution Asserts that a non-Principal class is neither initialized nor instantiated, and that a legitimate Principal still resolves. The probe's flags deliberately live in a separate holder class: reading or writing a static field triggers that class's initialization, so flags held on the probe itself would be set by the test's own reset and the was-it-initialized assertion would pass even against the unhardened code. Verified that both assertions fail against the 1-arg Class.forName shape. --- .../server/AuthXMLRequestSecurityTest.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 openam-core/src/test/java/com/sun/identity/authentication/server/AuthXMLRequestSecurityTest.java diff --git a/openam-core/src/test/java/com/sun/identity/authentication/server/AuthXMLRequestSecurityTest.java b/openam-core/src/test/java/com/sun/identity/authentication/server/AuthXMLRequestSecurityTest.java new file mode 100644 index 0000000000..91d12c6d13 --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/authentication/server/AuthXMLRequestSecurityTest.java @@ -0,0 +1,103 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + */ + +package com.sun.identity.authentication.server; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Principal; + +import org.testng.annotations.Test; + +/** + * setPrincipal(String, String) resolves a class by name. It is not reachable in-tree today, but it + * is public, so it is held to the same contract as AuthXMLUtils.createCustomCallback after + * GHSA-wg5r-wc3x-39vc: a class that is not a Principal must be rejected without being initialized + * or instantiated. + */ +public class AuthXMLRequestSecurityTest { + + @Test + public void setPrincipalRejectsNonPrincipalClassWithoutLoadingOrInstantiatingIt() { + ProbeFlags.initialised = false; + ProbeFlags.instantiated = false; + + AuthXMLRequest request = new AuthXMLRequest(); + request.setPrincipal(NonPrincipalProbe.class.getName(), "ignored"); + + assertThat(request.getPrincipal()) + .as("a class that is not a Principal must not become the principal").isNull(); + assertThat(ProbeFlags.instantiated) + .as("the named class must never be instantiated").isFalse(); + assertThat(ProbeFlags.initialised) + .as("the named class must not even be initialized: Class.forName must be called" + + " with initialize=false so an unvalidated name cannot run a static" + + " initializer").isFalse(); + } + + @Test + public void setPrincipalStillAcceptsALegitimatePrincipal() { + AuthXMLRequest request = new AuthXMLRequest(); + request.setPrincipal(LegitimatePrincipal.class.getName(), "ignored"); + + assertThat(request.getPrincipal()) + .as("a genuine Principal implementation must still resolve") + .isInstanceOf(LegitimatePrincipal.class); + } + + @Test + public void setPrincipalIgnoresAnUnknownClassName() { + AuthXMLRequest request = new AuthXMLRequest(); + request.setPrincipal("com.example.NoSuchClassAnywhere", "ignored"); + + assertThat(request.getPrincipal()).isNull(); + } + + /** + * The flags live outside the probe on purpose. Reading or writing a static field of a class + * triggers that class's initialization, so flags held on the probe itself would be set by the + * test's own reset, and the "was it initialized" assertion would pass even against the + * unhardened code. Keeping them here means the probe is only ever named by a class literal, + * which does not trigger initialization, so the assertion can actually fail. + */ + static final class ProbeFlags { + + static volatile boolean initialised; + static volatile boolean instantiated; + + private ProbeFlags() { + } + } + + /** Not a Principal. Records, elsewhere, whether it was initialized or constructed. */ + public static class NonPrincipalProbe { + + static { + ProbeFlags.initialised = true; + } + + public NonPrincipalProbe() { + ProbeFlags.instantiated = true; + } + } + + /** A minimal genuine Principal, to show the guard is not over-broad. */ + public static class LegitimatePrincipal implements Principal { + + @Override + public String getName() { + return "legitimate"; + } + } +} From 18ebdeb23ae893638b98aa4a7fbfa27168ae55ba Mon Sep 17 00:00:00 2001 From: BarakSrour <85163444+BarakSrour@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:41:56 -0400 Subject: [PATCH 3/3] Reword the setPrincipal comment: state that it has no in-tree callers The comment opened by asserting className is read from the /authservice (PLL) request XML, which is not true and contradicted this PR's own test javadoc. setPrincipal has no in-tree callers and AuthXMLTags.PRINCIPAL is declared but never parsed into it -- the sentence was carried over from the AuthXMLUtils.createCustomCallback case, where the value really does come off the wire. Reworded to say what is actually going on: no callers today, but it is public API on a request object, so it is held to the same contract anyway. The technical substance is unchanged -- initialize=false, verify before instantiating, and the note that the cast is not a control. Comment-only; no code lines touched. --- .../authentication/server/AuthXMLRequest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java b/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java index 5d6908782d..0131427a1f 100644 --- a/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java +++ b/openam-core/src/main/java/com/sun/identity/authentication/server/AuthXMLRequest.java @@ -281,13 +281,14 @@ public void setIndexName(String indexName) { */ public void setPrincipal(String className,String principalValue) { try { - // className is read from the /authservice (PLL) request XML, so it must not be - // resolved with side effects. Load the class without running its static - // initializers and verify it is a Principal BEFORE instantiating it: the - // (Principal) cast below is evaluated only after newInstance() has already run - // the class's static initializer and constructor, so the cast is not a control. - // Same hardening as AuthXMLUtils.createCustomCallback - // (GHSA-wg5r-wc3x-39vc / CVE-2026-62379). + // Nothing in the tree reaches this today: setPrincipal has no in-tree callers and + // AuthXMLTags.PRINCIPAL is declared but never parsed into it. It is public API on a + // request object, though, so it is held to the same contract as + // AuthXMLUtils.createCustomCallback after GHSA-wg5r-wc3x-39vc / CVE-2026-62379. + // Load the class without running its static initializers and verify it is a + // Principal BEFORE instantiating it: the (Principal) cast below is evaluated only + // after newInstance() has already run the class's static initializer and + // constructor, so the cast is not a control. Class clName = Class.forName(className, false, AuthXMLRequest.class.getClassLoader()); if (!Principal.class.isAssignableFrom(clName)) {