xref: /aosp_15_r20/external/libcap-ng/utils/pscap.c (revision 8dd5e09d5faf27a871e8654ddaa2d2af7c696578)
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