Skip to content

Commit eda15aa

Browse files
8277489: Rewrite JAAS UnixLoginModule with FFM
Co-authored-by: Martin Doerr <mdoerr@openjdk.org> Reviewed-by: mdoerr, ascarpino, erikj
1 parent 0d1d4d0 commit eda15aa

File tree

6 files changed

+220
-165
lines changed

6 files changed

+220
-165
lines changed

make/modules/jdk.security.auth/Lib.gmk

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ include LibCommon.gmk
3131
## Build libjaas
3232
################################################################################
3333

34-
$(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \
35-
NAME := jaas, \
36-
OPTIMIZATION := LOW, \
37-
EXTRA_HEADER_DIRS := java.base:libjava, \
38-
LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \
39-
))
40-
41-
TARGETS += $(BUILD_LIBJAAS)
34+
ifeq ($(call isTargetOs, windows), true)
35+
$(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \
36+
NAME := jaas, \
37+
OPTIMIZATION := LOW, \
38+
EXTRA_HEADER_DIRS := java.base:libjava, \
39+
LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \
40+
))
4241

42+
TARGETS += $(BUILD_LIBJAAS)
43+
endif
4344
################################################################################

src/java.base/share/classes/module-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@
274274
jdk.httpserver,
275275
jdk.jlink,
276276
jdk.jpackage,
277-
jdk.net;
277+
jdk.net,
278+
jdk.security.auth;
278279
exports sun.net to
279280
java.net.http,
280281
jdk.naming.dns;

src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,14 +26,14 @@
2626
package com.sun.security.auth.module;
2727

2828
import java.util.*;
29-
import java.io.IOException;
3029
import javax.security.auth.*;
3130
import javax.security.auth.callback.*;
3231
import javax.security.auth.login.*;
3332
import javax.security.auth.spi.*;
3433
import com.sun.security.auth.UnixPrincipal;
3534
import com.sun.security.auth.UnixNumericUserPrincipal;
3635
import com.sun.security.auth.UnixNumericGroupPrincipal;
36+
import jdk.internal.util.OperatingSystem;
3737

