2 * Copyright jattach authors
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
21 #include <sys/syscall.h>
28 // Less than MAX_PATH to leave some space for appending
29 char tmp_path[MAX_PATH - 100];
31 // Called just once to fill in tmp_path buffer
32 void get_tmp_path(int pid) {
33 // Try user-provided alternative path first
34 const char* jattach_path = getenv("JATTACH_PATH");
35 if (jattach_path != NULL && strlen(jattach_path) < sizeof(tmp_path)) {
36 strcpy(tmp_path, jattach_path);
40 if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) {
41 strcpy(tmp_path, "/tmp");
48 // The first line of /proc/pid/sched looks like
49 // java (1234, #threads: 12)
50 // where 1234 is the host PID (before Linux 4.1)
51 static int sched_get_host_pid(const char* path) {
52 static char* line = NULL;
56 FILE* sched_file = fopen(path, "r");
57 if (sched_file != NULL) {
58 if (getline(&line, &size, sched_file) != -1) {
59 char* c = strrchr(line, '(');
70 // Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
71 // Fortunately, /proc/pid/sched in a container exposes a host PID,
72 // so the idea is to scan all container PIDs to find which one matches the host PID.
73 static int alt_lookup_nspid(int pid) {
75 snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
77 // Don't bother looking for container PID if we are already in the same PID namespace
78 struct stat oldns_stat, newns_stat;
79 if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
80 if (oldns_stat.st_ino == newns_stat.st_ino) {
85 // Otherwise browse all PIDs in the namespace of the target process
86 // trying to find which one corresponds to the host PID
87 snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
88 DIR* dir = opendir(path);
91 while ((entry = readdir(dir)) != NULL) {
92 if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
93 // Check if /proc/<container-pid>/sched points back to <host-pid>
94 snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
95 if (sched_get_host_pid(path) == pid) {
96 pid = atoi(entry->d_name);
107 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
108 if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) {
112 // Check if the remote /tmp can be accessed via /proc/[pid]/root
114 return stat(buf, &stats);
117 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
118 // Parse /proc/pid/status to find process credentials
120 snprintf(path, sizeof(path), "/proc/%d/status", pid);
121 FILE* status_file = fopen(path, "r");
122 if (status_file == NULL) {
130 while (getline(&line, &size, status_file) != -1) {
131 if (strncmp(line, "Uid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
132 // Get the effective UID, which is the second value in the line
133 *uid = (uid_t)atoi(strtok(NULL, "\t "));
134 } else if (strncmp(line, "Gid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
135 // Get the effective GID, which is the second value in the line
136 *gid = (gid_t)atoi(strtok(NULL, "\t "));
137 } else if (strncmp(line, "NStgid:", 7) == 0) {
138 // PID namespaces can be nested; the last one is the innermost one
140 for (s = strtok(line + 7, "\t "); s != NULL; s = strtok(NULL, "\t ")) {
151 *nspid = alt_lookup_nspid(pid);
157 int enter_ns(int pid, const char* type) {
159 char path[64], selfpath[64];
160 snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type);
161 snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type);
163 struct stat oldns_stat, newns_stat;
164 if (stat(selfpath, &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
165 // Don't try to call setns() if we're in the same namespace already
166 if (oldns_stat.st_ino != newns_stat.st_ino) {
167 int newns = open(path, O_RDONLY);
172 // Some ancient Linux distributions do not have setns() function
173 int result = syscall(__NR_setns, newns, 0);
175 return result < 0 ? -1 : 1;
183 #elif defined(__APPLE__)
185 #include <sys/sysctl.h>
187 // macOS has a secure per-user temporary directory
188 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
189 size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize);
190 return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1;
193 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
194 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
195 struct kinfo_proc info;
196 size_t len = sizeof(info);
198 if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
202 *uid = info.kp_eproc.e_ucred.cr_uid;
203 *gid = info.kp_eproc.e_ucred.cr_gid;
208 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
209 int enter_ns(int pid, const char* type) {
215 #include <sys/sysctl.h>
216 #include <sys/user.h>
218 // Use default /tmp path on FreeBSD
219 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
223 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
224 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
225 struct kinfo_proc info;
226 size_t len = sizeof(info);
228 if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
233 *gid = info.ki_groups[0];
238 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
239 int enter_ns(int pid, const char* type) {