]> git.sven.stormbind.net Git - sven/jattach.git/blob - src/jattach_posix.c
update gitignore file
[sven/jattach.git] / src / jattach_posix.c
1 /*
2  * Copyright 2016 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/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <sys/un.h>
24 #include <sys/syscall.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <signal.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #define MAX_PATH 1024
33 #define TMP_PATH (MAX_PATH - 64)
34
35 static char temp_path_storage[TMP_PATH] = {0};
36
37
38 #ifdef __linux__
39
40 const char* get_temp_path() {
41     return temp_path_storage;
42 }
43
44 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
45     // A process may have its own root path (when running in chroot environment)
46     char path[64];
47     snprintf(path, sizeof(path), "/proc/%d/root", pid);
48
49     // Append /tmp to the resolved root symlink
50     ssize_t path_size = readlink(path, temp_path_storage, sizeof(temp_path_storage) - 10);
51     strcpy(temp_path_storage + (path_size > 1 ? path_size : 0), "/tmp");
52
53     // Parse /proc/pid/status to find process credentials
54     snprintf(path, sizeof(path), "/proc/%d/status", pid);
55     FILE* status_file = fopen(path, "r");
56     if (status_file == NULL) {
57         return 0;
58     }
59
60     char* line = NULL;
61     size_t size;
62
63     while (getline(&line, &size, status_file) != -1) {
64         if (strncmp(line, "Uid:", 4) == 0) {
65             // Get the effective UID, which is the second value in the line
66             *uid = (uid_t)atoi(strchr(line + 5, '\t'));
67         } else if (strncmp(line, "Gid:", 4) == 0) {
68             // Get the effective GID, which is the second value in the line
69             *gid = (gid_t)atoi(strchr(line + 5, '\t'));
70         } else if (strncmp(line, "NStgid:", 7) == 0) {
71             // PID namespaces can be nested; the last one is the innermost one
72             *nspid = atoi(strrchr(line, '\t'));
73         }
74     }
75
76     free(line);
77     fclose(status_file);
78     return 1;
79 }
80
81 int enter_mount_ns(int pid) {
82 #ifdef __NR_setns
83     char path[128];
84     snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
85
86     struct stat oldns_stat, newns_stat;
87     if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
88         // Don't try to call setns() if we're in the same namespace already
89         if (oldns_stat.st_ino != newns_stat.st_ino) {
90             int newns = open(path, O_RDONLY);
91             if (newns < 0) {
92                 return 0;
93             }
94
95             // Some ancient Linux distributions do not have setns() function
96             int result = syscall(__NR_setns, newns, 0);
97             close(newns);
98             return result < 0 ? 0 : 1;
99         }
100     }
101 #endif // __NR_setns
102
103     return 1;
104 }
105
106 // The first line of /proc/pid/sched looks like
107 // java (1234, #threads: 12)
108 // where 1234 is the required host PID
109 int sched_get_host_pid(const char* path) {
110     static char* line = NULL;
111     size_t size;
112     int result = -1;
113
114     FILE* sched_file = fopen(path, "r");
115     if (sched_file != NULL) {
116         if (getline(&line, &size, sched_file) != -1) {
117             char* c = strrchr(line, '(');
118             if (c != NULL) {
119                 result = atoi(c + 1);
120             }
121         }
122         fclose(sched_file);
123     }
124
125     return result;
126 }
127
128 // Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
129 // Fortunately, /proc/pid/sched in a container exposes a host PID,
130 // so the idea is to scan all container PIDs to find which one matches the host PID.
131 int alt_lookup_nspid(int pid) {
132     int namespace_differs = 0;
133     char path[300];
134     snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
135
136     // Don't bother looking for container PID if we are already in the same PID namespace
137     struct stat oldns_stat, newns_stat;
138     if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
139         if (oldns_stat.st_ino == newns_stat.st_ino) {
140             return pid;
141         }
142         namespace_differs = 1;
143     }
144
145     // Otherwise browse all PIDs in the namespace of the target process
146     // trying to find which one corresponds to the host PID
147     snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
148     DIR* dir = opendir(path);
149     if (dir != NULL) {
150         struct dirent* entry;
151         while ((entry = readdir(dir)) != NULL) {
152             if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
153                 // Check if /proc/<container-pid>/sched points back to <host-pid>
154                 snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
155                 if (sched_get_host_pid(path) == pid) {
156                     closedir(dir);
157                     return atoi(entry->d_name);
158                 }
159             }
160         }
161         closedir(dir);
162     }
163
164     if (namespace_differs) {
165         printf("WARNING: couldn't find container pid of the target process\n");
166     }
167
168     return pid;
169 }
170
171 #elif defined(__APPLE__)
172
173 #include <sys/sysctl.h>
174
175 // macOS has a secure per-user temporary directory
176 const char* get_temp_path() {
177     if (temp_path_storage[0] == 0) {
178         int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, sizeof(temp_path_storage));
179         if (path_size == 0 || path_size > sizeof(temp_path_storage)) {
180             strcpy(temp_path_storage, "/tmp");
181         }
182     }
183
184     return temp_path_storage;
185 }
186
187 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
188     int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
189     struct kinfo_proc info;
190     size_t len = sizeof(info);
191
192     if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
193         return 0;
194     }
195
196     *uid = info.kp_eproc.e_ucred.cr_uid;
197     *gid = info.kp_eproc.e_ucred.cr_gid;
198     *nspid = pid;
199     return 1;
200 }
201
202 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
203 int enter_mount_ns(int pid) {
204     return 1;
205 }
206
207 // Not used on macOS and FreeBSD
208 int alt_lookup_nspid(int pid) {
209     return pid;
210 }
211
212 #else // __FreeBSD__
213
214 #include <sys/sysctl.h>
215 #include <sys/user.h>
216
217 const char* get_temp_path() {
218     return "/tmp";
219 }
220
221 int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
222     int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
223     struct kinfo_proc info;
224     size_t len = sizeof(info);
225
226     if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
227         return 0;
228     }
229
230     *uid = info.ki_uid;
231     *gid = info.ki_groups[0];
232     *nspid = pid;
233     return 1;
234 }
235
236 // This is a Linux-specific API; nothing to do on macOS and FreeBSD
237 int enter_mount_ns(int pid) {
238     return 1;
239 }
240
241 // Not used on macOS and FreeBSD
242 int alt_lookup_nspid(int pid) {
243     return pid;
244 }
245
246 #endif
247
248
249 // Check if remote JVM has already opened socket for Dynamic Attach
250 static int check_socket(int pid) {
251     char path[MAX_PATH];
252     snprintf(path, sizeof(path), "%s/.java_pid%d", get_temp_path(), pid);
253
254     struct stat stats;
255     return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
256 }
257
258 // Check if a file is owned by current user
259 static int check_file_owner(const char* path) {
260     struct stat stats;
261     if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
262         return 1;
263     }
264
265     // Some mounted filesystems may change the ownership of the file.
266     // JVM will not trust such file, so it's better to remove it and try a different path
267     unlink(path);
268     return 0;
269 }
270
271 // Force remote JVM to start Attach listener.
272 // HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
273 static int start_attach_mechanism(int pid, int nspid) {
274     char path[MAX_PATH];
275     snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
276     
277     int fd = creat(path, 0660);
278     if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
279         // Failed to create attach trigger in current directory. Retry in /tmp
280         snprintf(path, sizeof(path), "%s/.attach_pid%d", get_temp_path(), nspid);
281         fd = creat(path, 0660);
282         if (fd == -1) {
283             return 0;
284         }
285         close(fd);
286     }
287     
288     // We have to still use the host namespace pid here for the kill() call
289     kill(pid, SIGQUIT);
290     
291     // Start with 20 ms sleep and increment delay each iteration
292     struct timespec ts = {0, 20000000};
293     int result;
294     do {
295         nanosleep(&ts, NULL);
296         result = check_socket(nspid);
297     } while (!result && (ts.tv_nsec += 20000000) < 300000000);
298
299     unlink(path);
300     return result;
301 }
302
303 // Connect to UNIX domain socket created by JVM for Dynamic Attach
304 static int connect_socket(int pid) {
305     int fd = socket(PF_UNIX, SOCK_STREAM, 0);
306     if (fd == -1) {
307         return -1;
308     }
309     
310     struct sockaddr_un addr;
311     addr.sun_family = AF_UNIX;
312     int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", get_temp_path(), pid);
313     if (bytes >= sizeof(addr.sun_path)) {
314         addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
315     }
316
317     if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
318         close(fd);
319         return -1;
320     }
321     return fd;
322 }
323
324 // Send command with arguments to socket
325 static int write_command(int fd, int argc, char** argv) {
326     // Protocol version
327     if (write(fd, "1", 2) <= 0) {
328         return 0;
329     }
330
331     int i;
332     for (i = 0; i < 4; i++) {
333         const char* arg = i < argc ? argv[i] : "";
334         if (write(fd, arg, strlen(arg) + 1) <= 0) {
335             return 0;
336         }
337     }
338     return 1;
339 }
340
341 // Mirror response from remote JVM to stdout
342 static int read_response(int fd) {
343     char buf[8192];
344     ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
345     if (bytes <= 0) {
346         perror("Error reading response");
347         return 1;
348     }
349
350     // First line of response is the command result code
351     buf[bytes] = 0;
352     int result = atoi(buf);
353
354     do {
355         fwrite(buf, 1, bytes, stdout);
356         bytes = read(fd, buf, sizeof(buf));
357     } while (bytes > 0);
358
359     return result;
360 }
361
362 int main(int argc, char** argv) {
363     if (argc < 3) {
364         printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
365                "Copyright 2018 Andrei Pangin\n"
366                "\n"
367                "Usage: jattach <pid> <cmd> [args ...]\n");
368         return 1;
369     }
370     
371     int pid = atoi(argv[1]);
372     if (pid == 0) {
373         perror("Invalid pid provided");
374         return 1;
375     }
376
377     uid_t my_uid = geteuid();
378     gid_t my_gid = getegid();
379     uid_t target_uid = my_uid;
380     gid_t target_gid = my_gid;
381     int nspid = -1;
382     if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
383         fprintf(stderr, "Process %d not found\n", pid);
384         return 1;
385     }
386
387     if (nspid < 0) {
388         nspid = alt_lookup_nspid(pid);
389     }
390
391     // Make sure our /tmp and target /tmp is the same
392     if (!enter_mount_ns(pid)) {
393         printf("WARNING: couldn't enter target process mnt namespace\n");
394     }
395
396     // Dynamic attach is allowed only for the clients with the same euid/egid.
397     // If we are running under root, switch to the required euid/egid automatically.
398     if ((my_gid != target_gid && setegid(target_gid) != 0) ||
399         (my_uid != target_uid && seteuid(target_uid) != 0)) {
400         perror("Failed to change credentials to match the target process");
401         return 1;
402     }
403
404     // Make write() return EPIPE instead of silent process termination
405     signal(SIGPIPE, SIG_IGN);
406
407     if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
408         perror("Could not start attach mechanism");
409         return 1;
410     }
411
412     int fd = connect_socket(nspid);
413     if (fd == -1) {
414         perror("Could not connect to socket");
415         return 1;
416     }
417     
418     printf("Connected to remote JVM\n");
419     if (!write_command(fd, argc - 2, argv + 2)) {
420         perror("Error writing to socket");
421         close(fd);
422         return 1;
423     }
424
425     printf("Response code = ");
426     fflush(stdout);
427
428     int result = read_response(fd);
429     printf("\n");
430     close(fd);
431
432     return result;
433 }