1 /*
2 * Copyright (c) 2020-2022 The strace developers.
3 * All rights reserved.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7
8 #include "defs.h"
9
10 #include <stdlib.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <selinux/selinux.h>
16 #include <selinux/label.h>
17
18 #include "largefile_wrappers.h"
19 #include "number_set.h"
20 #include "secontext.h"
21 #include "xmalloc.h"
22 #include "xstring.h"
23
24 /**
25 * @param secontext Pointer to security context string.
26 * @param result Stores pointer to the beginning of the part to print.
27 * @return Number of characters of the string to be printed.
28 */
29 static size_t
30 parse_secontext(char *secontext, char **result)
31 {
32 char *end_pos = NULL;
33
34 if (!is_number_in_set(SECONTEXT_FULL, secontext_set)) {
35 /* We're looking for the type wihch is the third field */
36 enum { SECONTEXT_TYPE = 2 };
37 char *start_pos = secontext;
38
39 for (unsigned int i = 0; i <= SECONTEXT_TYPE; i++) {
40 end_pos = strchr(start_pos, ':');
41
42 if (i == SECONTEXT_TYPE) {
43 secontext = start_pos;
44 break;
45 }
46
47 if (!end_pos)
48 break;
49
50 start_pos = end_pos + 1;
51 }
52 }
53
54 size_t len = end_pos ? (size_t) (end_pos - secontext)
55 : strlen(secontext);
56
57 /* Strip terminating \n as these tend to be present sometimes */
58 while (len && secontext[len - 1] == '\n')
59 len--;
60
61 return *result = secontext, len;
62 }
63
64 static int
65 get_expected_filecontext(const char *path, char **secontext, int mode)
66 {
67 static struct selabel_handle *hdl;
68
69 if (!hdl) {
70 static bool disabled;
71 if (disabled)
72 return -1;
73
74 hdl = selabel_open(SELABEL_CTX_FILE, NULL, 0);
75 if (!hdl) {
76 perror_msg("could not open SELinux database, disabling "
77 "context mismatch checking");
78 disabled = true;
79 return -1;
80 }
81 }
82
83 return selabel_lookup(hdl, secontext, path, mode);
84 }
85
86 /*
87 * Retrieves the SELinux context of the given PID (extracted from the tcb).
88 * Memory must be freed.
89 * Returns 0 on success, -1 on failure.
90 */
91 static int
92 selinux_getpidcon(struct tcb *tcp, char **secontext)
93 {
94 if (number_set_array_is_empty(secontext_set, 0))
95 return -1;
96
97 int proc_pid = get_proc_pid(tcp->pid);
98 if (!proc_pid)
99 return -1;
100
101 return getpidcon(proc_pid, secontext);
102 }
103
104 /*
105 * Retrieves the SELinux context of the given pid and descriptor.
106 * Memory must be freed.
107 * Returns 0 on success, -1 on failure.
108 */
109 static int
110 selinux_getfdcon(pid_t pid, int fd, char **secontext, char **expected)
111 {
112 if (number_set_array_is_empty(secontext_set, 0) || pid <= 0 || fd < 0)
113 return -1;
114
115 int proc_pid = get_proc_pid(pid);
116 if (!proc_pid)
117 return -1;
118
119 char linkpath[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int)*3];
120 xsprintf(linkpath, "/proc/%u/fd/%u", proc_pid, fd);
121
122 int rc = getfilecon(linkpath, secontext);
123 if (rc < 0 || !is_number_in_set(SECONTEXT_MISMATCH, secontext_set))
124 return rc;
125
126 /*
127 * We need to resolve the path, because selabel_lookup() doesn't
128 * resolve anything.
129 */
130 char buf[PATH_MAX + 1];
131 ssize_t n = get_proc_pid_fd_path(proc_pid, fd, buf, sizeof(buf), NULL);
132 if ((size_t) n >= (sizeof(buf) - 1))
133 return 0;
134
135 /*
136 * We retrieve stat() here since the path the procfs link resolves into
137 * may be reused by a different file with different context.
138 */
139 strace_stat_t st;
140 if (stat_file(linkpath, &st))
141 return 0;
142
143 get_expected_filecontext(buf, expected, st.st_mode);
144
145 return 0;
146 }
147
148 /*
149 * Retrieves the SELinux context of the given path.
150 * Memory must be freed.
151 * Returns 0 on success, -1 on failure.
152 */
153 static int
154 selinux_getfilecon(struct tcb *tcp, const char *path, char **secontext,
155 char **expected)
156 {
157 if (number_set_array_is_empty(secontext_set, 0))
158 return -1;
159
160 int proc_pid = get_proc_pid(tcp->pid);
161 if (!proc_pid)
162 return -1;
163
164 int rc = -1;
165 char fname[PATH_MAX];
166
167 if (path[0] == '/')
168 rc = snprintf(fname, sizeof(fname), "/proc/%u/root%s",
169 proc_pid, path);
170 else if (tcp->last_dirfd == AT_FDCWD)
171 rc = snprintf(fname, sizeof(fname), "/proc/%u/cwd/%s",
172 proc_pid, path);
173 else if (tcp->last_dirfd >= 0 )
174 rc = snprintf(fname, sizeof(fname), "/proc/%u/fd/%u/%s",
175 proc_pid, tcp->last_dirfd, path);
176
177 if ((unsigned int) rc >= sizeof(fname))
178 return -1;
179
180 rc = getfilecon(fname, secontext);
181 if (rc < 0 || !is_number_in_set(SECONTEXT_MISMATCH, secontext_set))
182 return rc;
183
184 /*
185 * We need to fully resolve the path, because selabel_lookup() doesn't
186 * resolve anything. Using realpath() is the only solution here to make
187 * sure the path is canonicalized.
188 */
189
190 char *resolved = realpath(fname, NULL);
191 if (!resolved)
192 return 0;
193
194 strace_stat_t st;
195 if (stat_file(resolved, &st) < 0)
196 goto out;
197
198 get_expected_filecontext(resolved, expected, st.st_mode);
199
200 out:
201 free(resolved);
202
203 return 0;
204 }
205
206 static void
207 print_context(char *secontext, char *expected)
208 {
209 if (!secontext)
210 return;
211
212 unsigned int style = QUOTE_OMIT_LEADING_TRAILING_QUOTES
213 | QUOTE_OVERWRITE_HEXSTR |
214 (xflag == HEXSTR_NONE
215 ? QUOTE_HEXSTR_NONE
216 : QUOTE_HEXSTR_NON_ASCII_CHARS);
217
218 char *ctx_str;
219 ssize_t ctx_len = parse_secontext(secontext, &ctx_str);
220
221 print_quoted_string_ex(ctx_str, ctx_len, style, "[]!");
222
223 if (!expected)
224 goto freecon_secontext;
225
226 char *exp_str;
227 ssize_t exp_len = parse_secontext(expected, &exp_str);
228
229 if (ctx_len != exp_len || strncmp(ctx_str, exp_str, ctx_len)) {
230 tprints_string("!!");
231 print_quoted_string_ex(exp_str, exp_len, style, "[]!");
232 }
233
234 freecon(expected);
235 freecon_secontext:
236 freecon(secontext);
237 }
238
239 void
240 selinux_printfdcon(pid_t pid, int fd)
241 {
242 char *ctx = NULL;
243 char *exp = NULL;
244
245 if (selinux_getfdcon(pid, fd, &ctx, &exp) < 0)
246 return;
247
248 tprint_space();
249 tprint_attribute_begin();
250 print_context(ctx, exp);
251 tprint_attribute_end();
252 }
253
254 void
255 selinux_printfilecon(struct tcb *tcp, const char *path)
256 {
257 char *ctx = NULL;
258 char *exp = NULL;
259
260 if (selinux_getfilecon(tcp, path, &ctx, &exp) < 0)
261 return;
262
263 tprint_space();
264 tprint_attribute_begin();
265 print_context(ctx, exp);
266 tprint_attribute_end();
267 }
268
269 void
270 selinux_printpidcon(struct tcb *tcp)
271 {
272 char *ctx = NULL;
273
274 if (selinux_getpidcon(tcp, &ctx) < 0)
275 return;
276
277 tprint_attribute_begin();
278 print_context(ctx, NULL);
279 tprint_attribute_end();
280 tprint_space();
281 }