GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/capabilities.cc
Date: 2026-03-15 02:35:27
Exec Total Coverage
Lines: 26 108 24.1%
Branches: 19 124 15.3%

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