Skip to content

Commit 3864bdd

Browse files
committed
backport 52ba72823be0c969ab873ead2863ec48f883210b
1 parent 9e29f47 commit 3864bdd

File tree

2 files changed

+210
-75
lines changed

2 files changed

+210
-75
lines changed

src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java

Lines changed: 112 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
import com.sun.tools.attach.AttachNotSupportedException;
2929
import com.sun.tools.attach.spi.AttachProvider;
3030

31-
import java.io.InputStream;
32-
import java.io.IOException;
3331
import java.io.File;
32+
import java.io.IOException;
33+
import java.io.InputStream;
34+
import java.nio.file.Files;
3435
import java.nio.file.Path;
3536
import java.nio.file.Paths;
36-
import java.nio.file.Files;
37+
import java.util.Optional;
3738

3839
import static java.nio.charset.StandardCharsets.UTF_8;
3940

@@ -46,13 +47,35 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
4647
// location is the same for all processes, otherwise the tools
4748
// will not be able to find all Hotspot processes.
4849
// Any changes to this needs to be synchronized with HotSpot.
49-
private static final String tmpdir = "/tmp";
50+
private static final Path TMPDIR = Path.of("/tmp");
51+
52+
private static final Path PROC = Path.of("/proc");
53+
private static final Path NS_MNT = Path.of("ns/mnt");
54+
private static final Path NS_PID = Path.of("ns/pid");
55+
private static final Path SELF = PROC.resolve("self");
56+
private static final Path STATUS = Path.of("status");
57+
private static final Path ROOT_TMP = Path.of("root/tmp");
58+
59+
private static final Optional<Path> SELF_MNT_NS;
60+
61+
static {
62+
Path nsPath = null;
63+
64+
try {
65+
nsPath = Files.readSymbolicLink(SELF.resolve(NS_MNT));
66+
} catch (IOException _) {
67+
// do nothing
68+
} finally {
69+
SELF_MNT_NS = Optional.ofNullable(nsPath);
70+
}
71+
}
72+
5073
String socket_path;
74+
5175
/**
5276
* Attaches to the target VM
5377
*/
54-
VirtualMachineImpl(AttachProvider provider, String vmid)
55-
throws AttachNotSupportedException, IOException
78+
VirtualMachineImpl(AttachProvider provider, String vmid) throws AttachNotSupportedException, IOException
5679
{
5780
super(provider, vmid);
5881

@@ -63,12 +86,12 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
6386
}
6487

6588
// Try to resolve to the "inner most" pid namespace
66-
int ns_pid = getNamespacePid(pid);
89+
final long ns_pid = getNamespacePid(pid);
6790

