From eda15aa19c36142984edaa08850132ca6ae7a369 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 22 Jan 2026 12:16:09 +0000 Subject: [PATCH] 8277489: Rewrite JAAS UnixLoginModule with FFM Co-authored-by: Martin Doerr Reviewed-by: mdoerr, ascarpino, erikj --- make/modules/jdk.security.auth/Lib.gmk | 17 +- src/java.base/share/classes/module-info.java | 3 +- .../security/auth/module/UnixLoginModule.java | 32 +++- .../sun/security/auth/module/UnixSystem.java | 180 ++++++++++++++++-- .../unix/native/libjaas/Unix.c | 130 ------------- .../security/auth/module/AllPlatforms.java | 23 ++- 6 files changed, 220 insertions(+), 165 deletions(-) delete mode 100644 src/jdk.security.auth/unix/native/libjaas/Unix.c diff --git a/make/modules/jdk.security.auth/Lib.gmk b/make/modules/jdk.security.auth/Lib.gmk index 9ead32dbe12..96d609f08d6 100644 --- a/make/modules/jdk.security.auth/Lib.gmk +++ b/make/modules/jdk.security.auth/Lib.gmk @@ -31,13 +31,14 @@ include LibCommon.gmk ## Build libjaas ################################################################################ -$(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \ - NAME := jaas, \ - OPTIMIZATION := LOW, \ - EXTRA_HEADER_DIRS := java.base:libjava, \ - LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \ -)) - -TARGETS += $(BUILD_LIBJAAS) +ifeq ($(call isTargetOs, windows), true) + $(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \ + NAME := jaas, \ + OPTIMIZATION := LOW, \ + EXTRA_HEADER_DIRS := java.base:libjava, \ + LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \ + )) + TARGETS += $(BUILD_LIBJAAS) +endif ################################################################################ diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 70a79390828..d20f6311bca 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -274,7 +274,8 @@ module java.base { jdk.httpserver, jdk.jlink, jdk.jpackage, - jdk.net; + jdk.net, + jdk.security.auth; exports sun.net to java.net.http, jdk.naming.dns; diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java index 785b178e296..4f6d8ca25ef 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ package com.sun.security.auth.module; import java.util.*; -import java.io.IOException; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; @@ -34,6 +33,7 @@ import javax.security.auth.spi.*; import com.sun.security.auth.UnixPrincipal; import com.sun.security.auth.UnixNumericUserPrincipal; import com.sun.security.auth.UnixNumericGroupPrincipal; +import jdk.internal.util.OperatingSystem; /** * This {@code LoginModule} imports a user's Unix @@ -121,20 +121,34 @@ public class UnixLoginModule implements LoginModule { */ public boolean login() throws LoginException { - long[] unixGroups = null; + // Fail immediately on Windows to avoid cygwin-like functions + // being loaded, which are not supported. + if (OperatingSystem.isWindows()) { + throw new FailedLoginException + ("Failed in attempt to import " + + "the underlying system identity information" + + " on " + System.getProperty("os.name")); + } try { ss = new UnixSystem(); - } catch (UnsatisfiedLinkError ule) { + } catch (ExceptionInInitializerError | UnsatisfiedLinkError ule) { + // Errors could happen in either static blocks or the constructor, + // both have a cause. succeeded = false; - throw new FailedLoginException + var error = new FailedLoginException ("Failed in attempt to import " + "the underlying system identity information" + " on " + System.getProperty("os.name")); + if (ule.getCause() != null) { + error.initCause(ule.getCause()); + } + throw error; } userPrincipal = new UnixPrincipal(ss.getUsername()); UIDPrincipal = new UnixNumericUserPrincipal(ss.getUid()); GIDPrincipal = new UnixNumericGroupPrincipal(ss.getGid(), true); + long[] unixGroups = null; if (ss.getGroups() != null && ss.getGroups().length > 0) { unixGroups = ss.getGroups(); for (int i = 0; i < unixGroups.length; i++) { @@ -150,9 +164,11 @@ public class UnixLoginModule implements LoginModule { "succeeded importing info: "); System.out.println("\t\t\tuid = " + ss.getUid()); System.out.println("\t\t\tgid = " + ss.getGid()); - unixGroups = ss.getGroups(); - for (int i = 0; i < unixGroups.length; i++) { - System.out.println("\t\t\tsupp gid = " + unixGroups[i]); + System.out.println("\t\t\tusername = " + ss.getUsername()); + if (unixGroups != null) { + for (int i = 0; i < unixGroups.length; i++) { + System.out.println("\t\t\tsupp gid = " + unixGroups[i]); + } } } succeeded = true; diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java index f3741c10404..c0e47fafc2c 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,38 +25,194 @@ package com.sun.security.auth.module; +import jdk.internal.util.Architecture; +import jdk.internal.util.OperatingSystem; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; + +import static java.lang.foreign.MemoryLayout.PathElement.groupElement; + /** * This class implementation retrieves and makes available Unix * UID/GID/groups information for the current user. * * @since 1.4 */ +@SuppressWarnings("restricted") public class UnixSystem { - private native void getUnixInfo(); - - // Warning: the following 4 fields are used by Unix.c - - /** The current username. */ + /** + * The current username. + */ protected String username; - /** The current user ID. */ + /** + * The current user ID. + */ protected long uid; - /** The current group ID. */ + /** + * The current group ID. + */ protected long gid; - /** The current list of groups. */ + /** + * The current list of groups. + */ protected long[] groups; + private static final Linker LINKER = Linker.nativeLinker(); + private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup() + .or(LINKER.defaultLookup()); + + private static final ValueLayout.OfByte C_CHAR + = (ValueLayout.OfByte) LINKER.canonicalLayouts().get("char"); + private static final ValueLayout.OfInt C_INT + = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int"); + private static final ValueLayout.OfLong C_LONG + = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long"); + private static final AddressLayout C_POINTER + = ((AddressLayout) LINKER.canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + private static final ValueLayout C_SIZE_T + = (ValueLayout) LINKER.canonicalLayouts().get("size_t"); + + private static final StructLayout CAPTURE_STATE_LAYOUT + = Linker.Option.captureStateLayout(); + private static final VarHandle VH_errno = CAPTURE_STATE_LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("errno")); + + private static final MethodHandle MH_strerror + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("strerror"), + FunctionDescriptor.of(C_POINTER, C_INT)); + + private static final MethodHandle MH_getgroups + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getgroups"), + FunctionDescriptor.of(C_INT, C_INT, C_POINTER), + Linker.Option.captureCallState("errno")); + private static final MethodHandle MH_getuid + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getuid"), + FunctionDescriptor.of(C_INT)); + + // Some architectures require appropriate zero or sign extension to 64 bit. + // Use long directly before https://bugs.openjdk.org/browse/JDK-8336664 is resolved. + private static final boolean calling_convention_requires_int_as_long + = Architecture.isPPC64() || Architecture.isPPC64LE() || Architecture.isS390(); + + // getpwuid_r does not work on AIX, instead we use another similar function + // extern int _posix_getpwuid_r(uid_t, struct passwd *, char *, int, struct passwd **) + private static final MethodHandle MH_getpwuid_r + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow( + OperatingSystem.isAix() ? "_posix_getpwuid_r" : "getpwuid_r"), + FunctionDescriptor.of(C_INT, + calling_convention_requires_int_as_long ? C_LONG : C_INT, + C_POINTER, C_POINTER, + OperatingSystem.isAix() ? C_INT : C_SIZE_T, + C_POINTER)); + + private static final GroupLayout ML_passwd = MemoryLayout.structLayout( + C_POINTER.withName("pw_name"), + C_POINTER.withName("pw_passwd"), + C_INT.withName("pw_uid"), + C_INT.withName("pw_gid"), + // Different platforms have different fields in `struct passwd`. + // While we don't need those fields here, the struct needs to be + // big enough to avoid buffer overflow when `getpwuid_r` is called. + MemoryLayout.paddingLayout(100)); + + private static final VarHandle VH_pw_uid + = ML_passwd.varHandle(groupElement("pw_uid")); + private static final VarHandle VH_pw_gid + = ML_passwd.varHandle(groupElement("pw_gid")); + private static final VarHandle VH_pw_name + = ML_passwd.varHandle(groupElement("pw_name")); + + // The buffer size for the getpwuid_r function: + // 1. sysconf(_SC_GETPW_R_SIZE_MAX) on macOS is 4096 and 1024 on Linux, + // so we choose a bigger one. + // 2. We do not call sysconf() here because even _SC_GETPW_R_SIZE_MAX + // could be different on different platforms. + // 3. We choose int instead of long because the buffer_size argument + // might be `int` or `long` and converting from `long` to `int` + // requires an explicit cast. + private static final int GETPW_R_SIZE_MAX = 4096; + /** * Instantiate a {@code UnixSystem} and load * the native library to access the underlying system information. */ - @SuppressWarnings("restricted") public UnixSystem() { - System.loadLibrary("jaas"); - getUnixInfo(); + // The FFM code has only been tested on multiple platforms + // (including macOS, Linux, AIX, etc) and might fail on other + // *nix systems. Especially, the `passwd` struct could be defined + // differently. I've checked several and an extra 100 chars at the + // end seems enough. + try (Arena scope = Arena.ofConfined()) { + MemorySegment capturedState = scope.allocate(CAPTURE_STATE_LAYOUT); + int groupnum = (int) MH_getgroups.invokeExact(capturedState, 0, MemorySegment.NULL); + if (groupnum == -1) { + throw new RuntimeException("getgroups returns " + groupnum); + } + + var gs = scope.allocate(C_INT, groupnum); + groupnum = (int) MH_getgroups.invokeExact(capturedState, groupnum, gs); + if (groupnum == -1) { + var errno = (int) VH_errno.get(capturedState, 0L); + var errMsg = (MemorySegment) MH_strerror.invokeExact(errno); + throw new RuntimeException("getgroups returns " + groupnum + + ". Reason: " + errMsg.reinterpret(Long.MAX_VALUE).getString(0)); + } + + groups = new long[groupnum]; + for (int i = 0; i < groupnum; i++) { + groups[i] = Integer.toUnsignedLong(gs.getAtIndex(C_INT, i)); + } + + var pwd = scope.allocate(ML_passwd); + var result = scope.allocate(C_POINTER); + var buffer = scope.allocate(GETPW_R_SIZE_MAX); + + long tmpUid = Integer.toUnsignedLong((int) MH_getuid.invokeExact()); + + // Do not call invokeExact because the type of buffer_size is not + // always long in the underlying system. + int out; + if (calling_convention_requires_int_as_long) { + out = (int) MH_getpwuid_r.invoke( + tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result); + } else { + out = (int) MH_getpwuid_r.invoke( + (int) tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result); + } + if (out != 0) { + // If ERANGE (Result too large) is detected in a new platform, + // consider adjusting GETPW_R_SIZE_MAX. + var err = (MemorySegment) MH_strerror.invokeExact(out); + throw new RuntimeException(err.reinterpret(Long.MAX_VALUE).getString(0)); + } else if (result.get(ValueLayout.ADDRESS, 0).equals(MemorySegment.NULL)) { + throw new RuntimeException("the requested entry is not found"); + } else { + // uid_t and gid_t were defined unsigned. + uid = Integer.toUnsignedLong((int) VH_pw_uid.get(pwd, 0L)); + gid = Integer.toUnsignedLong((int) VH_pw_gid.get(pwd, 0L)); + username = ((MemorySegment) VH_pw_name.get(pwd, 0L)).getString(0); + } + } catch (Throwable t) { + var error = new UnsatisfiedLinkError("FFM calls failed"); + error.initCause(t); + throw error; + } } /** diff --git a/src/jdk.security.auth/unix/native/libjaas/Unix.c b/src/jdk.security.auth/unix/native/libjaas/Unix.c deleted file mode 100644 index 3a93df9ffc9..00000000000 --- a/src/jdk.security.auth/unix/native/libjaas/Unix.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#include -#include "jni_util.h" -#include "com_sun_security_auth_module_UnixSystem.h" -#include -#include -#include -#include -#include - -#include - -/* - * Declare library specific JNI_Onload entry if static build - */ -DEF_STATIC_JNI_OnLoad - -JNIEXPORT void JNICALL -Java_com_sun_security_auth_module_UnixSystem_getUnixInfo - (JNIEnv *env, jobject obj) { - - int i; - char pwd_buf[1024]; - struct passwd *pwd = NULL; - struct passwd resbuf; - jfieldID userNameID; - jfieldID userID; - jfieldID groupID; - jfieldID supplementaryGroupID; - - jstring jstr; - jlongArray jgroups; - jlong *jgroupsAsArray; - jsize numSuppGroups; - gid_t *groups; - jclass cls; - - numSuppGroups = getgroups(0, NULL); - if (numSuppGroups == -1) { - return; - } - groups = (gid_t *)calloc(numSuppGroups, sizeof(gid_t)); - if (groups == NULL) { - jclass cls = (*env)->FindClass(env,"java/lang/OutOfMemoryError"); - if (cls != NULL) { - (*env)->ThrowNew(env, cls, NULL); - } - return; - } - - cls = (*env)->GetObjectClass(env, obj); - - supplementaryGroupID = (*env)->GetFieldID(env, cls, "groups", "[J"); - if (supplementaryGroupID == 0) { - goto cleanUpAndReturn; - } - - if (getgroups(numSuppGroups, groups) != -1) { - jgroups = (*env)->NewLongArray(env, numSuppGroups); - if (jgroups == NULL) { - goto cleanUpAndReturn; - } - jgroupsAsArray = (*env)->GetLongArrayElements(env, jgroups, 0); - if (jgroupsAsArray == NULL) { - goto cleanUpAndReturn; - } - for (i = 0; i < numSuppGroups; i++) { - jgroupsAsArray[i] = groups[i]; - } - (*env)->ReleaseLongArrayElements(env, jgroups, jgroupsAsArray, 0); - (*env)->SetObjectField(env, obj, supplementaryGroupID, jgroups); - } - - userNameID = (*env)->GetFieldID(env, cls, "username", "Ljava/lang/String;"); - if (userNameID == 0) { - goto cleanUpAndReturn; - } - - userID = (*env)->GetFieldID(env, cls, "uid", "J"); - if (userID == 0) { - goto cleanUpAndReturn; - } - - groupID = (*env)->GetFieldID(env, cls, "gid", "J"); - if (groupID == 0) { - goto cleanUpAndReturn; - } - - memset(pwd_buf, 0, sizeof(pwd_buf)); - if (getpwuid_r(getuid(), &resbuf, pwd_buf, sizeof(pwd_buf), &pwd) == 0 && - pwd != NULL) { - (*env)->SetLongField(env, obj, userID, pwd->pw_uid); - (*env)->SetLongField(env, obj, groupID, pwd->pw_gid); - jstr = (*env)->NewStringUTF(env, pwd->pw_name); - if (jstr == NULL) { - goto cleanUpAndReturn; - } - (*env)->SetObjectField(env, obj, userNameID, jstr); - } else { - (*env)->SetLongField(env, obj, userID, getuid()); - (*env)->SetLongField(env, obj, groupID, getgid()); - } -cleanUpAndReturn: - free(groups); - return; -} diff --git a/test/jdk/com/sun/security/auth/module/AllPlatforms.java b/test/jdk/com/sun/security/auth/module/AllPlatforms.java index 79eea7b4d23..6f7cbfb9c52 100644 --- a/test/jdk/com/sun/security/auth/module/AllPlatforms.java +++ b/test/jdk/com/sun/security/auth/module/AllPlatforms.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,11 @@ * @test * @bug 8039951 * @summary com.sun.security.auth.module missing classes on some platforms + * @modules java.base/jdk.internal.util * @run main/othervm AllPlatforms */ +import jdk.internal.util.OperatingSystem; + import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import java.nio.file.Files; @@ -39,14 +42,14 @@ public class AllPlatforms { private static final String NT_MODULE = "NTLoginModule"; public static void main(String[] args) throws Exception { - login("cross-platform", + login(true, "cross-platform", UNIX_MODULE, "optional", NT_MODULE, "optional"); - login("windows", NT_MODULE, "required"); - login("unix", UNIX_MODULE, "required"); + login(OperatingSystem.isWindows(), "windows", NT_MODULE, "required"); + login(!OperatingSystem.isWindows(), "unix", UNIX_MODULE, "required"); } - static void login(String test, String... conf) throws Exception { + static void login(boolean shouldSucceed, String test, String... conf) throws Exception { System.out.println("Testing " + test + "..."); StringBuilder sb = new StringBuilder(); @@ -68,11 +71,19 @@ public class AllPlatforms { lc.login(); System.out.println(lc.getSubject()); lc.logout(); + if (!shouldSucceed) { + throw new RuntimeException("Should not succeed"); + } } catch (FailedLoginException e) { + if (shouldSucceed) { + throw new RuntimeException("Should succeed"); + } // This exception can occur in other platform module than the running one. - if(e.getMessage().startsWith("Failed in attempt to import")) { + if (e.getMessage().startsWith("Failed in attempt to import")) { System.out.println("Expected Exception found."); e.printStackTrace(System.out); + } else { + throw new RuntimeException("Unexpected error", e); } } }