GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/capabilities.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 26 131 19.8%
Branches: 19 156 12.2%

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