28
28
import com .sun .tools .attach .AttachNotSupportedException ;
29
29
import com .sun .tools .attach .spi .AttachProvider ;
30
30
31
- import java .io .InputStream ;
32
- import java .io .IOException ;
33
31
import java .io .File ;
32
+ import java .io .IOException ;
33
+ import java .io .InputStream ;
34
+ import java .nio .file .Files ;
34
35
import java .nio .file .Path ;
35
36
import java .nio .file .Paths ;
36
- import java .nio . file . Files ;
37
+ import java .util . Optional ;
37
38
38
39
import static java .nio .charset .StandardCharsets .UTF_8 ;
39
40
@@ -47,13 +48,35 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
47
48
// location is the same for all processes, otherwise the tools
48
49
// will not be able to find all Hotspot processes.
49
50
// Any changes to this needs to be synchronized with HotSpot.
50
- private static final String tmpdir = "/tmp" ;
51
+ private static final Path TMPDIR = Path .of ("/tmp" );
52
+
53
+ private static final Path PROC = Path .of ("/proc" );
54
+ private static final Path NS_MNT = Path .of ("ns/mnt" );
55
+ private static final Path NS_PID = Path .of ("ns/pid" );
56
+ private static final Path SELF = PROC .resolve ("self" );
57
+ private static final Path STATUS = Path .of ("status" );
58
+ private static final Path ROOT_TMP = Path .of ("root/tmp" );
59
+
60
+ private static final Optional <Path > SELF_MNT_NS ;
61
+
62
+ static {
63
+ Path nsPath = null ;
64
+
65
+ try {
66
+ nsPath = Files .readSymbolicLink (SELF .resolve (NS_MNT ));
67
+ } catch (IOException _) {
68
+ // do nothing
69
+ } finally {
70
+ SELF_MNT_NS = Optional .ofNullable (nsPath );
71
+ }
72
+ }
73
+
51
74
String socket_path ;
75
+
52
76
/**
53
77
* Attaches to the target VM
54
78
*/
55
- VirtualMachineImpl (AttachProvider provider , String vmid )
56
- throws AttachNotSupportedException , IOException
79
+ VirtualMachineImpl (AttachProvider provider , String vmid ) throws AttachNotSupportedException , IOException
57
80
{
58
81
super (provider , vmid );
59
82
@@ -64,12 +87,12 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
64
87
}
65
88
66
89
// Try to resolve to the "inner most" pid namespace
67
- int ns_pid = getNamespacePid (pid );
90
+ final long ns_pid = getNamespacePid (pid );
68
91
69
92
// Find the socket file. If not found then we attempt to start the
70
93
// attach mechanism in the target VM by sending it a QUIT signal.
71
94
// Then we attempt to find the socket file again.
72
- File socket_file = findSocketFile (pid , ns_pid );
95
+ final File socket_file = findSocketFile (pid , ns_pid );
73
96
socket_path = socket_file .getPath ();
74
97
if (!socket_file .exists ()) {
75
98
// Keep canonical version of File, to delete, in case target process ends and /proc link has gone:
@@ -211,49 +234,102 @@ protected void close(long fd) throws IOException {
211
234
}
212
235
213
236
// Return the socket file for the given process.
214
- private File findSocketFile (int pid , int ns_pid ) throws IOException {
215
- String root = findTargetProcessTmpDirectory (pid , ns_pid );
216
- return new File (root , ".java_pid" + ns_pid );
237
+ private File findSocketFile (long pid , long ns_pid ) throws AttachNotSupportedException , IOException {
238
+ return new File (findTargetProcessTmpDirectory (pid , ns_pid ), ".java_pid" + ns_pid );
217
239
}
218
240
219
241
// On Linux a simple handshake is used to start the attach mechanism
220
242
// if not already started. The client creates a .attach_pid<pid> file in the
221
243
// target VM's working directory (or temp directory), and the SIGQUIT handler
222
244
// checks for the file.
223
- private File createAttachFile (int pid , int ns_pid ) throws IOException {
224
- String fn = ".attach_pid" + ns_pid ;
225
- String path = "/proc/" + pid + "/ cwd/" + fn ;
226
- File f = new File (path );
245
+ private File createAttachFile (long pid , long ns_pid ) throws AttachNotSupportedException , IOException {
246
+ Path fn = Path . of ( ".attach_pid" + ns_pid ) ;
247
+ Path path = PROC . resolve ( Path . of ( Long . toString ( pid ), " cwd" )). resolve ( fn ) ;
248
+ File f = new File (path . toString () );
227
249
try {
228
250
// Do not canonicalize the file path, or we will fail to attach to a VM in a container.
229
251
f .createNewFile ();
230
- } catch (IOException x ) {
231
- String root = findTargetProcessTmpDirectory (pid , ns_pid );
232
- f = new File (root , fn );
252
+ } catch (IOException _) {
253
+ f = new File (findTargetProcessTmpDirectory (pid , ns_pid ), fn .toString ());
233
254
f .createNewFile ();
234
255
}
235
256
return f ;
236
257
}
237
258
238
- private String findTargetProcessTmpDirectory (int pid , int ns_pid ) throws IOException {
239
- String root ;
240
- if (pid != ns_pid ) {
241
- // A process may not exist in the same mount namespace as the caller, e.g.
242
- // if we are trying to attach to a JVM process inside a container.
243
- // Instead, attach relative to the target root filesystem as exposed by
244
- // procfs regardless of namespaces.
245
- String procRootDirectory = "/proc/" + pid + "/root" ;
246
- if (!Files .isReadable (Path .of (procRootDirectory ))) {
247
- throw new IOException (
248
- String .format ("Unable to access root directory %s " +
249
- "of target process %d" , procRootDirectory , pid ));
259
+ private String findTargetProcessTmpDirectory (long pid , long ns_pid ) throws AttachNotSupportedException , IOException {
260
+ // We need to handle at least 4 different cases:
261
+ // 1. Caller and target processes share PID namespace and root filesystem (host to host or container to
262
+ // container with both /tmp mounted between containers).
263
+ // 2. Caller and target processes share PID namespace and root filesystem but the target process has elevated
264
+ // privileges (host to host).
265
+ // 3. Caller and target processes share PID namespace but NOT root filesystem (container to container).
266
+ // 4. Caller and target processes share neither PID namespace nor root filesystem (host to container).
267
+
268
+ Optional <ProcessHandle > target = ProcessHandle .of (pid );
269
+ Optional <ProcessHandle > ph = target ;
270
+ long nsPid = ns_pid ;
271
+ Optional <Path > prevPidNS = Optional .empty ();
272
+
273
+ while (ph .isPresent ()) {
274
+ final var curPid = ph .get ().pid ();
275
+ final var procPidPath = PROC .resolve (Long .toString (curPid ));
276
+ Optional <Path > targetMountNS = Optional .empty ();
277
+
278
+ try {
279
+ // attempt to read the target's mnt ns id
280
+ targetMountNS = Optional .ofNullable (Files .readSymbolicLink (procPidPath .resolve (NS_MNT )));
281
+ } catch (IOException _) {
282
+ // if we fail to read the target's mnt ns id then we either don't have access or it no longer exists!
283
+ if (!Files .exists (procPidPath )) {
284
+ throw new IOException (String .format ("unable to attach, %s non-existent! process: %d terminated" , procPidPath , pid ));
285
+ }
286
+ // the process still exists, but we don't have privileges to read its procfs
250
287
}
251
288
252
- root = procRootDirectory + "/" + tmpdir ;
289
+ final var sameMountNS = SELF_MNT_NS .isPresent () && SELF_MNT_NS .equals (targetMountNS );
290
+
291
+ if (sameMountNS ) {
292
+ return TMPDIR .toString (); // we share TMPDIR in common!
293
+ } else {
294
+ // we could not read the target's mnt ns
295
+ final var procPidRootTmp = procPidPath .resolve (ROOT_TMP );
296
+ if (Files .isReadable (procPidRootTmp )) {
297
+ return procPidRootTmp .toString (); // not in the same mnt ns but tmp is accessible via /proc
298
+ }
299
+ }
300
+
301
+ // let's attempt to obtain the pid ns, best efforts to avoid crossing pid ns boundaries (as with a container)
302
+ Optional <Path > curPidNS = Optional .empty ();
303
+
304
+ try {
305
+ // attempt to read the target's pid ns id
306
+ curPidNS = Optional .ofNullable (Files .readSymbolicLink (procPidPath .resolve (NS_PID )));
307
+ } catch (IOException _) {
308
+ // if we fail to read the target's pid ns id then we either don't have access or it no longer exists!
309
+ if (!Files .exists (procPidPath )) {
310
+ throw new IOException (String .format ("unable to attach, %s non-existent! process: %d terminated" , procPidPath , pid ));
311
+ }
312
+ // the process still exists, but we don't have privileges to read its procfs
313
+ }
314
+
315
+ // recurse "up" the process hierarchy if appropriate. PID 1 cannot have a parent in the same namespace
316
+ final var havePidNSes = prevPidNS .isPresent () && curPidNS .isPresent ();
317
+ final var ppid = ph .get ().parent ();
318
+
319
+ if (ppid .isPresent () && (havePidNSes && curPidNS .equals (prevPidNS )) || (!havePidNSes && nsPid > 1 )) {
320
+ ph = ppid ;
321
+ nsPid = getNamespacePid (ph .get ().pid ()); // get the ns pid of the parent
322
+ prevPidNS = curPidNS ;
323
+ } else {
324
+ ph = Optional .empty ();
325
+ }
326
+ }
327
+
328
+ if (target .orElseThrow (AttachNotSupportedException ::new ).isAlive ()) {
329
+ return TMPDIR .toString (); // fallback...
253
330
} else {
254
- root = tmpdir ;
331
+ throw new IOException ( String . format ( "unable to attach, process: %d terminated" , pid )) ;
255
332
}
256
- return root ;
257
333
}
258
334
259
335
/*
@@ -270,13 +346,12 @@ private void writeString(int fd, String s) throws IOException {
270
346
write (fd , b , 0 , 1 );
271
347
}
272
348
273
-
274
349
// Return the inner most namespaced PID if there is one,
275
350
// otherwise return the original PID.
276
- private int getNamespacePid (int pid ) throws AttachNotSupportedException , IOException {
351
+ private long getNamespacePid (long pid ) throws AttachNotSupportedException , IOException {
277
352
// Assuming a real procfs sits beneath, reading this doesn't block
278
353
// nor will it consume a lot of memory.
279
- String statusFile = "/proc/" + pid + "/status" ;
354
+ final var statusFile = PROC . resolve ( Long . toString ( pid )). resolve ( STATUS ). toString () ;
280
355
File f = new File (statusFile );
281
356
if (!f .exists ()) {
282
357
return pid ; // Likely a bad pid, but this is properly handled later.
@@ -292,8 +367,7 @@ private int getNamespacePid(int pid) throws AttachNotSupportedException, IOExcep
292
367
// The last entry represents the PID the JVM "thinks" it is.
293
368
// Even in non-namespaced pids these entries should be
294
369
// valid. You could refer to it as the inner most pid.
295
- int ns_pid = Integer .parseInt (parts [parts .length - 1 ]);
296
- return ns_pid ;
370
+ return Long .parseLong (parts [parts .length - 1 ]);
297
371
}
298
372
}
299
373
// Old kernels may not have NSpid field (i.e. 3.10).
0 commit comments