8373867: Improve robustness of Attach API for finding tmp directory

Reviewed-by: sspitsyn, amenkov
This commit is contained in:
Yasumasa Suenaga 2026-01-17 06:24:31 +00:00
parent 0dd5b59194
commit 436c62afd2
3 changed files with 154 additions and 16 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2026, 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
@ -41,6 +41,8 @@ import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import sun.jvmstat.monitor.MonitoredHost;
/*
* Linux implementation of HotSpotVirtualMachine
*/
@ -228,7 +230,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
// Return the socket file for the given process.
private File findSocketFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
return new File(findTargetProcessTmpDirectory(pid, ns_pid), ".java_pid" + ns_pid);
return new File(findTargetProcessTmpDirectory(pid), ".java_pid" + ns_pid);
}
// On Linux a simple handshake is used to start the attach mechanism
@ -243,14 +245,14 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
// Do not canonicalize the file path, or we will fail to attach to a VM in a container.
f.createNewFile();
} catch (IOException _) {
f = new File(findTargetProcessTmpDirectory(pid, ns_pid), fn.toString());
f = new File(findTargetProcessTmpDirectory(pid), fn.toString());
f.createNewFile();
}
return f;
}
private String findTargetProcessTmpDirectory(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
final var procPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP);
private String findTargetProcessTmpDirectory(long pid) throws AttachNotSupportedException {
final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP);
/* We need to handle at least 4 different cases:
* 1. Caller and target processes share PID namespace and root filesystem (host to host or container to
@ -261,21 +263,44 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
* 4. Caller and target processes share neither PID namespace nor root filesystem (host to container)
*
* if target is elevated, we cant use /proc/<pid>/... so we have to fallback to /tmp, but that may not be shared
* with the target/attachee process, we can try, except in the case where the ns_pid also exists in this pid ns
* which is ambiguous, if we share /tmp with the intended target, the attach will succeed, if we do not,
* then we will potentially attempt to attach to some arbitrary process with the same pid (in this pid ns)
* as that of the intended target (in its * pid ns).
* with the target/attachee process, so we should check whether /tmp on both is same. This method would throw
* AttachNotSupportedException if they are different because we cannot make a connection with target VM.
*
* so in that case we should prehaps throw - or risk sending SIGQUIT to some arbitrary process... which could kill it
*
* however we can also check the target pid's signal masks to see if it catches SIGQUIT and only do so if in
* In addition, we can also check the target pid's signal masks to see if it catches SIGQUIT and only do so if in
* fact it does ... this reduces the risk of killing an innocent process in the current ns as opposed to
* attaching to the actual target JVM ... c.f: checkCatchesAndSendQuitTo() below.
*
* note that if pid == ns_pid we are in a shared pid ns with the target and may (potentially) share /tmp
*/
return (Files.isWritable(procPidRoot) ? procPidRoot : TMPDIR).toString();
try {
if (Files.isWritable(tmpOnProcPidRoot)) {
return tmpOnProcPidRoot.toString();
} else if (Files.isSameFile(tmpOnProcPidRoot, TMPDIR)) {
return TMPDIR.toString();
} else {
throw new AttachNotSupportedException("Unable to access the filesystem of the target process");
}
} catch (IOException ioe) {
try {
boolean found = MonitoredHost.getMonitoredHost("//localhost")
.activeVms()
.stream()
.anyMatch(i -> pid == i.intValue());
if (found) {
// We can use /tmp because target process is on same host
// even if we cannot access /proc/<PID>/root.
// The process with capsh/setcap would fall this pattern.
return TMPDIR.toString();
} else {
throw new AttachNotSupportedException("Unable to access the filesystem of the target process", ioe);
}
} catch (AttachNotSupportedException e) {
// AttachNotSupportedException happened in above should go through
throw e;
} catch (Exception e) {
// Other exceptions would be wrapped with AttachNotSupportedException
throw new AttachNotSupportedException("Unable to access the filesystem of the target process", e);
}
}
}
// Return the inner most namespaced PID if there is one,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2026, 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
@ -62,4 +62,15 @@ public class AttachNotSupportedException extends Exception {
super(s);
}
/**
* Constructs an <code>AttachNotSupportedException</code> with
* the specified cause.
*
* @param message the detail message.
* @param cause the cause of this exception.
*/
public AttachNotSupportedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, NTT DATA
* 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.
*
* 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.
*/
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.ValueLayout;
import com.sun.tools.attach.VirtualMachine;
import jdk.test.lib.Asserts;
import jdk.test.lib.thread.ProcessThread;
import jdk.test.lib.process.ProcessTools;
/*
* @test
* @bug 8226919 8373867
* @summary Test to make sure attach target process which is not dumpable.
* @library /test/lib
* @modules jdk.attach
* @requires os.family == "linux"
*
* @run main/timeout=200 TestWithoutDumpableProcess
*/
public class TestWithoutDumpableProcess {
private static final String EXPECTED_PROP_KEY = "attach.test";
private static final String EXPECTED_PROP_VALUE = "true";
public static class Debuggee {
// Disable dumpable attribute via prctl(2)
private static void disableDumpable() throws Throwable {
var linker = Linker.nativeLinker();
var prctl = linker.downcallHandle(linker.defaultLookup().findOrThrow("prctl"),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG),
Linker.Option.firstVariadicArg(1), Linker.Option.captureCallState("errno"));
var errnoSeg = Arena.global().allocate(Linker.Option.captureStateLayout());
final int PR_SET_DUMPABLE = 4; // from linux/prctl.h
int ret = (int)prctl.invoke(errnoSeg, PR_SET_DUMPABLE, 0L);
if (ret == -1){
var hndErrno = Linker.Option
.captureStateLayout()
.varHandle(MemoryLayout.PathElement.groupElement("errno"));
int errno = (int)hndErrno.get(errnoSeg, 0L);
throw new RuntimeException("prctl: errno=" + errno);
}
}
public static void main(String[] args) throws Throwable {
disableDumpable();
IO.println(Application.READY_MSG);
while (IO.readln().equals(Application.SHUTDOWN_MSG));
}
public static ProcessThread start() {
var args = new String[]{
"--enable-native-access=ALL-UNNAMED",
String.format("-D%s=%s", EXPECTED_PROP_KEY, EXPECTED_PROP_VALUE), Debuggee.class.getName()
};
var pb = ProcessTools.createLimitedTestJavaProcessBuilder(args);
var pt = new ProcessThread("runApplication", Application.READY_MSG::equals, pb);
pt.start();
return pt;
}
}
public static void main(String[] args) throws Exception {
var pt = Debuggee.start();
var vm = VirtualMachine.attach(Long.toString(pt.getPid()));
var val = vm.getSystemProperties().getProperty(EXPECTED_PROP_KEY);
Asserts.assertNotNull(val, "Expected sysprop not found");
Asserts.assertEquals(val, "true", "Unexpected sysprop value");
}
}