diff --git a/make/modules/jdk.security.auth/Lib.gmk b/make/modules/jdk.security.auth/Lib.gmk index 9ead32dbe121f..96d609f08d661 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/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 785b178e2967a..01462d9dca81a 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, 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 @@ -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.*; @@ -121,20 +120,33 @@ public void initialize(Subject subject, CallbackHandler callbackHandler, */ public boolean login() throws LoginException { - long[] unixGroups = null; - try { + // TODO: Shall we fail early for unsupported systems? + // 1. If it's Windows, we should fail immediately to avoid + // cygwin-like functions being loaded, which are not supported + // and could have a different `long`. + // 2. The FFM code has only been tested on macOS and Linux and + // might fail on other *nix systems. Especially, the `passwd` + // struct could be defined differently, although I've checked + // several and an extra 100 chars at the end seems enough. 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 +162,10 @@ public boolean login() throws LoginException { "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]); + 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 f3741c1040401..c5b25636d13ee 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, 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,38 +25,144 @@ package com.sun.security.auth.module; +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.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; + +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 AddressLayout C_POINTER + = ((AddressLayout) LINKER.canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + private static final ValueLayout.OfLong C_LONG + = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long"); + + private static final MethodHandle getgroups = LINKER + .downcallHandle(SYMBOL_LOOKUP.findOrThrow("getgroups"), + FunctionDescriptor.of(C_INT, C_INT, C_POINTER)); + private static final MethodHandle getuid = LINKER + .downcallHandle(SYMBOL_LOOKUP.findOrThrow("getuid"), + FunctionDescriptor.of(C_INT)); + private static final MethodHandle getgid = LINKER + .downcallHandle(SYMBOL_LOOKUP.findOrThrow("getgid"), + FunctionDescriptor.of(C_INT)); + private static final MethodHandle getpwuid_r = LINKER + .downcallHandle(SYMBOL_LOOKUP.findOrThrow("getpwuid_r"), + FunctionDescriptor.of(C_INT, C_INT, C_POINTER, C_POINTER, + C_LONG, C_POINTER)); + + private static final GroupLayout passwd_layout = 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.sequenceLayout(100, C_CHAR).withName("dummy")); + + private static final ValueLayout.OfInt pw_uid_layout + = (ValueLayout.OfInt) passwd_layout.select(groupElement("pw_uid")); + private static final long pw_uid_offset + = passwd_layout.byteOffset(groupElement("pw_uid")); + private static final ValueLayout.OfInt pw_gid_layout + = (ValueLayout.OfInt) passwd_layout.select(groupElement("pw_gid")); + private static final long pw_gid_offset + = passwd_layout.byteOffset(groupElement("pw_gid")); + private static final AddressLayout pw_name_layout + = (AddressLayout) passwd_layout.select(groupElement("pw_name")); + private static final long pw_name_offset + = passwd_layout.byteOffset(groupElement("pw_name")); + + // sysconf(_SC_GETPW_R_SIZE_MAX) on macOS is 4096 and 1024 on Linux + private static final long GETPW_R_SIZE_MAX = 4096L; + /** * Instantiate a {@code UnixSystem} and load * the native library to access the underlying system information. */ - @SuppressWarnings("restricted") public UnixSystem() { - System.loadLibrary("jaas"); - getUnixInfo(); + try (Arena scope = Arena.ofConfined()) { + int groupnum = (int) getgroups.invokeExact(0, MemorySegment.NULL); + if (groupnum == -1) { + throw new RuntimeException("getgroups returns " + groupnum); + } + + var gs = scope.allocate(C_INT, groupnum); + groupnum = (int) getgroups.invokeExact(groupnum, gs); + if (groupnum == -1) { + throw new RuntimeException("getgroups returns " + groupnum); + } + + groups = new long[groupnum]; + for (int i = 0; i < groupnum; i++) { + groups[i] = gs.getAtIndex(C_INT, i); + } + + var resbuf = scope.allocate(passwd_layout); + var pwd = scope.allocate(C_POINTER); + var pwd_buf = scope.allocate(GETPW_R_SIZE_MAX); + var tmpUid = (int)getuid.invokeExact(); + int out = (int) getpwuid_r.invokeExact( + tmpUid, resbuf, pwd_buf, GETPW_R_SIZE_MAX, pwd); + if (out != 0 || pwd.get(ValueLayout.ADDRESS, 0).equals(MemorySegment.NULL)) { + uid = tmpUid; + gid = (int)getgid.invokeExact(); + username = null; + } else { + uid = resbuf.get(pw_uid_layout, pw_uid_offset); + gid = resbuf.get(pw_gid_layout, pw_gid_offset); + username = resbuf.get(pw_name_layout, pw_name_offset).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 3a93df9ffc96b..0000000000000 --- 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 79eea7b4d23a4..6f7cbfb9c5245 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 @@ static void login(String test, String... conf) throws Exception { 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); } } }