GCC Code Coverage Report


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