|
1 | 1 | /* |
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. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | 4 | * |
5 | 5 | * This code is free software; you can redistribute it and/or modify it |
|
25 | 25 |
|
26 | 26 | package com.sun.security.auth.module; |
27 | 27 |
|
| 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 | + |
28 | 46 | /** |
29 | 47 | * This class implementation retrieves and makes available Unix |
30 | 48 | * UID/GID/groups information for the current user. |
31 | 49 | * |
32 | 50 | * @since 1.4 |
33 | 51 | */ |
| 52 | +@SuppressWarnings("restricted") |
34 | 53 | public class UnixSystem { |
35 | 54 |
|
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 | + */ |
41 | 58 | protected String username; |
42 | 59 |
|
43 | | - /** The current user ID. */ |
| 60 | + /** |
| 61 | + * The current user ID. |
| 62 | + */ |
44 | 63 | protected long uid; |
45 | 64 |
|
46 | | - /** The current group ID. */ |
| 65 | + /** |
| 66 | + * The current group ID. |
| 67 | + */ |
47 | 68 | protected long gid; |
48 | 69 |
|
49 | | - /** The current list of groups. */ |
| 70 | + /** |
| 71 | + * The current list of groups. |
| 72 | + */ |
50 | 73 | protected long[] groups; |
51 | 74 |
|
| 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 | + |
52 | 151 | /** |
53 | 152 | * Instantiate a {@code UnixSystem} and load |
54 | 153 | * the native library to access the underlying system information. |
55 | 154 | */ |
56 | | - @SuppressWarnings("restricted") |
57 | 155 | 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 | + } |
60 | 216 | } |
61 | 217 |
|
62 | 218 | /** |
|
0 commit comments