3838
/**
3939
* This {@code LoginModule} imports a user's Unix
@@ -121,20 +121,34 @@ public void initialize(Subject subject, CallbackHandler callbackHandler,
121121
*/
122122
public boolean login() throws LoginException {
123123

124-
long[] unixGroups = null;
124+
// Fail immediately on Windows to avoid cygwin-like functions
125+
// being loaded, which are not supported.
126+
if (OperatingSystem.isWindows()) {
127+
throw new FailedLoginException
128+
("Failed in attempt to import " +
129+
"the underlying system identity information" +
130+
" on " + System.getProperty("os.name"));
131+
}
125132

126133
try {
127134
ss = new UnixSystem();
128-
} catch (UnsatisfiedLinkError ule) {
135+
} catch (ExceptionInInitializerError | UnsatisfiedLinkError ule) {
136+
// Errors could happen in either static blocks or the constructor,
137+
// both have a cause.
129138
succeeded = false;
130-
throw new FailedLoginException
139+
var error = new FailedLoginException
131140
("Failed in attempt to import " +
132141
"the underlying system identity information" +
133142
" on " + System.getProperty("os.name"));
143+
if (ule.getCause() != null) {
144+
error.initCause(ule.getCause());
145+
}
146+
throw error;
134147
}
135148
userPrincipal = new UnixPrincipal(ss.getUsername());
136149
UIDPrincipal = new UnixNumericUserPrincipal(ss.getUid());
137150
GIDPrincipal = new UnixNumericGroupPrincipal(ss.getGid(), true);
151+
long[] unixGroups = null;
138152
if (ss.getGroups() != null && ss.getGroups().length > 0) {
139153
unixGroups = ss.getGroups();
140154
for (int i = 0; i < unixGroups.length; i++) {
@@ -150,9 +164,11 @@ public boolean login() throws LoginException {
150164
"succeeded importing info: ");
151165
System.out.println("\t\t\tuid = " + ss.getUid());
152166
System.out.println("\t\t\tgid = " + ss.getGid());
153-
unixGroups = ss.getGroups();
154-
for (int i = 0; i < unixGroups.length; i++) {
155-
System.out.println("\t\t\tsupp gid = " + unixGroups[i]);
167+
System.out.println("\t\t\tusername = " + ss.getUsername());
168+
if (unixGroups != null) {
169+
for (int i = 0; i < unixGroups.length; i++) {
170+
System.out.println("\t\t\tsupp gid = " + unixGroups[i]);
171+
}
156172
}
157173
}
158174
succeeded = true;

src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,38 +25,194 @@
2525

2626
package com.sun.security.auth.module;
2727

28+
import jdk.internal.util.Architecture;
29+
import jdk.internal.util.OperatingSystem;
30+
31+
import java.lang.foreign.AddressLayout;
32+
import java.lang.foreign.Arena;
33+
import java.lang.foreign.FunctionDescriptor;
34+
import java.lang.foreign.GroupLayout;
35+
import java.lang.foreign.Linker;
36+
import java.lang.foreign.MemoryLayout;
37+
import java.lang.foreign.MemorySegment;
38+
import java.lang.foreign.StructLayout;
39+
import java.lang.foreign.SymbolLookup;
40+
import java.lang.foreign.ValueLayout;
41+
import java.lang.invoke.MethodHandle;
42+
import java.lang.invoke.VarHandle;
43+
44+
import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
45+
2846
/**
2947
* This class implementation retrieves and makes available Unix
3048
* UID/GID/groups information for the current user.
3149
*
3250
* @since 1.4
3351
*/
52+
@SuppressWarnings("restricted")
3453
public class UnixSystem {
3554

36-
private native void getUnixInfo();
37-
38-
// Warning: the following 4 fields are used by Unix.c
39-
40-
/** The current username. */
55+
/**
56+
* The current username.
57+
*/
4158
protected String username;
4259

43-
/** The current user ID. */
60+
/**
61+
* The current user ID.
62+
*/
4463
protected long uid;
4564

46-
/** The current group ID. */
65+
/**
66+
* The current group ID.
67+
*/
4768
protected long gid;
4869

49-
/** The current list of groups. */
70+
/**
71+
* The current list of groups.
72+
*/
5073
protected long[] groups;
5174

75+
private static final Linker LINKER = Linker.nativeLinker();
76+
private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
77+
.or(LINKER.defaultLookup());
78+
79+
private static final ValueLayout.OfByte C_CHAR
80+
= (ValueLayout.OfByte) LINKER.canonicalLayouts().get("char");
81+
private static final ValueLayout.OfInt C_INT
82+
= (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int");
83+
private static final ValueLayout.OfLong C_LONG
84+
= (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long");
85+
private static final AddressLayout C_POINTER
86+
= ((AddressLayout) LINKER.canonicalLayouts().get("void*"))
87+
.withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
88+
private static final ValueLayout C_SIZE_T
89+
= (ValueLayout) LINKER.canonicalLayouts().get("size_t");
90+
91+
private static final StructLayout CAPTURE_STATE_LAYOUT
92+
= Linker.Option.captureStateLayout();
93+
private static final VarHandle VH_errno = CAPTURE_STATE_LAYOUT.varHandle(
94+
MemoryLayout.PathElement.groupElement("errno"));
95+
96+
private static final MethodHandle MH_strerror
97+
= LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("strerror"),
98+
FunctionDescriptor.of(C_POINTER, C_INT));
99+
100+
private static final MethodHandle MH_getgroups
101+
= LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getgroups"),
102+
FunctionDescriptor.of(C_INT, C_INT, C_POINTER),
103+
Linker.Option.captureCallState("errno"));
104+
private static final MethodHandle MH_getuid
105+
= LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getuid"),
106+
FunctionDescriptor.of(C_INT));
107+
108+
// Some architectures require appropriate zero or sign extension to 64 bit.
109+
// Use long directly before https://bugs.openjdk.org/browse/JDK-8336664 is resolved.
110+
private static final boolean calling_convention_requires_int_as_long
111+
= Architecture.isPPC64() || Architecture.isPPC64LE() || Architecture.isS390();
112+
113+
// getpwuid_r does not work on AIX, instead we use another similar function
114+
// extern int _posix_getpwuid_r(uid_t, struct passwd *, char *, int, struct passwd **)
115+
private static final MethodHandle MH_getpwuid_r
116+
= LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow(
117+
OperatingSystem.isAix() ? "_posix_getpwuid_r" : "getpwuid_r"),
118+
FunctionDescriptor.of(C_INT,
119+
calling_convention_requires_int_as_long ? C_LONG : C_INT,
120+
C_POINTER, C_POINTER,
121+
OperatingSystem.isAix() ? C_INT : C_SIZE_T,
122+
C_POINTER));
123+
124+
private static final GroupLayout ML_passwd = MemoryLayout.structLayout(
125+
C_POINTER.withName("pw_name"),
126+
C_POINTER.withName("pw_passwd"),
127+
C_INT.withName("pw_uid"),
128+
C_INT.withName("pw_gid"),
129+
// Different platforms have different fields in `struct passwd`.
130+
// While we don't need those fields here, the struct needs to be
131+
// big enough to avoid buffer overflow when `getpwuid_r` is called.
132+
MemoryLayout.paddingLayout(100));
133+
134+
private static final VarHandle VH_pw_uid
135+
= ML_passwd.varHandle(groupElement("pw_uid"));
136+
private static final VarHandle VH_pw_gid
137+
= ML_passwd.varHandle(groupElement("pw_gid"));
138+
private static final VarHandle VH_pw_name
139+
= ML_passwd.varHandle(groupElement("pw_name"));
140+
141+
// The buffer size for the getpwuid_r function:
142+
// 1. sysconf(_SC_GETPW_R_SIZE_MAX) on macOS is 4096 and 1024 on Linux,
143+
// so we choose a bigger one.
144+
// 2. We do not call sysconf() here because even _SC_GETPW_R_SIZE_MAX
145+
// could be different on different platforms.
146+
// 3. We choose int instead of long because the buffer_size argument
147+
// might be `int` or `long` and converting from `long` to `int`
148+
// requires an explicit cast.
149+
private static final int GETPW_R_SIZE_MAX = 4096;
150+
52151
/**
53152
* Instantiate a {@code UnixSystem} and load
54153
* the native library to access the underlying system information.
55154
*/
56-
@SuppressWarnings("restricted")
57155
public UnixSystem() {
58-
System.loadLibrary("jaas");
59-
getUnixInfo();
156+
// The FFM code has only been tested on multiple platforms
157+
// (including macOS, Linux, AIX, etc) and might fail on other
158+
// *nix systems. Especially, the `passwd` struct could be defined
159+
// differently. I've checked several and an extra 100 chars at the
160+
// end seems enough.
161+
try (Arena scope = Arena.ofConfined()) {
162+
MemorySegment capturedState = scope.allocate(CAPTURE_STATE_LAYOUT);
163+
int groupnum = (int) MH_getgroups.invokeExact(capturedState, 0, MemorySegment.NULL);
164+
if (groupnum == -1) {
165+
throw new RuntimeException("getgroups returns " + groupnum);
166+
}
167+
168+
var gs = scope.allocate(C_INT, groupnum);
169+
groupnum = (int) MH_getgroups.invokeExact(capturedState, groupnum, gs);
170+
if (groupnum == -1) {
171+
var errno = (int) VH_errno.get(capturedState, 0L);
172+
var errMsg = (MemorySegment) MH_strerror.invokeExact(errno);
173+
throw new RuntimeException("getgroups returns " + groupnum
174+
+ ". Reason: " + errMsg.reinterpret(Long.MAX_VALUE).getString(0));
175+
}
176+
177+
groups = new long[groupnum];
178+
for (int i = 0; i < groupnum; i++) {
179+
groups[i] = Integer.toUnsignedLong(gs.getAtIndex(C_INT, i));
180+
}
181+
182+
var pwd = scope.allocate(ML_passwd);
183+
var result = scope.allocate(C_POINTER);
184+
var buffer = scope.allocate(GETPW_R_SIZE_MAX);
185+
186+
long tmpUid = Integer.toUnsignedLong((int) MH_getuid.invokeExact());
187+
188+
// Do not call invokeExact because the type of buffer_size is not
189+
// always long in the underlying system.
190+
int out;
191+
if (calling_convention_requires_int_as_long) {
192+
out = (int) MH_getpwuid_r.invoke(
193+
tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result);
194+
} else {
195+
out = (int) MH_getpwuid_r.invoke(
196+
(int) tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result);
197+
}
198+
if (out != 0) {
199+
// If ERANGE (Result too large) is detected in a new platform,
200+
// consider adjusting GETPW_R_SIZE_MAX.
201+
var err = (MemorySegment) MH_strerror.invokeExact(out);
202+
throw new RuntimeException(err.reinterpret(Long.MAX_VALUE).getString(0));
203+
} else if (result.get(ValueLayout.ADDRESS, 0).equals(MemorySegment.NULL)) {
204+
throw new RuntimeException("the requested entry is not found");
205+
} else {
206+
// uid_t and gid_t were defined unsigned.
207+
uid = Integer.toUnsignedLong((int) VH_pw_uid.get(pwd, 0L));
208+
gid = Integer.toUnsignedLong((int) VH_pw_gid.get(pwd, 0L));
209+
username = ((MemorySegment) VH_pw_name.get(pwd, 0L)).getString(0);
210+
}
211+
} catch (Throwable t) {
212+
var error = new UnsatisfiedLinkError("FFM calls failed");
213+
error.initCause(t);
214+
throw error;
215+
}
60216
}
61217

62218
/**

0 commit comments

Comments
 (0)