Date: Tue, 30 May 2023 03:43:28 +0000
Subject: [PATCH 02/59] 8179498: attach in linux should be relative to
 /proc/pid/root and namespace aware
Bug url: https://bugs.openjdk.org/browse/JDK-8179498
---
 .../sun/tools/attach/LinuxVirtualMachine.java | 70 ++++++++++++++++---
 1 file changed, 62 insertions(+), 8 deletions(-)

diff --git a/jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java b/jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java
index 20fdb5c0d..cc2ac0df2 100644
--- a/jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java
+++ b/jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java
@@ -32,6 +32,10 @@ import com.sun.tools.attach.spi.AttachProvider;
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.Files;
 
 /*
  * Linux implementation of HotSpotVirtualMachine
@@ -66,12 +70,15 @@ public class LinuxVirtualMachine extends HotSpotVirtualMachine {
             throw new AttachNotSupportedException("Invalid process identifier");
         }
 
+        // Try to resolve to the "inner most" pid namespace
+        int ns_pid = getNamespacePid(pid);
+
         // 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.
-        path = findSocketFile(pid);
+        path = findSocketFile(pid, ns_pid);
         if (path == null) {
-            File f = createAttachFile(pid);
+            File f = createAttachFile(pid, ns_pid);
             try {
                 // On LinuxThreads each thread is a process and we don't have the
                 // pid of the VMThread which has SIGQUIT unblocked. To workaround
@@ -99,7 +106,7 @@ public class LinuxVirtualMachine extends HotSpotVirtualMachine {
                     try {
                         Thread.sleep(delay);
                     } catch (InterruptedException x) { }
-                    path = findSocketFile(pid);
+                    path = findSocketFile(pid, ns_pid);
                     i++;
                 } while (i <= retries && path == null);
                 if (path == null) {
@@ -272,8 +279,12 @@ public class LinuxVirtualMachine extends HotSpotVirtualMachine {
     }
 
     // Return the socket file for the given process.
-    private String findSocketFile(int pid) {
-        File f = new File(tmpdir, ".java_pid" + pid);
+    private String findSocketFile(int pid, int ns_pid) {
+        // A process may not exist in the same mount namespace as the caller.
+        // Instead, attach relative to the target root filesystem as exposed by
+        // procfs regardless of namespaces.
+        String root = "/proc/" + pid + "/root/" + tmpdir;
+        File f = new File(root, ".java_pid" + ns_pid);
         if (!f.exists()) {
             return null;
         }
@@ -284,14 +295,23 @@ public class LinuxVirtualMachine extends HotSpotVirtualMachine {
     // if not already started. The client creates a .attach_pid<pid> file in the
     // target VM's working directory (or temp directory), and the SIGQUIT handler
     // checks for the file.
-    private File createAttachFile(int pid) throws IOException {
-        String fn = ".attach_pid" + pid;
+    private File createAttachFile(int pid, int ns_pid) throws IOException {
+        String fn = ".attach_pid" + ns_pid;
         String path = "/proc/" + pid + "/cwd/" + fn;
         File f = new File(path);
         try {
             f.createNewFile();
         } catch (IOException x) {
-            f = new File(tmpdir, fn);
+            String root;
+            if (pid != ns_pid) {
+                // A process may not exist in the same mount namespace as the caller.
+                // Instead, attach relative to the target root filesystem as exposed by
+                // procfs regardless of namespaces.
+                root = "/proc/" + pid + "/root/" + tmpdir;
+            } else {
+                root = tmpdir;
+            }
+            f = new File(root, fn);
             f.createNewFile();
         }
         return f;
@@ -317,6 +337,40 @@ public class LinuxVirtualMachine extends HotSpotVirtualMachine {
     }
 
 
+    // Return the inner most namespaced PID if there is one,
+    // otherwise return the original PID.
+    private int getNamespacePid(int pid) throws AttachNotSupportedException, IOException {
+        // Assuming a real procfs sits beneath, reading this doesn't block
+        // nor will it consume a lot of memory.
+        String statusFile = "/proc/" + pid + "/status";
+        File f = new File(statusFile);
+        if (!f.exists()) {
+            return pid; // Likely a bad pid, but this is properly handled later.
+        }
+
+        Path statusPath = Paths.get(statusFile);
+
+        try {
+            for (String line : Files.readAllLines(statusPath, StandardCharsets.UTF_8)) {
+                String[] parts = line.split(":");
+                if (parts.length == 2 && parts[0].trim().equals("NSpid")) {
+                    parts = parts[1].trim().split("\\s+");
+                    // The last entry represents the PID the JVM "thinks" it is.
+                    // Even in non-namespaced pids these entries should be
+                    // valid. You could refer to it as the inner most pid.
+                    int ns_pid = Integer.parseInt(parts[parts.length - 1]);
+                    return ns_pid;
+                }
+            }
+            // Old kernels may not have NSpid field (i.e. 3.10).
+            // Fallback to original pid in the event we cannot deduce.
+            return pid;
+        } catch (NumberFormatException | IOException x) {
+            throw new AttachNotSupportedException("Unable to parse namespace");
+        }
+    }
+
+
     //-- native methods
 
     static native boolean isLinuxThreads();
-- 
2.22.0

