1 /*
2 * pscap.c - A program that lists running processes with capabilities
3 * Copyright (c) 2009,2012,2020 Red Hat Inc.
4 * All Rights Reserved.
5 *
6 * This software may be freely redistributed and/or modified under the
7 * terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; see the file COPYING. If not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
19 * Boston, MA 02110-1335, USA.
20 *
21 * Authors:
22 * Steve Grubb <[email protected]>
23 */
24
25 #include "config.h"
26 #include <stdio.h>
27 #include <stdio_ext.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <dirent.h>
33 #include <fcntl.h>
34 #include <pwd.h>
35 #include <stdbool.h>
36 #include <sys/stat.h>
37 #include "cap-ng.h"
38
39 #define CMD_LEN 16
40 #define USERNS_MARK_LEN 2
41
usage(void)42 static void usage(void)
43 {
44 fprintf(stderr, "usage: pscap [-a|-p pid]\n");
45 exit(1);
46 }
47
48 /*
49 * Precise recursive checks for parent-child relation between namespaces
50 * using ioctl() were avoided, because there didn't seem to be any case when
51 * we may dereference the namespace symlink in /proc/PID/ns for processes in
52 * user namespaces other than the current or child ones. Thus, the check just
53 * tries to dereference the link and checks that it does not point to the
54 * current NS.
55 */
in_child_userns(int pid)56 static bool in_child_userns(int pid)
57 {
58 char ns_file_path[32];
59 struct stat statbuf;
60 ino_t own_ns_inode;
61 dev_t own_ns_dev;
62
63 if (stat("/proc/self/ns/user", &statbuf) < 0)
64 return false;
65
66 own_ns_inode = statbuf.st_ino;
67 own_ns_dev = statbuf.st_dev;
68
69 snprintf(ns_file_path, 32, "/proc/%d/ns/user", pid);
70 if (stat(ns_file_path, &statbuf) < 0)
71 return false;
72
73 return statbuf.st_ino != own_ns_inode || statbuf.st_dev != own_ns_dev;
74 }
75
main(int argc,char * argv[])76 int main(int argc, char *argv[])
77 {
78 char *endptr = NULL;
79 DIR *d;
80 struct dirent *ent;
81 int header = 0, show_all = 0, caps;
82 pid_t our_pid = getpid();
83 pid_t target_pid = 0;
84
85 if (argc > 3) {
86 fputs("Too many arguments\n", stderr);
87 usage();
88 }
89 if (argc == 2) {
90 if (strcmp(argv[1], "-a") == 0)
91 show_all = 1;
92 else
93 usage();
94 }
95 else if (argc == 3) {
96 if (strcmp(argv[1], "-p") == 0) {
97 errno = 0;
98 target_pid = strtol(argv[2], &endptr, 10);
99 if (errno) {
100 fprintf(stderr, "Can't read pid: %s\n", argv[2]);
101 return 1;
102 }
103 if ((endptr == argv[2]) || (*endptr != '\0') || !target_pid) {
104 fprintf(stderr, "Invalid pid argument: %s\n", argv[2]);
105 return 1;
106 }
107 if (target_pid == 1)
108 show_all = 1;
109 } else
110 usage();
111 }
112
113 d = opendir("/proc");
114 if (d == NULL) {
115 fprintf(stderr, "Can't open /proc: %s\n", strerror(errno));
116 return 1;
117 }
118 while (( ent = readdir(d) )) {
119 int pid, ppid, uid = -1, euid = -1;
120 char buf[100];
121 char *tmp, cmd[CMD_LEN + USERNS_MARK_LEN], state, *name = NULL;
122 int fd, len;
123 struct passwd *p;
124
125 // Skip non-process dir entries
126 if(*ent->d_name<'0' || *ent->d_name>'9')
127 continue;
128 errno = 0;
129 pid = strtol(ent->d_name, NULL, 10);
130 if (errno)
131 continue;
132
133 if (target_pid && (pid != target_pid))
134 continue;
135
136 /* Skip our pid so we aren't listed */
137 if (pid == our_pid)
138 continue;
139
140 // Parse up the stat file for the proc
141 snprintf(buf, 32, "/proc/%d/stat", pid);
142 fd = open(buf, O_RDONLY|O_CLOEXEC, 0);
143 if (fd < 0)
144 continue;
145 len = read(fd, buf, sizeof buf - 1);
146 close(fd);
147 if (len < 40)
148 continue;
149 buf[len] = 0;
150 tmp = strrchr(buf, ')');
151 if (tmp)
152 *tmp = 0;
153 else
154 continue;
155 memset(cmd, 0, sizeof(cmd));
156 sscanf(buf, "%d (%15c", &ppid, cmd);
157 sscanf(tmp+2, "%c %d", &state, &ppid);
158
159 // Skip kthreads
160 if (pid == 2 || ppid == 2)
161 continue;
162
163 if (!show_all && pid == 1)
164 continue;
165
166 // now get the capabilities
167 capng_clear(CAPNG_SELECT_ALL);
168 capng_setpid(pid);
169 if (capng_get_caps_process())
170 continue;
171
172 // And print out anything with capabilities
173 caps = capng_have_capabilities(CAPNG_SELECT_CAPS);
174 if (caps > CAPNG_NONE) {
175 // Get the effective uid
176 FILE *f;
177 int line;
178 snprintf(buf, 32, "/proc/%d/status", pid);
179 f = fopen(buf, "rte");
180 if (f == NULL)
181 euid = 0;
182 else {
183 line = 0;
184 __fsetlocking(f, FSETLOCKING_BYCALLER);
185 while (fgets(buf, sizeof(buf), f)) {
186 if (line == 0) {
187 line++;
188 continue;
189 }
190 if (memcmp(buf, "Uid:", 4) == 0) {
191 int id;
192 sscanf(buf, "Uid: %d %d",
193 &id, &euid);
194 break;
195 }
196 }
197 fclose(f);
198 }
199
200 if (header == 0) {
201 printf("%-5s %-5s %-10s %-18s %s\n",
202 "ppid", "pid", "uid", "command",
203 "capabilities");
204 header = 1;
205 }
206 if (euid == 0) {
207 // Take short cut for this one
208 name = "root";
209 uid = 0;
210 } else if (euid != uid) {
211 // Only look up if name changed
212 p = getpwuid(euid);
213 uid = euid;
214 if (p)
215 name = p->pw_name;
216 // If not taking this branch, use last val
217 }
218
219 if (in_child_userns(pid))
220 strcat(cmd, " *");
221
222 if (name) {
223 printf("%-5d %-5d %-10s %-18s ", ppid, pid,
224 name, cmd);
225 } else
226 printf("%-5d %-5d %-10d %-18s ", ppid, pid,
227 uid, cmd);
228 if (caps == CAPNG_PARTIAL) {
229 capng_print_caps_text(CAPNG_PRINT_STDOUT,
230 CAPNG_PERMITTED);
231 if (capng_have_capabilities(
232 CAPNG_SELECT_AMBIENT) > CAPNG_NONE)
233 printf(" @");
234 if (capng_have_capabilities(CAPNG_SELECT_BOUNDS)
235 > CAPNG_NONE)
236 printf(" +");
237 printf("\n");
238 } else {
239 printf("full");
240 if (capng_have_capabilities(
241 CAPNG_SELECT_AMBIENT) > CAPNG_NONE)
242 printf(" @");
243 if (capng_have_capabilities(CAPNG_SELECT_BOUNDS)
244 > CAPNG_NONE)
245 printf(" +");
246 printf("\n");
247 }
248 }
249 }
250 closedir(d);
251 return 0;
252 }
253
254
255