2828import com .sun .tools .attach .AttachNotSupportedException ;
2929import com .sun .tools .attach .spi .AttachProvider ;
3030
31- import java .io .InputStream ;
32- import java .io .IOException ;
3331import java .io .File ;
32+ import java .io .IOException ;
33+ import java .io .InputStream ;
34+ import java .nio .file .Files ;
3435import java .nio .file .Path ;
3536import java .nio .file .Paths ;
36- import java .nio . file . Files ;
37+ import java .util . Optional ;
3738
3839import 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