mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8319589: Attach from root to a user java process not supported in Mac
Reviewed-by: sspitsyn
This commit is contained in:
parent
45ee89c4c8
commit
f52d49925f
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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__ */
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user