Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/hotspot/os/bsd/os_bsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,99 @@ 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 /* = PATH_MAX */) {

// 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 = 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 = 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 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();
Expand Down
8 changes: 7 additions & 1 deletion src/hotspot/os/bsd/os_bsd.hpp
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 /* = PATH_MAX */);
#endif

static intptr_t* ucontext_get_sp(const ucontext_t* uc);
static intptr_t* ucontext_get_fp(const ucontext_t* uc);
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/os/posix/os_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/os/posix/os_posix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class os::Posix {
// 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);

Expand Down
18 changes: 17 additions & 1 deletion src/hotspot/os/posix/perfMemory_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#if defined(LINUX)
#include "os_linux.hpp"
#endif
#if defined(BSD)
#include "os_bsd.hpp"
#endif

// put OS-includes here
# include <sys/types.h>
Expand Down Expand Up @@ -144,6 +147,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;
Expand Down Expand Up @@ -1161,7 +1176,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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@
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;

Expand All @@ -39,14 +43,17 @@
*/
@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

Expand All @@ -67,7 +74,8 @@ 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.
File socket_file = new File(getTempDirFromPid(pid), ".java_pid" + pid);
socket_path = socket_file.getPath();
if (!socket_file.exists()) {
File f = createAttachFile(pid);
Expand Down Expand Up @@ -212,11 +220,34 @@ protected void close(long fd) throws IOException {
}

private File createAttachFile(int pid) throws IOException {
File f = new File(tmpdir, ".attach_pid" + pid);
// In macOS the attach file is created in the target user temp directory
File f = new File(getTempDirFromPid(pid), ".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;
Expand All @@ -239,6 +270,5 @@ private File createAttachFile(int pid) throws IOException {

static {
System.loadLibrary("attach");
tmpdir = getTempDir();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
4 changes: 3 additions & 1 deletion src/jdk.internal.jvmstat/share/classes/module-info.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,6 +40,8 @@
jdk.jstatd;
exports sun.jvmstat.perfdata.monitor to
jdk.jstatd;
exports sun.jvmstat to
jdk.attach;

uses sun.jvmstat.monitor.MonitoredHostService;

Expand Down