GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_session.cc
Date: 2024-04-21 02:33:16
Exec Total Coverage
Lines: 175 188 93.1%
Branches: 110 186 59.1%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #define __STDC_FORMAT_MACROS
6 #include "authz_session.h"
7
8 #include <errno.h>
9 #include <inttypes.h>
10 #ifdef __APPLE__
11 #include <sys/sysctl.h>
12 #endif
13
14 #include <cassert>
15 #include <cstdio>
16 #include <cstring>
17 #include <vector>
18
19 #include "authz/authz_fetch.h"
20 #include "statistics.h"
21 #include "util/concurrency.h"
22 #include "util/logging.h"
23 #include "util/platform.h"
24 #include "util/posix.h"
25
26 using namespace std; // NOLINT
27
28
29 46 AuthzSessionManager::AuthzSessionManager()
30 46 : deadline_sweep_pids_(0)
31 46 , deadline_sweep_creds_(0)
32 46 , authz_fetcher_(NULL)
33 46 , no_pid_(NULL)
34 46 , no_session_(NULL)
35 46 , n_fetch_(NULL)
36 46 , n_grant_(NULL)
37
1/2
✓ Branch 3 taken 46 times.
✗ Branch 4 not taken.
46 , n_deny_(NULL)
38 {
39 46 int retval = pthread_mutex_init(&lock_pid2session_, NULL);
40
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(retval == 0);
41 46 retval = pthread_mutex_init(&lock_session2cred_, NULL);
42
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(retval == 0);
43
44
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 session2cred_.Init(16, SessionKey(), HashSessionKey);
45
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 pid2session_.Init(16, PidKey(), HashPidKey);
46 46 }
47
48
49 46 AuthzSessionManager::~AuthzSessionManager() {
50 46 int retval = pthread_mutex_destroy(&lock_pid2session_);
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(retval == 0);
52 46 retval = pthread_mutex_destroy(&lock_session2cred_);
53
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(retval == 0);
54
55 46 SessionKey empty_key;
56
2/2
✓ Branch 1 taken 966 times.
✓ Branch 2 taken 46 times.
1012 for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
57
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 962 times.
966 if (session2cred_.keys()[i] != empty_key) {
58
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
4 if ((session2cred_.values() + i)->token.data != NULL)
59 1 free((session2cred_.values() + i)->token.data);
60 }
61 }
62 46 }
63
64
65 1 void AuthzSessionManager::ClearSessionCache() {
66 1 MutexLockGuard m(&lock_session2cred_);
67
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 session2cred_.Clear();
68 1 no_session_->Set(0);
69 1 }
70
71
72 46 AuthzSessionManager *AuthzSessionManager::Create(
73 AuthzFetcher *authz_fetcher,
74 perf::Statistics *statistics)
75 {
76
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 AuthzSessionManager *authz_mgr = new AuthzSessionManager();
77 46 authz_mgr->authz_fetcher_ = authz_fetcher;
78
79
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 46 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 46 times.
✗ Branch 10 not taken.
46 authz_mgr->no_pid_ = statistics->Register("authz.no_pid", "cached pids");
80
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 46 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 46 times.
✗ Branch 10 not taken.
46 authz_mgr->no_session_ = statistics->Register(
81 "authz.no_session", "cached sessions");
82
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 46 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 46 times.
✗ Branch 10 not taken.
46 authz_mgr->n_fetch_ = statistics->Register(
83 "authz.n_fetch", "overall number of authz helper invocations");
84
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 46 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 46 times.
✗ Branch 10 not taken.
46 authz_mgr->n_grant_ = statistics->Register(
85 "authz.n_grant", "overall number of granted membership queries");
86
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 46 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 46 times.
✗ Branch 10 not taken.
46 authz_mgr->n_deny_ = statistics->Register(
87 "authz.n_deny", "overall number of denied membership queries");
88
89 46 return authz_mgr;
90 }
91
92
93 /**
94 * Gathers SID, birthday, uid, and gid from given PID.
95 */
96 19 bool AuthzSessionManager::GetPidInfo(pid_t pid, PidKey *pid_key) {
97 int retval;
98
99 // TODO(jblomer): better in platform.h? Maybe a bit too bulky for that?
100 #ifdef __APPLE__
101 pid_key->sid = getsid(pid);
102 if (pid_key->sid == static_cast<pid_t>(-1)) {
103 LogCvmfs(kLogAuthz, kLogDebug, "failed to get sid (%s)", strerror(errno));
104 return false;
105 }
106
107 int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
108 struct kinfo_proc kp;
109 size_t len = sizeof(kp);
110 retval = sysctl(mib, 4, &kp, &len, NULL, 0);
111 if (retval == -1) {
112 LogCvmfs(kLogAuthz, kLogDebug, "failed to get pid info (%s)",
113 strerror(errno));
114 return false;
115 }
116 pid_key->uid = kp.kp_eproc.e_pcred.p_ruid;
117 pid_key->gid = kp.kp_eproc.e_pcred.p_rgid;
118 int64_t usec =
119 static_cast<int64_t>(kp.kp_proc.p_un.__p_starttime.tv_sec) * 1000000;
120 usec += static_cast<int64_t>(kp.kp_proc.p_un.__p_starttime.tv_usec);
121 pid_key->pid_bday = usec;
122 pid_key->pid = pid;
123 return true;
124 #endif
125
126 19 const int kMaxProcPath = 64; // Enough to store /proc/PID/stat
127 char pid_path[kMaxProcPath];
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (snprintf(pid_path, kMaxProcPath, "/proc/%d/stat", pid) >= kMaxProcPath) {
129 return false;
130 }
131
132
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 FILE *fp_stat = fopen(pid_path, "r");
133
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 15 times.
19 if (fp_stat == NULL) {
134 8 LogCvmfs(kLogAuthz, kLogDebug,
135 "Failed to open status file /proc/%d/stat: (errno=%d) %s",
136
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 pid, errno, strerror(errno));
137
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
138 "Authorization for session %d disappeared", pid);
139 4 return false;
140 }
141
142 // The uid and gid can be gathered from /proc/$PID/stat file ownership
143 15 int fd_stat = fileno(fp_stat);
144 platform_stat64 info;
145 15 retval = platform_fstat(fd_stat, &info);
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (retval != 0) {
147 fclose(fp_stat);
148 LogCvmfs(kLogAuthz, kLogDebug,
149 "Failed to get stat information of running process.");
150 return false;
151 }
152 15 pid_key->uid = info.st_uid;
153 15 pid_key->gid = info.st_gid;
154
155 // TODO(bbockelm): EINTR handling
156
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 retval = fscanf(fp_stat, "%*d %*s %*c %*d %*d %d %*d %*d %*u %*u %*u %*u "
157 "%*u %*u %*u %*d %*d %*d %*d %*d %*d %" SCNu64,
158 &(pid_key->sid), &(pid_key->pid_bday));
159
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 fclose(fp_stat);
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (retval != 2) {
161 if (errno == 0) {
162 errno = EINVAL;
163 }
164 LogCvmfs(kLogAuthz, kLogDebug, "Failed to parse status file for "
165 "pid %d: (errno=%d) %s, fscanf result %d", pid, errno,
166 strerror(errno), retval);
167 return false;
168 }
169
170 15 pid_key->pid = pid;
171 15 return true;
172 }
173
174
175 /**
176 * Caller is responsible for freeing the returned token.
177 */
178 3 AuthzToken *AuthzSessionManager::GetTokenCopy(
179 const pid_t pid,
180 const std::string &membership)
181 {
182 3 SessionKey session_key;
183 3 PidKey pid_key;
184
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 bool retval = LookupSessionKey(pid, &pid_key, &session_key);
185
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (!retval)
186 1 return NULL;
187
188 2 AuthzData authz_data;
189 const bool granted =
190
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 LookupAuthzData(pid_key, session_key, membership, &authz_data);
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!granted)
192 return NULL;
193
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 return authz_data.token.DeepCopy();
194 2 }
195
196
197 7 bool AuthzSessionManager::IsMemberOf(
198 const pid_t pid,
199 const std::string &membership)
200 {
201 7 SessionKey session_key;
202 7 PidKey pid_key;
203
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 bool retval = LookupSessionKey(pid, &pid_key, &session_key);
204
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (!retval)
205 1 return false;
206
207 6 AuthzData authz_data;
208
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 return LookupAuthzData(pid_key, session_key, membership, &authz_data);
209 6 }
210
211
212 /**
213 * Calls out to the AuthzFetcher if the data is not cached. Verifies the
214 * membership.
215 */
216 14 bool AuthzSessionManager::LookupAuthzData(
217 const PidKey &pid_key,
218 const SessionKey &session_key,
219 const std::string &membership,
220 AuthzData *authz_data)
221 {
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 assert(authz_data != NULL);
223
224 bool found;
225 {
226 14 MutexLockGuard m(&lock_session2cred_);
227
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 MaySweepCreds();
228
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 found = session2cred_.Lookup(session_key, authz_data);
229 14 }
230
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 if (found) {
231 14 LogCvmfs(kLogAuthz, kLogDebug,
232 "cached authz data for sid %d, membership %s, status %d",
233
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 session_key.sid, authz_data->membership.c_str(),
234 7 authz_data->status);
235 7 const bool granted = authz_data->IsGranted(membership);
236
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (granted)
237 2 perf::Inc(n_grant_);
238 else
239 5 perf::Inc(n_deny_);
240 7 return granted;
241 }
242
243 // Not found in cache, ask for help
244 7 perf::Inc(n_fetch_);
245 unsigned ttl;
246
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 authz_data->status = authz_fetcher_->Fetch(
247
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
14 AuthzFetcher::QueryInfo(pid_key.pid, pid_key.uid, pid_key.gid, membership),
248 &(authz_data->token), &ttl);
249 7 authz_data->deadline = platform_monotonic_time() + ttl;
250
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 if (authz_data->status == kAuthzOk)
251
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 authz_data->membership = membership;
252 14 LogCvmfs(kLogAuthz, kLogDebug,
253 "fetched authz data for sid %d (pid %d), membership %s, status %d, "
254
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 "ttl %u", session_key.sid, pid_key.pid,
255 7 authz_data->membership.c_str(), authz_data->status, ttl);
256
257 {
258 7 MutexLockGuard m(&lock_session2cred_);
259
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
7 if (!session2cred_.Contains(session_key)) perf::Inc(no_session_);
260
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 session2cred_.Insert(session_key, *authz_data);
261 7 }
262 7 const bool granted = authz_data->status == kAuthzOk;
263
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 if (granted)
264 6 perf::Inc(n_grant_);
265 else
266 1 perf::Inc(n_deny_);
267 7 return granted;
268 }
269
270
271 /**
272 * Translate a PID and its birthday into an SID and its birthday. The Session
273 * ID and its birthday together with UID and GID make the Session Key. The
274 * translation result is cached in pid2session_.
275 */
276 12 bool AuthzSessionManager::LookupSessionKey(
277 pid_t pid,
278 PidKey *pid_key,
279 SessionKey *session_key)
280 {
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 assert(pid_key != NULL);
282
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 assert(session_key != NULL);
283
3/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 9 times.
12 if (!GetPidInfo(pid, pid_key))
284 3 return false;
285
286 bool found;
287 {
288 9 MutexLockGuard m(&lock_pid2session_);
289
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 found = pid2session_.Lookup(*pid_key, session_key);
290
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 MaySweepPids();
291 9 }
292
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 4 times.
9 if (found) {
293
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 LogCvmfs(kLogAuthz, kLogDebug,
294 "Session key %d/%" PRIu64 " in cache; sid=%d, bday=%" PRIu64,
295 pid_key->pid, pid_key->pid_bday,
296 session_key->sid, session_key->sid_bday);
297 5 return true;
298 }
299
300
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 LogCvmfs(kLogAuthz, kLogDebug,
301 "Session key not found in cache, getting information from OS");
302 4 PidKey sid_key;
303 4 pid_t sid = pid_key->sid;
304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (sid == 0) {
305 // This can happen inside process namespaces such as those used by
306 // singularity and cvmfsexec. Use init process id instead.
307 sid = 1;
308 }
309
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if (!GetPidInfo(sid, &sid_key))
310 return false;
311
312 4 session_key->sid = sid_key.pid;
313 4 session_key->sid_bday = sid_key.pid_bday;
314 {
315 4 MutexLockGuard m(&lock_pid2session_);
316 4 pid_key->deadline = platform_monotonic_time() + kPidLifetime;
317
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 if (!pid2session_.Contains(*pid_key)) perf::Inc(no_pid_);
318
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 pid2session_.Insert(*pid_key, *session_key);
319 4 }
320
321
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 LogCvmfs(kLogAuthz, kLogDebug, "Lookup key %d/%" PRIu64 "; sid=%d, bday=%lu",
322 pid_key->pid, pid_key->pid_bday,
323 session_key->sid, session_key->sid_bday);
324 4 return true;
325 }
326
327
328 /**
329 * Scan through old sessions only every so often.
330 */
331 14 void AuthzSessionManager::MaySweepCreds() {
332 14 uint64_t now = platform_monotonic_time();
333
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 if (now >= deadline_sweep_creds_) {
334 4 SweepCreds(now);
335 4 deadline_sweep_creds_ = now + kSweepInterval;
336 }
337 14 }
338
339
340 /**
341 * Scan through old PIDs only every so often.
342 */
343 9 void AuthzSessionManager::MaySweepPids() {
344 9 uint64_t now = platform_monotonic_time();
345
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if (now >= deadline_sweep_pids_) {
346 4 SweepPids(now);
347 4 deadline_sweep_pids_ = now + kSweepInterval;
348 }
349 9 }
350
351
352 /**
353 * Remove cache PIDs with expired cache life time.
354 * TODO(jblomer): a generalized sweeping can become part of smallhash
355 */
356 5 void AuthzSessionManager::SweepCreds(uint64_t now) {
357 5 SessionKey empty_key;
358 5 vector<SessionKey> trash_bin;
359
2/2
✓ Branch 1 taken 105 times.
✓ Branch 2 taken 5 times.
110 for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
360 105 SessionKey this_key = session2cred_.keys()[i];
361
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 103 times.
105 if (this_key != empty_key) {
362
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (now >= (session2cred_.values() + i)->deadline)
363
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 trash_bin.push_back(this_key);
364 }
365 }
366
367
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 5 times.
7 for (unsigned i = 0; i < trash_bin.size(); ++i) {
368
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 session2cred_.Erase(trash_bin[i]);
369 2 perf::Dec(no_session_);
370 }
371 5 }
372
373
374 /**
375 * Remove cache PIDs with expired cache life time.
376 * TODO(jblomer): a generalized sweeping can become part of smallhash
377 */
378 6 void AuthzSessionManager::SweepPids(uint64_t now) {
379 6 PidKey empty_key;
380 6 vector<PidKey> trash_bin;
381
2/2
✓ Branch 1 taken 126 times.
✓ Branch 2 taken 6 times.
132 for (unsigned i = 0; i < pid2session_.capacity(); ++i) {
382 126 PidKey this_key = pid2session_.keys()[i];
383
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 124 times.
126 if (this_key != empty_key) {
384
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (now >= this_key.deadline)
385
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 trash_bin.push_back(this_key);
386 }
387 }
388
389
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
7 for (unsigned i = 0; i < trash_bin.size(); ++i) {
390
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 pid2session_.Erase(trash_bin[i]);
391 1 perf::Dec(no_pid_);
392 }
393 6 }
394