8319589: Attach from root to a user java process not supported in Mac

Reviewed-by: sspitsyn
This commit is contained in:
Sergey Chernyshev 2025-12-15 20:19:05 +00:00 committed by Serguei Spitsyn
parent 45ee89c4c8
commit f52d49925f
9 changed files with 252 additions and 42 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -40,6 +40,9 @@
#if defined(LINUX)
#include "os_linux.hpp"
#endif
#if defined(BSD)
#include "os_bsd.hpp"
#endif
# include <errno.h>
# include <pwd.h>
@ -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(),

View File

@ -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<pid>. and .attach_pid<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();
}
}

View File

@ -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__ */
}

View File

@ -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/<BUCKET>/<ENCODED_UUID_UID>/T
*/
@Override
public List<String> getTemporaryDirectories(int pid) {
if (!isCurrentUserRoot) {
// early exit for non-root
return List.of(PlatformSupport.getTemporaryDirectory());
}
List<String> result = new ArrayList<>();
try (DirectoryStream<Path> bs = Files.newDirectoryStream(Path.of(VAR_FOLDERS_PATH))) {
for (Path bucket : bs) {
try (DirectoryStream<Path> 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;
}
}

View File

@ -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;