GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_session.cc
Date: 2026-05-10 02:36:07
Exec Total Coverage
Lines: 177 189 93.7%
Branches: 110 186 59.1%

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