6891
// Find the socket file. If not found then we attempt to start the
6992
// attach mechanism in the target VM by sending it a QUIT signal.
7093
// Then we attempt to find the socket file again.
71-
File socket_file = findSocketFile(pid, ns_pid);
94+
final File socket_file = findSocketFile(pid, ns_pid);
7295
socket_path = socket_file.getPath();
7396
if (!socket_file.exists()) {
7497
// Keep canonical version of File, to delete, in case target process ends and /proc link has gone:
@@ -210,49 +233,102 @@ protected void close(long fd) throws IOException {
210233
}
211234

212235
// Return the socket file for the given process.
213-
private File findSocketFile(int pid, int ns_pid) throws IOException {
214-
String root = findTargetProcessTmpDirectory(pid, ns_pid);
215-
return new File(root, ".java_pid" + ns_pid);
236+
private File findSocketFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
237+
return new File(findTargetProcessTmpDirectory(pid, ns_pid), ".java_pid" + ns_pid);
216238
}
217239

218240
// On Linux a simple handshake is used to start the attach mechanism
219241
// if not already started. The client creates a .attach_pid<pid> file in the
220242
// target VM's working directory (or temp directory), and the SIGQUIT handler
221243
// checks for the file.
222-
private File createAttachFile(int pid, int ns_pid) throws IOException {
223-
String fn = ".attach_pid" + ns_pid;
224-
String path = "/proc/" + pid + "/cwd/" + fn;
225-
File f = new File(path);
244+
private File createAttachFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
245+
Path fn = Path.of(".attach_pid" + ns_pid);
246+
Path path = PROC.resolve(Path.of(Long.toString(pid), "cwd")).resolve(fn);
247+
File f = new File(path.toString());
226248
try {
227249
// Do not canonicalize the file path, or we will fail to attach to a VM in a container.
228250
f.createNewFile();
229-
} catch (IOException x) {
230-
String root = findTargetProcessTmpDirectory(pid, ns_pid);
231-
f = new File(root, fn);
251+
} catch (IOException _) {
252+
f = new File(findTargetProcessTmpDirectory(pid, ns_pid), fn.toString());
232253
f.createNewFile();
233254
}
234255
return f;
235256
}
236257

237-
private String findTargetProcessTmpDirectory(int pid, int ns_pid) throws IOException {
238-
String root;
239-
if (pid != ns_pid) {
240-
// A process may not exist in the same mount namespace as the caller, e.g.
241-
// if we are trying to attach to a JVM process inside a container.
242-
// Instead, attach relative to the target root filesystem as exposed by
243-
// procfs regardless of namespaces.
244-
String procRootDirectory = "/proc/" + pid + "/root";
245-
if (!Files.isReadable(Path.of(procRootDirectory))) {
246-
throw new IOException(
247-
String.format("Unable to access root directory %s " +
248-
"of target process %d", procRootDirectory, pid));
258+
private String findTargetProcessTmpDirectory(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
259+
// We need to handle at least 4 different cases:
260+
// 1. Caller and target processes share PID namespace and root filesystem (host to host or container to
261+
// container with both /tmp mounted between containers).
262+
// 2. Caller and target processes share PID namespace and root filesystem but the target process has elevated
263+
// privileges (host to host).
264+
// 3. Caller and target processes share PID namespace but NOT root filesystem (container to container).
265+
// 4. Caller and target processes share neither PID namespace nor root filesystem (host to container).
266+
267+
Optional<ProcessHandle> target = ProcessHandle.of(pid);
268+
Optional<ProcessHandle> ph = target;
269+
long nsPid = ns_pid;
270+
Optional<Path> prevPidNS = Optional.empty();
271+
272+
while (ph.isPresent()) {
273+
final var curPid = ph.get().pid();
274+
final var procPidPath = PROC.resolve(Long.toString(curPid));
275+
Optional<Path> targetMountNS = Optional.empty();
276+
277+
try {
278+
// attempt to read the target's mnt ns id
279+
targetMountNS = Optional.ofNullable(Files.readSymbolicLink(procPidPath.resolve(NS_MNT)));
280+
} catch (IOException _) {
281+
// if we fail to read the target's mnt ns id then we either don't have access or it no longer exists!
282+
if (!Files.exists(procPidPath)) {
283+
throw new IOException(String.format("unable to attach, %s non-existent! process: %d terminated", procPidPath, pid));
284+
}
285+
// the process still exists, but we don't have privileges to read its procfs
249286
}
250287

251-
root = procRootDirectory + "/" + tmpdir;
288+
final var sameMountNS = SELF_MNT_NS.isPresent() && SELF_MNT_NS.equals(targetMountNS);
289+
290+
if (sameMountNS) {
291+
return TMPDIR.toString(); // we share TMPDIR in common!
292+
} else {
293+
// we could not read the target's mnt ns
294+
final var procPidRootTmp = procPidPath.resolve(ROOT_TMP);
295+
if (Files.isReadable(procPidRootTmp)) {
296+
return procPidRootTmp.toString(); // not in the same mnt ns but tmp is accessible via /proc
297+
}
298+
}
299+
300+
// let's attempt to obtain the pid ns, best efforts to avoid crossing pid ns boundaries (as with a container)
301+
Optional<Path> curPidNS = Optional.empty();
302+
303+
try {
304+
// attempt to read the target's pid ns id
305+
curPidNS = Optional.ofNullable(Files.readSymbolicLink(procPidPath.resolve(NS_PID)));
306+
} catch (IOException _) {
307+
// if we fail to read the target's pid ns id then we either don't have access or it no longer exists!
308+
if (!Files.exists(procPidPath)) {
309+
throw new IOException(String.format("unable to attach, %s non-existent! process: %d terminated", procPidPath, pid));
310+
}
311+
// the process still exists, but we don't have privileges to read its procfs
312+
}
313+
314+
// recurse "up" the process hierarchy if appropriate. PID 1 cannot have a parent in the same namespace
315+
final var havePidNSes = prevPidNS.isPresent() && curPidNS.isPresent();
316+
final var ppid = ph.get().parent();
317+
318+
if (ppid.isPresent() && (havePidNSes && curPidNS.equals(prevPidNS)) || (!havePidNSes && nsPid > 1)) {
319+
ph = ppid;
320+
nsPid = getNamespacePid(ph.get().pid()); // get the ns pid of the parent
321+
prevPidNS = curPidNS;
322+
} else {
323+
ph = Optional.empty();
324+
}
325+
}
326+
327+
if (target.orElseThrow(AttachNotSupportedException::new).isAlive()) {
328+
return TMPDIR.toString(); // fallback...
252329
} else {
253-
root = tmpdir;
330+
throw new IOException(String.format("unable to attach, process: %d terminated", pid));
254331
}
255-
return root;
256332
}
257333

258334
/*
@@ -269,13 +345,12 @@ private void writeString(int fd, String s) throws IOException {
269345
write(fd, b, 0, 1);
270346
}
271347

272-
273348
// Return the inner most namespaced PID if there is one,
274349
// otherwise return the original PID.
275-
private int getNamespacePid(int pid) throws AttachNotSupportedException, IOException {
350+
private long getNamespacePid(long pid) throws AttachNotSupportedException, IOException {
276351
// Assuming a real procfs sits beneath, reading this doesn't block
277352
// nor will it consume a lot of memory.
278-
String statusFile = "/proc/" + pid + "/status";
353+
final var statusFile = PROC.resolve(Long.toString(pid)).resolve(STATUS).toString();
279354
File f = new File(statusFile);
280355
if (!f.exists()) {
281356
return pid; // Likely a bad pid, but this is properly handled later.
@@ -291,8 +366,7 @@ private int getNamespacePid(int pid) throws AttachNotSupportedException, IOExcep
291366
// The last entry represents the PID the JVM "thinks" it is.
292367
// Even in non-namespaced pids these entries should be
293368
// valid. You could refer to it as the inner most pid.
294-
int ns_pid = Integer.parseInt(parts[parts.length - 1]);
295-
return ns_pid;
369+
return Long.parseLong(parts[parts.length - 1]);
296370
}
297371
}
298372
// Old kernels may not have NSpid field (i.e. 3.10).

0 commit comments

Comments
 (0)