GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/capabilities.cc
Date: 2026-09-13 02:40:16
Exec Total Coverage
Lines: 26 180 14.4%
Branches: 19 188 10.1%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include <errno.h>
7 #ifdef __APPLE__
8 #include <unistd.h>
9 #else
10 #include <sys/prctl.h>
11 #endif
12
13 #include <cstdlib>
14
15 #include "util/capabilities.h"
16 #include "util/exception.h"
17 #include "util/logging.h"
18 #include "util/platform.h"
19 #include "util/posix.h"
20
21 #ifdef CVMFS_NAMESPACE_GUARD
22 namespace CVMFS_NAMESPACE_GUARD {
23 #endif
24
25 #ifdef __APPLE__
26
27 bool ClearPermittedCapabilities(const std::vector<cap_value_t> &,
28 const std::vector<cap_value_t> &) {
29 return true;
30 }
31
32 namespace {
33
34 uid_t old_uid;
35 gid_t old_gid;
36
37 bool ObtainCapability(const cap_value_t,
38 const char *,
39 const bool avoid_mutexes = false) {
40 // there are no individual capabilities on OSX so switch to root.
41 // Only save uid/gid before the first elevation; subsequent calls while
42 // already root would overwrite them with 0 and the Drop would never
43 // restore to the original non-root credentials.
44 if (geteuid() != 0) {
45 old_uid = geteuid();
46 old_gid = getegid();
47 }
48 return (SwitchCredentials(0, getgid(), true, avoid_mutexes));
49 }
50
51 bool CheckCapabilityPermitted(const cap_value_t) {
52 return (getuid() == 0);
53 }
54
55 bool DropCapability(const cap_value_t,
56 const char *,
57 const bool avoid_mutexes = false) {
58 // there are no individual capabilities on OSX so temporarily back to user
59 return (SwitchCredentials(old_uid, old_gid, true, avoid_mutexes));
60 }
61
62 } // namespace
63
64 #else
65
66 /**
67 * Clear all CAP_PERMITTED capabilities except those reserved.
68 * This function requires being run with CAP_SETPCAP capability permitted.
69 * If the real uid & gid do not match the effective uid & gid, it also
70 * requires CAP_SETUID and CAP_SETGID capabilities to be permitted and
71 * ends up switching the real uid & gid to match the incoming effective
72 * uid & gid. Beware that switching the uid is not thread-safe; it is
73 * process-wide and clears all capabilities from threads that do not
74 * have keepcaps enabled.
75 *
76 * @param[in] reservecaps vector of capabilities to reserve
77 * @param[in] inheritcaps vector of capabilities to make inheritable
78 */
79 bool ClearPermittedCapabilities(const std::vector<cap_value_t> &reservecaps,
80 const std::vector<cap_value_t> &inheritcaps) {
81 int retval = 0;
82 uid_t uid, gid;
83 const int nreservecaps = (int) reservecaps.size();
84 const int ninheritcaps = (int) inheritcaps.size();
85
86 if (!SetpcapCapabilityPermitted()) {
87 if (nreservecaps > 0) {
88 LogCvmfs(kLogCvmfs, kLogDebug,
89 "Capabilities cannot be reserved because setpcap is not "
90 "permitted.");
91 return false;
92 }
93 LogCvmfs(kLogCvmfs, kLogDebug,
94 "Capabilities are already cleared because setpcap is not "
95 "permitted.");
96 return true;
97 }
98
99 uid = geteuid();
100 gid = getegid();
101 if ((uid != getuid()) || (gid != getgid())) {
102 // Only do setuid & setgid when necessary because it is a process-wide
103 // setting, not a per-thread setting.
104 if (!ObtainSetuidgidCapabilities()) {
105 LogCvmfs(kLogCvmfs, kLogSyslogErr | kLogDebug,
106 "Failed to obtain setuid/setgid capabilities"
107 " while clearing capabilities (errno: %d)",
108 errno);
109 return false;
110 }
111 if (nreservecaps != 0) {
112 // keep all capabilities when switching uid, and clear all but the
113 // reserved ones below
114 if (!platform_keepcaps(true)) {
115 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
116 "Failed to retain capabilities while switching credentials "
117 "(errno: %d)", errno);
118 }
119 }
120 retval = setgid(gid) || setuid(uid);
121 if (nreservecaps != 0) {
122 if (!platform_keepcaps(false)) {
123 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
124 "Failed to stop retaining capabilities after switching "
125 "credentials (errno: %d)", errno);
126 }
127 }
128 if (retval != 0) {
129 LogCvmfs(kLogCvmfs, kLogSyslogErr | kLogDebug,
130 "Failed to set uid %d gid %d while clearing capabilities (errno: %d)",
131 uid, gid, errno);
132 return false;
133 }
134 if (nreservecaps == 0) {
135 // all capabilities have been dropped
136 return true;
137 }
138 }
139
140 if (!ObtainSetpcapCapability()) {
141 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
142 "Failed to obtain setpcap capability while clearing capabilities "
143 "(errno: %d)", errno);
144 }
145
146 cap_t caps_proc = cap_get_proc();
147 if (caps_proc == NULL) {
148 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
149 "Failed to get process capabilities (errno: %d)", errno);
150 }
151
152 for (int i = 0; i < nreservecaps; i++) {
153 const cap_value_t cap = reservecaps[i];
154
155 #ifdef CAP_IS_SUPPORTED
156 if (!CAP_IS_SUPPORTED(cap)) {
157 cap_free(caps_proc);
158 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
159 "Capability 0x%x is not supported", cap);
160 }
161 #endif
162
163 cap_flag_value_t cap_state;
164 retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state);
165 if (retval != 0) {
166 cap_free(caps_proc);
167 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
168 "Failed to inspect capability 0x%x (errno: %d)", cap, errno);
169 }
170 if (cap_state != CAP_SET) {
171 LogCvmfs(kLogCvmfs, kLogDebug,
172 "Warning: cap 0x%x cannot be reserved. "
173 "It's not in the process's permitted set.",
174 cap);
175 }
176 }
177
178 // Drop all EFFECTIVE, PERMITTED, and INHERITABLE capabilities other
179 // than those requested PERMITTED & INHERITABLE capabilities.
180 retval = cap_clear(caps_proc);
181 if (retval != 0) {
182 cap_free(caps_proc);
183 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
184 "Failed to clear process capabilities (errno: %d)", errno);
185 }
186
187 if (nreservecaps != 0) {
188 retval = cap_set_flag(caps_proc, CAP_PERMITTED,
189 nreservecaps, reservecaps.data(), CAP_SET);
190 if (retval != 0) {
191 cap_free(caps_proc);
192 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
193 "Failed to reserve process capabilities (errno: %d)", errno);
194 }
195 if (ninheritcaps != 0) {
196 retval = cap_set_flag(caps_proc, CAP_INHERITABLE,
197 ninheritcaps, inheritcaps.data(), CAP_SET);
198 if (retval != 0) {
199 cap_free(caps_proc);
200 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
201 "Failed to make process capabilities inheritable (errno: %d)",
202 errno);
203 }
204 }
205 }
206
207 retval = cap_set_proc(caps_proc);
208 const int saveerrno = errno;
209 cap_free(caps_proc);
210
211 if (retval != 0) {
212 errno = saveerrno; // otherwise the linter doesn't see saveerrno as used
213 LogCvmfs(kLogCvmfs, kLogDebug,
214 "Cannot clear permitted capabilities for current process "
215 "(errno: %d)",
216 errno);
217 return false;
218 }
219
220 if (ninheritcaps != 0) {
221 for (int i = 0; i < ninheritcaps; i++) {
222 retval = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE,
223 inheritcaps[i], 0, 0);
224 if (retval != 0) {
225 PANIC(kLogStderr | kLogSyslogErr | kLogDebug,
226 "Failed to raise ambient capability 0x%x (errno: %d)",
227 inheritcaps[i], errno);
228 }
229 }
230 }
231
232 return true;
233 }
234
235 namespace {
236
237 37 bool ObtainCapability(const cap_value_t cap,
238 const char *capname,
239 const bool avoid_mutexes = false) {
240 #ifdef CAP_IS_SUPPORTED
241
2/4
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 37 times.
37 if (!CAP_IS_SUPPORTED(cap)) {
242 if (avoid_mutexes)
243 abort();
244 PANIC(kLogSyslogErr | kLogDebug, "Capability %s is not supported", capname);
245 }
246 #endif
247
248
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 cap_t caps_proc = cap_get_proc();
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 if (caps_proc == NULL) {
250 if (avoid_mutexes)
251 abort();
252 PANIC(kLogSyslogErr | kLogDebug,
253 "Cannot get process capabilities (errno: %d)", errno);
254 }
255
256 cap_flag_value_t cap_state;
257
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 int retval = cap_get_flag(caps_proc, cap, CAP_EFFECTIVE, &cap_state);
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 if (retval != 0) {
259 cap_free(caps_proc);
260 if (avoid_mutexes)
261 abort();
262 PANIC(kLogSyslogErr | kLogDebug, "Cannot inspect %s capability (errno: %d)",
263 capname, errno);
264 }
265
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 if (cap_state == CAP_SET) {
267 cap_free(caps_proc);
268 return true;
269 }
270
271
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state);
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 if (retval != 0) {
273 cap_free(caps_proc);
274 if (avoid_mutexes)
275 abort();
276 PANIC(kLogSyslogErr | kLogDebug, "Cannot inspect %s capability (errno: %d)",
277 capname, errno);
278 }
279
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 if (cap_state != CAP_SET) {
280
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 if (!avoid_mutexes) {
281
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 LogCvmfs(kLogCvmfs, kLogDebug,
282 "Warning: %s cannot be obtained. "
283 "It's not in the process's permitted set.",
284 capname);
285 }
286
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 cap_free(caps_proc);
287 37 return false;
288 }
289
290 retval = cap_set_flag(caps_proc, CAP_EFFECTIVE, 1, &cap, CAP_SET);
291 if (retval != 0) {
292 cap_free(caps_proc);
293 if (avoid_mutexes)
294 abort();
295 PANIC(kLogSyslogErr | kLogDebug, "Cannot enable %s capability (errno: %d)",
296 capname, errno);
297 }
298
299 retval = cap_set_proc(caps_proc);
300 cap_free(caps_proc);
301
302 if (retval != 0) {
303 if (!avoid_mutexes) {
304 LogCvmfs(kLogCvmfs, kLogSyslogErr | kLogDebug,
305 "Cannot set %s capability for current process (errno: %d)",
306 capname, errno);
307 }
308 return false;
309 }
310
311 return true;
312 }
313
314 bool DropCapability(const cap_value_t cap,
315 const char *capname,
316 const bool avoid_mutexes = false) {
317 #ifdef CAP_IS_SUPPORTED
318 if (!CAP_IS_SUPPORTED(cap)) {
319 if (avoid_mutexes)
320 abort();
321 PANIC(kLogSyslogErr | kLogDebug, "Capability %s is not supported", capname);
322 }
323 #endif
324
325 cap_t caps_proc = cap_get_proc();
326 if (caps_proc == NULL) {
327 if (avoid_mutexes)
328 abort();
329 PANIC(kLogSyslogErr | kLogDebug,
330 "Cannot get process capabilities (errno: %d)", errno);
331 }
332
333 cap_flag_value_t cap_state;
334 int retval = cap_get_flag(caps_proc, cap, CAP_EFFECTIVE, &cap_state);
335 if (retval != 0) {
336 cap_free(caps_proc);
337 if (avoid_mutexes)
338 abort();
339 PANIC(kLogSyslogErr | kLogDebug, "Cannot inspect %s capability (errno: %d)",
340 capname, errno);
341 }
342
343 if (cap_state == CAP_CLEAR) {
344 cap_free(caps_proc);
345 return true;
346 }
347
348 retval = cap_set_flag(caps_proc, CAP_EFFECTIVE, 1, &cap, CAP_CLEAR);
349 if (retval != 0) {
350 cap_free(caps_proc);
351 if (avoid_mutexes)
352 abort();
353 PANIC(kLogSyslogErr | kLogDebug, "Cannot disable %s capability (errno: %d)",
354 capname, errno);
355 }
356
357 retval = cap_set_proc(caps_proc);
358 cap_free(caps_proc);
359
360 if (retval != 0) {
361 if (!avoid_mutexes) {
362 LogCvmfs(kLogCvmfs, kLogStderr | kLogDebug,
363 "Cannot reset %s capability for current process (errno: %d)",
364 capname, errno);
365 }
366 return false;
367 }
368
369 return true;
370 }
371
372 81 bool CheckCapabilityPermitted(const cap_value_t cap) {
373
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
81 cap_t caps_proc = cap_get_proc();
374
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 81 times.
81 if (caps_proc == NULL)
375 PANIC(kLogSyslogErr | kLogDebug,
376 "Cannot get process capabilities (errno: %d)", errno);
377 cap_flag_value_t cap_state;
378
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
81 const int retval = cap_get_flag(caps_proc,
379 cap,
380 CAP_PERMITTED,
381 &cap_state);
382
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
81 cap_free(caps_proc);
383
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 81 times.
81 if (retval != 0)
384 PANIC(kLogSyslogErr | kLogDebug,
385 "Cannot inspect permitted capability 0x%x (errno: %d)", cap,
386 errno);
387 81 return (cap_state == CAP_SET);
388 }
389
390 } // namespace
391
392 #endif // __APPLE__
393
394 bool ObtainDacReadSearchCapability() {
395 return ObtainCapability(CAP_DAC_READ_SEARCH, "CAP_DAC_READ_SEARCH");
396 }
397
398 bool DropDacReadSearchCapability() {
399 return DropCapability(CAP_DAC_READ_SEARCH, "CAP_DAC_READ_SEARCH");
400 }
401
402 bool ObtainSysAdminCapability() {
403 return ObtainCapability(CAP_SYS_ADMIN, "CAP_SYS_ADMIN");
404 }
405
406 bool ObtainSysPtraceCapability() {
407 return ObtainCapability(CAP_SYS_PTRACE, "CAP_SYS_PTRACE");
408 }
409
410 bool DropSysPtraceCapability() {
411 return DropCapability(CAP_SYS_PTRACE, "CAP_SYS_PTRACE");
412 }
413
414 37 bool ObtainSetuidgidCapabilities(const bool avoid_mutexes) {
415
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
37 return (ObtainCapability(CAP_SETUID, "CAP_SETUID", avoid_mutexes) &&
416
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
37 ObtainCapability(CAP_SETGID, "CAP_SETGID", avoid_mutexes));
417 }
418
419 bool ObtainSetpcapCapability() {
420 return (ObtainCapability(CAP_SETPCAP, "CAP_SETPCAP"));
421 }
422
423 81 bool SetuidCapabilityPermitted() {
424 81 return (CheckCapabilityPermitted(CAP_SETUID));
425 }
426
427 bool SetpcapCapabilityPermitted() {
428 return (CheckCapabilityPermitted(CAP_SETPCAP));
429 }
430
431 #ifdef CVMFS_NAMESPACE_GUARD
432 } // namespace CVMFS_NAMESPACE_GUARD
433 #endif
434