From f52d49925f9c60814a0a34720d7443e748b35c25 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Mon, 15 Dec 2025 20:19:05 +0000 Subject: [PATCH] 8319589: Attach from root to a user java process not supported in Mac Reviewed-by: sspitsyn --- src/hotspot/os/bsd/os_bsd.cpp | 84 +++++++++++++++++ src/hotspot/os/bsd/os_bsd.hpp | 8 +- src/hotspot/os/posix/os_posix.cpp | 4 + src/hotspot/os/posix/os_posix.hpp | 3 + src/hotspot/os/posix/perfMemory_posix.cpp | 18 +++- .../sun/tools/attach/VirtualMachineImpl.java | 56 +++++++++--- .../native/libattach/VirtualMachineImpl.c | 26 +----- .../sun/jvmstat/PlatformSupportImpl.java | 91 +++++++++++++++++++ .../share/classes/module-info.java | 4 +- 9 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index dc8f5187b5a..61de48bb7fa 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -861,6 +861,90 @@ pid_t os::Bsd::gettid() { } } +// Returns the uid of a process or -1 on error. +uid_t os::Bsd::get_process_uid(pid_t pid) { + struct kinfo_proc kp; + size_t size = sizeof kp; + int mib_kern[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; + if (sysctl(mib_kern, 4, &kp, &size, nullptr, 0) == 0) { + if (size > 0 && kp.kp_proc.p_pid == pid) { + return kp.kp_eproc.e_ucred.cr_uid; + } + } + return (uid_t)-1; +} + +// Returns true if the process is running as root. +bool os::Bsd::is_process_root(pid_t pid) { + uid_t uid = get_process_uid(pid); + return (uid != (uid_t)-1) ? os::Posix::is_root(uid) : false; +} + +#ifdef __APPLE__ + +// macOS has a secure per-user temporary directory. +// Root can attach to a non-root process, hence it needs +// to lookup /var/folders for the user specific temporary directory +// of the form /var/folders/*/*/T, that contains PERFDATA_NAME_user +// directory. +static const char VAR_FOLDERS[] = "/var/folders/"; +int os::Bsd::get_user_tmp_dir_macos(const char* user, int vmid, char* output_path, int output_size) { + + // read the var/folders directory + DIR* varfolders_dir = os::opendir(VAR_FOLDERS); + if (varfolders_dir != nullptr) { + + // var/folders directory contains 2-characters subdirectories (buckets) + struct dirent* bucket_de; + + // loop until the PERFDATA_NAME_user directory has been found + while ((bucket_de = os::readdir(varfolders_dir)) != nullptr) { + // skip over files and special "." and ".." + if (bucket_de->d_type != DT_DIR || bucket_de->d_name[0] == '.') { + continue; + } + // absolute path to the bucket + char bucket[PATH_MAX]; + int b = os::snprintf(bucket, PATH_MAX, "%s%s/", VAR_FOLDERS, bucket_de->d_name); + + // the total length of the absolute path must not exceed the buffer size + if (b >= PATH_MAX || b < 0) { + continue; + } + // each bucket contains next level subdirectories + DIR* bucket_dir = os::opendir(bucket); + if (bucket_dir == nullptr) { + continue; + } + // read each subdirectory, skipping over regular files + struct dirent* subbucket_de; + while ((subbucket_de = os::readdir(bucket_dir)) != nullptr) { + if (subbucket_de->d_type != DT_DIR || subbucket_de->d_name[0] == '.') { + continue; + } + // If the PERFDATA_NAME_user directory exists in the T subdirectory, + // this means the subdirectory is the temporary directory of the user. + char perfdata_path[PATH_MAX]; + int p = os::snprintf(perfdata_path, PATH_MAX, "%s%s/T/%s_%s/", bucket, subbucket_de->d_name, PERFDATA_NAME, user); + + // the total length must not exceed the output buffer size + if (p >= PATH_MAX || p < 0) { + continue; + } + // check if the subdirectory exists + if (os::file_exists(perfdata_path)) { + // the return value of snprintf is not checked for the second time + return os::snprintf(output_path, output_size, "%s%s/T", bucket, subbucket_de->d_name); + } + } + os::closedir(bucket_dir); + } + os::closedir(varfolders_dir); + } + return -1; +} +#endif + intx os::current_thread_id() { #ifdef __APPLE__ return (intx)os::Bsd::gettid(); diff --git a/src/hotspot/os/bsd/os_bsd.hpp b/src/hotspot/os/bsd/os_bsd.hpp index 82002917f39..da73211b9a7 100644 --- a/src/hotspot/os/bsd/os_bsd.hpp +++ b/src/hotspot/os/bsd/os_bsd.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -61,6 +61,12 @@ class os::Bsd { static pthread_t main_thread(void) { return _main_thread; } static pid_t gettid(); + static uid_t get_process_uid(pid_t pid); + static bool is_process_root(pid_t pid); + +#ifdef __APPLE__ + static int get_user_tmp_dir_macos(const char* user, int vmid, char* output_buffer, int buffer_size); +#endif static intptr_t* ucontext_get_sp(const ucontext_t* uc); static intptr_t* ucontext_get_fp(const ucontext_t* uc); diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index ef52b946cc6..07e1920c62d 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -1352,6 +1352,10 @@ bool os::Posix::is_root(uid_t uid){ return ROOT_UID == uid; } +bool os::Posix::is_current_user_root(){ + return is_root(geteuid()); +} + bool os::Posix::matches_effective_uid_or_root(uid_t uid) { return is_root(uid) || geteuid() == uid; } diff --git a/src/hotspot/os/posix/os_posix.hpp b/src/hotspot/os/posix/os_posix.hpp index 4b8b75ea07e..424d737cf42 100644 --- a/src/hotspot/os/posix/os_posix.hpp +++ b/src/hotspot/os/posix/os_posix.hpp @@ -76,6 +76,9 @@ public: // Returns true if given uid is root. static bool is_root(uid_t uid); + // Returns true if the current user is root. + static bool is_current_user_root(); + // Returns true if given uid is effective or root uid. static bool matches_effective_uid_or_root(uid_t uid); diff --git a/src/hotspot/os/posix/perfMemory_posix.cpp b/src/hotspot/os/posix/perfMemory_posix.cpp index 2cc0263d291..39bfc72a486 100644 --- a/src/hotspot/os/posix/perfMemory_posix.cpp +++ b/src/hotspot/os/posix/perfMemory_posix.cpp @@ -40,6 +40,9 @@ #if defined(LINUX) #include "os_linux.hpp" #endif +#if defined(BSD) +#include "os_bsd.hpp" +#endif # include # include @@ -142,6 +145,18 @@ static char* get_user_tmp_dir(const char* user, int vmid, int nspid) { jio_snprintf(buffer, TMP_BUFFER_LEN, "/proc/%d/root%s", vmid, tmpdir); tmpdir = buffer; } +#endif +#ifdef __APPLE__ + char buffer[PATH_MAX] = {0}; + // Check if the current user is root and the target VM is running as non-root. + // Otherwise the output of os::get_temp_directory() is used. + // + if (os::Posix::is_current_user_root() && !os::Bsd::is_process_root(vmid)) { + int path_size = os::Bsd::get_user_tmp_dir_macos(user, vmid, buffer, sizeof buffer); + if (path_size > 0 && (size_t)path_size < sizeof buffer) { + tmpdir = buffer; + } + } #endif const char* perfdir = PERFDATA_NAME; size_t nbytes = strlen(tmpdir) + strlen(perfdir) + strlen(user) + 3; @@ -1138,7 +1153,8 @@ static void mmap_attach_shared(int vmid, char** addr, size_t* sizep, TRAPS) { // for linux, determine if vmid is for a containerized process int nspid = LINUX_ONLY(os::Linux::get_namespace_pid(vmid)) NOT_LINUX(-1); - const char* luser = get_user_name(vmid, &nspid, CHECK); + const char* luser = NOT_MACOS(get_user_name(vmid, &nspid, CHECK)) + MACOS_ONLY(get_user_name(os::Bsd::get_process_uid(vmid))); if (luser == nullptr) { THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), diff --git a/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java index 5c786db1366..a7a348affe3 100644 --- a/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java @@ -28,9 +28,13 @@ import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.spi.AttachProvider; +import sun.jvmstat.PlatformSupport; + import java.io.InputStream; import java.io.IOException; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import static java.nio.charset.StandardCharsets.UTF_8; @@ -39,14 +43,17 @@ import static java.nio.charset.StandardCharsets.UTF_8; */ @SuppressWarnings("restricted") public class VirtualMachineImpl extends HotSpotVirtualMachine { - // "tmpdir" is used as a global well-known location for the files - // .java_pid. and .attach_pid. It is important that this - // location is the same for all processes, otherwise the tools - // will not be able to find all Hotspot processes. - // This is intentionally not the same as java.io.tmpdir, since - // the latter can be changed by the user. - // Any changes to this needs to be synchronized with HotSpot. - private static final String tmpdir; + + /** + * HotSpot PerfData file prefix + */ + private static final String HSPERFDATA_PREFIX = "hsperfdata_"; + + /** + * Use platform specific methods for looking up temporary directories. + */ + private static final PlatformSupport platformSupport = PlatformSupport.getInstance(); + String socket_path; private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor @@ -67,10 +74,12 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // Find the socket file. If not found then we attempt to start the // attach mechanism in the target VM by sending it a QUIT signal. // Then we attempt to find the socket file again. - File socket_file = new File(tmpdir, ".java_pid" + pid); + // In macOS the socket file is located in per-user temp directory. + String tempdir = getTempDirFromPid(pid); + File socket_file = new File(tempdir, ".java_pid" + pid); socket_path = socket_file.getPath(); if (!socket_file.exists()) { - File f = createAttachFile(pid); + File f = createAttachFile(tempdir, pid); try { checkCatchesAndSendQuitTo(pid, false); @@ -211,12 +220,34 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { } } - private File createAttachFile(int pid) throws IOException { + private File createAttachFile(String tmpdir, int pid) throws IOException { File f = new File(tmpdir, ".attach_pid" + pid); createAttachFile0(f.getPath()); return f; } + /* + * Returns a platform-specific temporary directory for a given process. + * In VMs running as unprivileged user it returns the default platform-specific + * temporary directory. In VMs running as root it searches over the list of + * temporary directories for one containing HotSpot PerfData directory. + */ + private String getTempDirFromPid(int pid) { + ProcessHandle ph = ProcessHandle.of(pid).orElse(null); + if (ph != null) { + String user = ph.info().user().orElse(null); + if (user != null) { + for (String dir : platformSupport.getTemporaryDirectories(pid)) { + Path fullPath = Path.of(dir, HSPERFDATA_PREFIX + user, String.valueOf(pid)); + if (Files.exists(fullPath)) { + return dir; + } + } + } + } + return PlatformSupport.getTemporaryDirectory(); + } + //-- native methods static native boolean checkCatchesAndSendQuitTo(int pid, boolean throwIfNotReady) throws IOException, AttachNotSupportedException; @@ -235,10 +266,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { static native void createAttachFile0(String path); - static native String getTempDir(); - static { System.loadLibrary("attach"); - tmpdir = getTempDir(); } } diff --git a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c index d105d7d3b1b..5b2579eba39 100644 --- a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -335,27 +335,3 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_createAttachFile JNU_ReleaseStringPlatformChars(env, path, _path); } } - -/* - * Class: sun_tools_attach_BSDVirtualMachine - * Method: getTempDir - * Signature: (V)Ljava.lang.String; - */ -JNIEXPORT jstring JNICALL Java_sun_tools_attach_VirtualMachineImpl_getTempDir(JNIEnv *env, jclass cls) -{ - // This must be hard coded because it's the system's temporary - // directory not the java application's temp directory, ala java.io.tmpdir. - -#ifdef __APPLE__ - // macosx has a secure per-user temporary directory. - // Don't cache the result as this is only called once. - char path[PATH_MAX]; - int pathSize = confstr(_CS_DARWIN_USER_TEMP_DIR, path, PATH_MAX); - if (pathSize == 0 || pathSize > PATH_MAX) { - strlcpy(path, "/tmp", sizeof(path)); - } - return JNU_NewStringPlatform(env, path); -#else /* __APPLE__ */ - return (*env)->NewStringUTF(env, "/tmp"); -#endif /* __APPLE__ */ -} diff --git a/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java b/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java new file mode 100644 index 00000000000..f858fde4dce --- /dev/null +++ b/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, BELLSOFT. 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. + */ + +package sun.jvmstat; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/* + * macOS specific implementation of the PlatformSupport routines + * providing temporary directory support. + */ +public class PlatformSupportImpl extends PlatformSupport { + + private static final String VAR_FOLDERS_PATH = "/var/folders"; + private static final String USER_NAME_SYSTEM_PROPERTY = "user.name"; + private static final String USER_NAME_ROOT = "root"; + private static final String DIRHELPER_TEMP_STR = "T"; + + private static final boolean isCurrentUserRoot = + System.getProperty(USER_NAME_SYSTEM_PROPERTY).equals(USER_NAME_ROOT); + + public PlatformSupportImpl() { + super(); + } + + /* + * Return a list of the temporary directories that the VM uses + * for the attach and perf data files. + * + * This function returns the traditional temp directory. Additionally, + * when called by root, it returns other temporary directories of non-root + * users. + * + * macOS per-user temp directories are located under /var/folders + * and have the form /var/folders///T + */ + @Override + public List getTemporaryDirectories(int pid) { + if (!isCurrentUserRoot) { + // early exit for non-root + return List.of(PlatformSupport.getTemporaryDirectory()); + } + List result = new ArrayList<>(); + try (DirectoryStream bs = Files.newDirectoryStream(Path.of(VAR_FOLDERS_PATH))) { + for (Path bucket : bs) { + try (DirectoryStream encUuids = Files.newDirectoryStream(bucket)) { + for (Path encUuid : encUuids) { + try { + Path tempDir = encUuid.resolve(DIRHELPER_TEMP_STR); + if (Files.isDirectory(tempDir) && Files.isReadable(tempDir)) { + result.add(tempDir.toString()); + } + } catch (Exception ignore) { // ignored unreadable bucket/encUuid, continue + } + } + } catch (IOException ignore) { // IOException ignored, continue to the next bucket + } + } + } catch (Exception ignore) { // var/folders directory is inaccessible / other errors + } + return result.isEmpty() ? List.of(PlatformSupport.getTemporaryDirectory()) : result; + } +} diff --git a/src/jdk.internal.jvmstat/share/classes/module-info.java b/src/jdk.internal.jvmstat/share/classes/module-info.java index 9e12c98b016..d9e1bc68008 100644 --- a/src/jdk.internal.jvmstat/share/classes/module-info.java +++ b/src/jdk.internal.jvmstat/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, 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 @@ -40,6 +40,8 @@ module jdk.internal.jvmstat { jdk.jstatd; exports sun.jvmstat.perfdata.monitor to jdk.jstatd; + exports sun.jvmstat to + jdk.attach; uses sun.jvmstat.monitor.MonitoredHostService;