GCC Code Coverage Report


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