]> git.sven.stormbind.net Git - sven/jattach.git/blob - src/posix/psutil.c
releasing package jattach version 2.2-1
[sven/jattach.git] / src / posix / psutil.c
1 /*
2  * Copyright 2021 Andrei Pangin
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/stat.h>
21 #include <sys/syscall.h>
22 #include <dirent.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include "psutil.h"
26
27
28 // Less than MAX_PATH to leave some space for appending
29 char tmp_path[MAX_PATH - 100];
30
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);
37         return;
38     }
39
40     if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) {
41         strcpy(tmp_path, "/tmp");
42     }
43 }
44
45
46 #ifdef __linux__
47
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;
53     size_t size;
54     int result = -1;
55
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, '(');
60             if (c != NULL) {
61                 result = atoi(c + 1);
62             }
63         }
64         fclose(sched_file);
65     }
66
67     return result;
68 }
69
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) {
74     char path[300];
75     snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
76
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) {
81             return pid;
82         }
83     }
84
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);
89     if (dir != NULL) {
90         struct dirent* entry;
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                     closedir(dir);
97                     return atoi(entry->d_name);
98                 }
99             }
100         }
101         closedir(dir);
102     }
103
104     // Could not find container pid; return host pid as the last resort
105     return pid;
106 }
107
108 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
109     if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) {
110         return -1;
111     }
112
113     // Check if the remote /tmp can be accessed via /proc/[pid]/root
114     struct stat stats;
115     return stat(buf, &stats);
116 }
117
118 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
119     // Parse /proc/pid/status to find process credentials
120     char path[64];
121     snprintf(path, sizeof(path), "/proc/%d/status", pid);
122     FILE* status_file = fopen(path, "r");
123     if (status_file == NULL) {
124         return -1;
125     }
126
127     char* line = NULL;
128     size_t size;
129     int nspid_found = 0;
130
131     while (getline(&line, &size, status_file) != -1) {
132         if (strncmp(line, "Uid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
133             // Get the effective UID, which is the second value in the line
134             *uid = (uid_t)atoi(strtok(NULL, "\t "));
135         } else if (strncmp(line, "Gid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
136             // Get the effective GID, which is the second value in the line
137             *gid = (gid_t)atoi(strtok(NULL, "\t "));
138         } else if (strncmp(line, "NStgid:", 7) == 0) {
139             // PID namespaces can be nested; the last one is the innermost one
140             char* s;
141             for (s = strtok(line + 7, "\t "); s != NULL; s = strtok(NULL, "\t ")) {
142                 *nspid = atoi(s);
143             }
144             nspid_found = 1;
145         }
146     }
147
148     free(line);
149     fclose(status_file);
150
151     if (!nspid_found) {
152         *nspid = alt_lookup_nspid(pid);
153     }
154
155     return 0;
156 }
157
158 int enter_ns(int pid, const char* type) {
159 #ifdef __NR_setns
160     char path[64], selfpath[64];
161     snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type);
162     snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type);
163
164     struct stat oldns_stat, newns_stat;
165     if (stat(selfpath, &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
166         // Don't try to call setns() if we're in the same namespace already
167         if (oldns_stat.st_ino != newns_stat.st_ino) {
168             int newns = open(path, O_RDONLY);
169             if (newns < 0) {
170                 return -1;
171             }
172
173             // Some ancient Linux distributions do not have setns() function
174             int result = syscall(__NR_setns, newns, 0);
175             close(newns);
176             return result < 0 ? -1 : 1;
177         }
178     }
179 #endif // __NR_setns
180
181     return 0;
182 }
183
184 #elif defined(__APPLE__)
185
186 #include <sys/sysctl.h>
187
188 // macOS has a secure per-user temporary directory
189 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
190     size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize);
191     return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1;
192 }
193
194 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
195     int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
196     struct kinfo_proc info;
197     size_t len = sizeof(info);
198
199     if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
200         return -1;
201     }
202
203     *uid = info.kp_eproc.e_ucred.cr_uid;
204     *gid = info.kp_eproc.e_ucred.cr_gid;
205     *nspid = pid;
206     return 0;
207 }
208
209 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
210 int enter_ns(int pid, const char* type) {
211     return 0;
212 }
213
214 #else // __FreeBSD__
215
216 #include <sys/sysctl.h>
217 #include <sys/user.h>
218
219 // Use default /tmp path on FreeBSD
220 int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
221     return -1;
222 }
223
224 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
225     int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
226     struct kinfo_proc info;
227     size_t len = sizeof(info);
228
229     if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
230         return -1;
231     }
232
233     *uid = info.ki_uid;
234     *gid = info.ki_groups[0];
235     *nspid = pid;
236     return 0;
237 }
238
239 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
240 int enter_ns(int pid, const char* type) {
241     return 0;
242 }
243
244 #endif