GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/capabilities.cc
Date: 2026-10-04 02:40:33
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 26 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 26 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
26 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 26 times.
✗ Branch 2 not taken.
26 cap_t caps_proc = cap_get_proc();
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 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 26 times.
✗ Branch 2 not taken.
26 int retval = cap_get_flag(caps_proc, cap, CAP_EFFECTIVE, &cap_state);
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 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 26 times.
26 if (cap_state == CAP_SET) {
267 ✗ cap_free(caps_proc);
268 ✗ return true;
269 }
270
271
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 retval = cap_get_flag(caps_proc, cap, CAP_PERMITTED, &cap_state);
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 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 26 times.
✗ Branch 1 not taken.
26 if (cap_state != CAP_SET) {
280
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (!avoid_mutexes) {
281
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 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 26 times.
✗ Branch 2 not taken.
26 cap_free(caps_proc);
287 26 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 26 bool ObtainSetuidgidCapabilities(const bool avoid_mutexes) {
415
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
26 return (ObtainCapability(CAP_SETUID, "CAP_SETUID", avoid_mutexes) &&
416
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
26 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