GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_session.cc
Date: 2026-08-30 02:40:36
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 2125 AuthzSessionManager::AuthzSessionManager()
29 2125 : deadline_sweep_pids_(0)
30 2125 , deadline_sweep_creds_(0)
31 2125 , authz_fetcher_(NULL)
32 2125 , no_pid_(NULL)
33 2125 , no_session_(NULL)
34 2125 , n_fetch_(NULL)
35 2125 , n_grant_(NULL)
36
1/2
✓ Branch 3 taken 2125 times.
✗ Branch 4 not taken.
2125 , n_deny_(NULL) {
37 2125 int retval = pthread_mutex_init(&lock_pid2session_, NULL);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125 assert(retval == 0);
39 2125 retval = pthread_mutex_init(&lock_session2cred_, NULL);
40
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125 assert(retval == 0);
41
42
1/2
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
2125 session2cred_.Init(16, SessionKey(), HashSessionKey);
43
1/2
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
2125 pid2session_.Init(16, PidKey(), HashPidKey);
44 2125 }
45
46
47 2125 AuthzSessionManager::~AuthzSessionManager() {
48 2125 int retval = pthread_mutex_destroy(&lock_pid2session_);
49
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125 assert(retval == 0);
50 2125 retval = pthread_mutex_destroy(&lock_session2cred_);
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125 assert(retval == 0);
52
53 2125 const SessionKey empty_key;
54
2/2
✓ Branch 1 taken 44625 times.
✓ Branch 2 taken 2125 times.
46750 for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
55
2/2
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 44445 times.
44625 if (session2cred_.keys()[i] != empty_key) {
56
2/2
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 135 times.
180 if ((session2cred_.values() + i)->token.data != NULL)
57 45 free((session2cred_.values() + i)->token.data);
58 }
59 }
60 2125 }
61
62
63 45 void AuthzSessionManager::ClearSessionCache() {
64 45 const MutexLockGuard m(&lock_session2cred_);
65
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 session2cred_.Clear();
66 45 no_session_->Set(0);
67 45 }
68
69
70 2125 AuthzSessionManager *AuthzSessionManager::Create(AuthzFetcher *authz_fetcher,
71 perf::Statistics *statistics) {
72
1/2
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
2125 AuthzSessionManager *authz_mgr = new AuthzSessionManager();
73 2125 authz_mgr->authz_fetcher_ = authz_fetcher;
74
75
3/6
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2125 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2125 times.
✗ Branch 10 not taken.
2125 authz_mgr->no_pid_ = statistics->Register("authz.no_pid", "cached pids");
76
3/6
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2125 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2125 times.
✗ Branch 10 not taken.
2125 authz_mgr->no_session_ = statistics->Register("authz.no_session",
77 "cached sessions");
78
3/6
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2125 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2125 times.
✗ Branch 10 not taken.
2125 authz_mgr->n_fetch_ = statistics->Register(
79 "authz.n_fetch", "overall number of authz helper invocations");
80
3/6
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2125 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2125 times.
✗ Branch 10 not taken.
2125 authz_mgr->n_grant_ = statistics->Register(
81 "authz.n_grant", "overall number of granted membership queries");
82
3/6
✓ Branch 2 taken 2125 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2125 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2125 times.
✗ Branch 10 not taken.
2125 authz_mgr->n_deny_ = statistics->Register(
83 "authz.n_deny", "overall number of denied membership queries");
84
85 2125 return authz_mgr;
86 }
87
88
89 /**
90 * Gathers SID, birthday, uid, and gid from given PID.
91 */
92 855 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 855 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 855 times.
855 if (snprintf(pid_path, kMaxProcPath, "/proc/%d/stat", pid) >= kMaxProcPath) {
125 return false;
126 }
127
128
1/2
✓ Branch 1 taken 855 times.
✗ Branch 2 not taken.
855 FILE *fp_stat = fopen(pid_path, "r");
129
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 675 times.
855 if (fp_stat == NULL) {
130 360 LogCvmfs(kLogAuthz, kLogDebug,
131 "Failed to open status file /proc/%d/stat: (errno=%d) %s", pid,
132
1/2
✓ Branch 2 taken 180 times.
✗ Branch 3 not taken.
180 errno, strerror(errno));
133
1/2
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
180 LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
134 "Authorization for session %d disappeared", pid);
135 180 return false;
136 }
137
138 // The uid and gid can be gathered from /proc/$PID/stat file ownership
139 675 const int fd_stat = fileno(fp_stat);
140 platform_stat64 info;
141 675 retval = platform_fstat(fd_stat, &info);
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 675 times.
675 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 675 pid_key->uid = info.st_uid;
149 675 pid_key->gid = info.st_gid;
150
151 // TODO(bbockelm): EINTR handling
152
1/2
✓ Branch 1 taken 675 times.
✗ Branch 2 not taken.
675 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 675 times.
✗ Branch 2 not taken.
675 fclose(fp_stat);
157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 675 times.
675 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 675 pid_key->pid = pid;
169 675 return true;
170 }
171
172
173 /**
174 * Caller is responsible for freeing the returned token.
175 */
176 135 AuthzToken *AuthzSessionManager::GetTokenCopy(const pid_t pid,
177 const std::string &membership) {
178 135 SessionKey session_key;
179 135 PidKey pid_key;
180
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 const bool retval = LookupSessionKey(pid, &pid_key, &session_key);
181
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 90 times.
135 if (!retval)
182 45 return NULL;
183
184 90 AuthzData authz_data;
185
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 const bool granted = LookupAuthzData(pid_key, session_key, membership,
186 &authz_data);
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
90 if (!granted)
188 return NULL;
189
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 return authz_data.token.DeepCopy();
190 90 }
191
192
193 315 bool AuthzSessionManager::IsMemberOf(const pid_t pid,
194 const std::string &membership) {
195 315 SessionKey session_key;
196 315 PidKey pid_key;
197
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
315 const bool retval = LookupSessionKey(pid, &pid_key, &session_key);
198
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 270 times.
315 if (!retval)
199 45 return false;
200
201 270 AuthzData authz_data;
202
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 return LookupAuthzData(pid_key, session_key, membership, &authz_data);
203 270 }
204
205
206 /**
207 * Calls out to the AuthzFetcher if the data is not cached. Verifies the
208 * membership.
209 */
210 630 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 630 times.
630 assert(authz_data != NULL);
215
216 bool found;
217 {
218 630 const MutexLockGuard m(&lock_session2cred_);
219
1/2
✓ Branch 1 taken 630 times.
✗ Branch 2 not taken.
630 MaySweepCreds();
220
1/2
✓ Branch 1 taken 630 times.
✗ Branch 2 not taken.
630 found = session2cred_.Lookup(session_key, authz_data);
221 630 }
222
2/2
✓ Branch 0 taken 315 times.
✓ Branch 1 taken 315 times.
630 if (found) {
223 630 LogCvmfs(kLogAuthz, kLogDebug,
224 "cached authz data for sid %d, membership %s, status %d",
225
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
315 session_key.sid, authz_data->membership.c_str(),
226 315 authz_data->status);
227 315 const bool granted = authz_data->IsGranted(membership);
228
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 225 times.
315 if (granted)
229 90 perf::Inc(n_grant_);
230 else
231 225 perf::Inc(n_deny_);
232 315 return granted;
233 }
234
235 // Not found in cache, ask for help
236 315 perf::Inc(n_fetch_);
237 unsigned ttl;
238
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
315 authz_data->status = authz_fetcher_->Fetch(
239
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
630 AuthzFetcher::QueryInfo(pid_key.pid, pid_key.uid, pid_key.gid,
240 membership),
241 &(authz_data->token), &ttl);
242 315 authz_data->deadline = platform_monotonic_time() + ttl;
243
2/2
✓ Branch 0 taken 270 times.
✓ Branch 1 taken 45 times.
315 if (authz_data->status == kAuthzOk)
244
1/2
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
270 authz_data->membership = membership;
245 630 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 315 times.
✗ Branch 2 not taken.
315 session_key.sid, pid_key.pid, authz_data->membership.c_str(),
249 315 authz_data->status, ttl);
250
251 {
252 315 const MutexLockGuard m(&lock_session2cred_);
253
2/4
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 315 times.
✗ Branch 4 not taken.
315 if (!session2cred_.Contains(session_key))
254 315 perf::Inc(no_session_);
255
1/2
✓ Branch 1 taken 315 times.
✗ Branch 2 not taken.
315 session2cred_.Insert(session_key, *authz_data);
256 315 }
257 315 const bool granted = authz_data->status == kAuthzOk;
258
2/2
✓ Branch 0 taken 270 times.
✓ Branch 1 taken 45 times.
315 if (granted)
259 270 perf::Inc(n_grant_);
260 else
261 45 perf::Inc(n_deny_);
262 315 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 540 bool AuthzSessionManager::LookupSessionKey(pid_t pid,
272 PidKey *pid_key,
273 SessionKey *session_key) {
274
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 540 times.
540 assert(pid_key != NULL);
275
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 540 times.
540 assert(session_key != NULL);
276
3/4
✓ Branch 1 taken 540 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 135 times.
✓ Branch 4 taken 405 times.
540 if (!GetPidInfo(pid, pid_key))
277 135 return false;
278
279 bool found;
280 {
281 405 const MutexLockGuard m(&lock_pid2session_);
282
1/2
✓ Branch 1 taken 405 times.
✗ Branch 2 not taken.
405 found = pid2session_.Lookup(*pid_key, session_key);
283
1/2
✓ Branch 1 taken 405 times.
✗ Branch 2 not taken.
405 MaySweepPids();
284 405 }
285
2/2
✓ Branch 0 taken 225 times.
✓ Branch 1 taken 180 times.
405 if (found) {
286
1/2
✓ Branch 1 taken 225 times.
✗ Branch 2 not taken.
225 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 225 return true;
291 }
292
293
1/2
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
180 LogCvmfs(kLogAuthz, kLogDebug,
294 "Session key not found in cache, getting information from OS");
295 180 PidKey sid_key;
296 180 pid_t sid = pid_key->sid;
297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 180 times.
180 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 180 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 180 times.
180 if (!GetPidInfo(sid, &sid_key))
303 return false;
304
305 180 session_key->sid = sid_key.pid;
306 180 session_key->sid_bday = sid_key.pid_bday;
307 {
308 180 const MutexLockGuard m(&lock_pid2session_);
309 180 pid_key->deadline = platform_monotonic_time() + kPidLifetime;
310
2/4
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 180 times.
✗ Branch 4 not taken.
180 if (!pid2session_.Contains(*pid_key))
311 180 perf::Inc(no_pid_);
312
1/2
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
180 pid2session_.Insert(*pid_key, *session_key);
313 180 }
314
315
1/2
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
180 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 180 return true;
319 }
320
321
322 /**
323 * Scan through old sessions only every so often.
324 */
325 630 void AuthzSessionManager::MaySweepCreds() {
326 630 const uint64_t now = platform_monotonic_time();
327
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 450 times.
630 if (now >= deadline_sweep_creds_) {
328 180 SweepCreds(now);
329 180 deadline_sweep_creds_ = now + kSweepInterval;
330 }
331 630 }
332
333
334 /**
335 * Scan through old PIDs only every so often.
336 */
337 405 void AuthzSessionManager::MaySweepPids() {
338 405 const uint64_t now = platform_monotonic_time();
339
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 225 times.
405 if (now >= deadline_sweep_pids_) {
340 180 SweepPids(now);
341 180 deadline_sweep_pids_ = now + kSweepInterval;
342 }
343 405 }
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 225 void AuthzSessionManager::SweepCreds(uint64_t now) {
351 225 const SessionKey empty_key;
352 225 vector<SessionKey> trash_bin;
353
2/2
✓ Branch 1 taken 4725 times.
✓ Branch 2 taken 225 times.
4950 for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
354 4725 const SessionKey this_key = session2cred_.keys()[i];
355
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 4635 times.
4725 if (this_key != empty_key) {
356
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 if (now >= (session2cred_.values() + i)->deadline)
357
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 trash_bin.push_back(this_key);
358 }
359 }
360
361
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 225 times.
315 for (unsigned i = 0; i < trash_bin.size(); ++i) {
362
1/2
✓ Branch 2 taken 90 times.
✗ Branch 3 not taken.
90 session2cred_.Erase(trash_bin[i]);
363 90 perf::Dec(no_session_);
364 }
365 225 }
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 270 void AuthzSessionManager::SweepPids(uint64_t now) {
373 270 const PidKey empty_key;
374 270 vector<PidKey> trash_bin;
375
2/2
✓ Branch 1 taken 5670 times.
✓ Branch 2 taken 270 times.
5940 for (unsigned i = 0; i < pid2session_.capacity(); ++i) {
376 5670 const PidKey this_key = pid2session_.keys()[i];
377
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 5580 times.
5670 if (this_key != empty_key) {
378
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 45 times.
90 if (now >= this_key.deadline)
379
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 trash_bin.push_back(this_key);
380 }
381 }
382
383
2/2
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 270 times.
315 for (unsigned i = 0; i < trash_bin.size(); ++i) {
384
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 pid2session_.Erase(trash_bin[i]);
385 45 perf::Dec(no_pid_);
386 }
387 270 }
388