GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/authz/authz_session.cc Lines: 159 169 94.1 %
Date: 2019-02-03 02:48:13 Branches: 59 78 75.6 %

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 "logging.h"
21
#include "platform.h"
22
#include "statistics.h"
23
#include "util/posix.h"
24
25
using namespace std;  // NOLINT
26
27
28
85
AuthzSessionManager::AuthzSessionManager()
29
  : deadline_sweep_pids_(0)
30
  , deadline_sweep_creds_(0)
31
  , authz_fetcher_(NULL)
32
  , no_pid_(NULL)
33
  , no_session_(NULL)
34
  , n_fetch_(NULL)
35
  , n_grant_(NULL)
36
85
  , n_deny_(NULL)
37
{
38
85
  int retval = pthread_mutex_init(&lock_pid2session_, NULL);
39
85
  assert(retval == 0);
40
85
  retval = pthread_mutex_init(&lock_session2cred_, NULL);
41
85
  assert(retval == 0);
42
43
85
  session2cred_.Init(16, SessionKey(), HashSessionKey);
44
85
  pid2session_.Init(16, PidKey(), HashPidKey);
45
}
46
47
48
85
AuthzSessionManager::~AuthzSessionManager() {
49
85
  int retval = pthread_mutex_destroy(&lock_pid2session_);
50
85
  assert(retval == 0);
51
85
  retval = pthread_mutex_destroy(&lock_session2cred_);
52
85
  assert(retval == 0);
53
54
85
  SessionKey empty_key;
55
1870
  for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
56
1785
    if (session2cred_.keys()[i] != empty_key) {
57
16
      if ((session2cred_.values() + i)->token.data != NULL)
58
4
        free((session2cred_.values() + i)->token.data);
59
    }
60
  }
61
}
62
63
64
4
void AuthzSessionManager::ClearSessionCache() {
65
4
  LockMutex(&lock_session2cred_);
66
4
  session2cred_.Clear();
67
4
  no_session_->Set(0);
68
4
  UnlockMutex(&lock_session2cred_);
69
4
}
70
71
72
85
AuthzSessionManager *AuthzSessionManager::Create(
73
  AuthzFetcher *authz_fetcher,
74
  perf::Statistics *statistics)
75
{
76
85
  AuthzSessionManager *authz_mgr = new AuthzSessionManager();
77
85
  authz_mgr->authz_fetcher_ = authz_fetcher;
78
79
85
  authz_mgr->no_pid_ = statistics->Register("authz.no_pid", "cached pids");
80
  authz_mgr->no_session_ = statistics->Register(
81
85
    "authz.no_session", "cached sessions");
82
  authz_mgr->n_fetch_ = statistics->Register(
83
85
    "authz.n_fetch", "overall number of authz helper invocations");
84
  authz_mgr->n_grant_ = statistics->Register(
85
85
    "authz.n_grant", "overall number of granted membership queries");
86
  authz_mgr->n_deny_ = statistics->Register(
87
85
    "authz.n_deny", "overall number of denied membership queries");
88
89
85
  return authz_mgr;
90
}
91
92
93
/**
94
 * Gathers SID, birthday, uid, and gid from given PID.
95
 */
96
76
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
76
  const int kMaxProcPath = 64;  // Enough to store /proc/PID/stat
127
  char pid_path[kMaxProcPath];
128
76
  if (snprintf(pid_path, kMaxProcPath, "/proc/%d/stat", pid) >= kMaxProcPath) {
129
    return false;
130
  }
131
132
76
  FILE *fp_stat = fopen(pid_path, "r");
133
76
  if (fp_stat == NULL) {
134
    LogCvmfs(kLogAuthz, kLogDebug,
135
             "Failed to open status file /proc/%d/stat: (errno=%d) %s",
136
16
             pid, errno, strerror(errno));
137
    LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
138
16
             "Authorization for session %d disappeared", pid);
139
16
    return false;
140
  }
141
142
  // The uid and gid can be gathered from /proc/$PID/stat file ownership
143
60
  int fd_stat = fileno(fp_stat);
144
  platform_stat64 info;
145
60
  retval = platform_fstat(fd_stat, &info);
146
60
  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
60
  pid_key->uid = info.st_uid;
153
60
  pid_key->gid = info.st_gid;
154
155
  // TODO(bbockelm): EINTR handling
156
  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
60
                  &(pid_key->sid), &(pid_key->pid_bday));
159
60
  fclose(fp_stat);
160
60
  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
60
  pid_key->pid = pid;
171
60
  return true;
172
}
173
174
175
/**
176
 * Caller is responsible for freeing the returned token.
177
 */
178
12
AuthzToken *AuthzSessionManager::GetTokenCopy(
179
  const pid_t pid,
180
  const std::string &membership)
181
{
182
12
  SessionKey session_key;
183
12
  PidKey pid_key;
184
12
  bool retval = LookupSessionKey(pid, &pid_key, &session_key);
185
12
  if (!retval)
186
4
    return NULL;
187
188
8
  AuthzData authz_data;
189
  const bool granted =
190
8
    LookupAuthzData(pid_key, session_key, membership, &authz_data);
191
8
  if (!granted)
192
    return NULL;
193
8
  return authz_data.token.DeepCopy();
194
}
195
196
197
28
bool AuthzSessionManager::IsMemberOf(
198
  const pid_t pid,
199
  const std::string &membership)
200
{
201
28
  SessionKey session_key;
202
28
  PidKey pid_key;
203
28
  bool retval = LookupSessionKey(pid, &pid_key, &session_key);
204
28
  if (!retval)
205
4
    return false;
206
207
24
  AuthzData authz_data;
208
24
  return LookupAuthzData(pid_key, session_key, membership, &authz_data);
209
}
210
211
212
/**
213
 * Calls out to the AuthzFetcher if the data is not cached.  Verifies the
214
 * membership.
215
 */
216
56
bool AuthzSessionManager::LookupAuthzData(
217
  const PidKey &pid_key,
218
  const SessionKey &session_key,
219
  const std::string &membership,
220
  AuthzData *authz_data)
221
{
222
56
  assert(authz_data != NULL);
223
224
56
  LockMutex(&lock_session2cred_);
225
56
  MaySweepCreds();
226
56
  bool found = session2cred_.Lookup(session_key, authz_data);
227
56
  UnlockMutex(&lock_session2cred_);
228
56
  if (found) {
229
    LogCvmfs(kLogAuthz, kLogDebug,
230
             "cached authz data for sid %d, membership %s, status %d",
231
             session_key.sid, authz_data->membership.c_str(),
232
28
             authz_data->status);
233
28
    const bool granted = authz_data->IsGranted(membership);
234
28
    if (granted)
235
8
      perf::Inc(n_grant_);
236
    else
237
20
      perf::Inc(n_deny_);
238
28
    return granted;
239
  }
240
241
  // Not found in cache, ask for help
242
28
  perf::Inc(n_fetch_);
243
  unsigned ttl;
244
  authz_data->status = authz_fetcher_->Fetch(
245
    AuthzFetcher::QueryInfo(pid_key.pid, pid_key.uid, pid_key.gid, membership),
246
28
    &(authz_data->token), &ttl);
247
28
  authz_data->deadline = platform_monotonic_time() + ttl;
248
28
  if (authz_data->status == kAuthzOk)
249
24
    authz_data->membership = membership;
250
  LogCvmfs(kLogAuthz, kLogDebug,
251
           "fetched authz data for sid %d (pid %d), membership %s, status %d, "
252
           "ttl %u", session_key.sid, pid_key.pid,
253
28
           authz_data->membership.c_str(), authz_data->status, ttl);
254
255
28
  LockMutex(&lock_session2cred_);
256
28
  if (!session2cred_.Contains(session_key))
257
28
    perf::Inc(no_session_);
258
28
  session2cred_.Insert(session_key, *authz_data);
259
28
  UnlockMutex(&lock_session2cred_);
260
261
28
  const bool granted = authz_data->status == kAuthzOk;
262
28
  if (granted)
263
24
    perf::Inc(n_grant_);
264
  else
265
4
    perf::Inc(n_deny_);
266
28
  return granted;
267
}
268
269
270
/**
271
 * Translate a PID and its birthday into an SID and its birthday.  The Session
272
 * ID and its birthday together with UID and GID make the Session Key.  The
273
 * translation result is cached in pid2session_.
274
 */
275
48
bool AuthzSessionManager::LookupSessionKey(
276
  pid_t pid,
277
  PidKey *pid_key,
278
  SessionKey *session_key)
279
{
280
48
  assert(pid_key != NULL);
281
48
  assert(session_key != NULL);
282
48
  if (!GetPidInfo(pid, pid_key))
283
12
    return false;
284
285
36
  LockMutex(&lock_pid2session_);
286
36
  bool found = pid2session_.Lookup(*pid_key, session_key);
287
36
  MaySweepPids();
288
36
  UnlockMutex(&lock_pid2session_);
289
36
  if (found) {
290
    LogCvmfs(kLogAuthz, kLogDebug,
291
             "Session key %d/%" PRIu64 " in cache; sid=%d, bday=%" PRIu64,
292
             pid_key->pid, pid_key->pid_bday,
293
20
             session_key->sid, session_key->sid_bday);
294
20
    return true;
295
  }
296
297
  LogCvmfs(kLogAuthz, kLogDebug,
298
16
           "Session key not found in cache, getting information from OS");
299
16
  PidKey sid_key;
300
16
  if (!GetPidInfo(pid_key->sid, &sid_key))
301
    return false;
302
303
16
  session_key->sid = sid_key.pid;
304
16
  session_key->sid_bday = sid_key.pid_bday;
305
16
  LockMutex(&lock_pid2session_);
306
16
  pid_key->deadline = platform_monotonic_time() + kPidLifetime;
307
16
  if (!pid2session_.Contains(*pid_key))
308
16
    perf::Inc(no_pid_);
309
16
  pid2session_.Insert(*pid_key, *session_key);
310
16
  UnlockMutex(&lock_pid2session_);
311
312
  LogCvmfs(kLogAuthz, kLogDebug, "Lookup key %d/%" PRIu64 "; sid=%d, bday=%llu",
313
           pid_key->pid, pid_key->pid_bday,
314
16
           session_key->sid, session_key->sid_bday);
315
16
  return true;
316
}
317
318
319
/**
320
 * Scan through old sessions only every so often.
321
 */
322
56
void AuthzSessionManager::MaySweepCreds() {
323
56
  uint64_t now = platform_monotonic_time();
324
56
  if (now >= deadline_sweep_creds_) {
325
16
    SweepCreds(now);
326
16
    deadline_sweep_creds_ = now + kSweepInterval;
327
  }
328
56
}
329
330
331
/**
332
 * Scan through old PIDs only every so often.
333
 */
334
36
void AuthzSessionManager::MaySweepPids() {
335
36
  uint64_t now = platform_monotonic_time();
336
36
  if (now >= deadline_sweep_pids_) {
337
16
    SweepPids(now);
338
16
    deadline_sweep_pids_ = now + kSweepInterval;
339
  }
340
36
}
341
342
343
/**
344
 * Remove cache PIDs with expired cache life time.
345
 * TODO(jblomer): a generalized sweeping can become part of smallhash
346
 */
347
20
void AuthzSessionManager::SweepCreds(uint64_t now) {
348
20
  SessionKey empty_key;
349
20
  vector<SessionKey> trash_bin;
350
440
  for (unsigned i = 0; i < session2cred_.capacity(); ++i) {
351
420
    SessionKey this_key = session2cred_.keys()[i];
352
420
    if (this_key != empty_key) {
353
8
      if (now >= (session2cred_.values() + i)->deadline)
354
8
        trash_bin.push_back(this_key);
355
    }
356
  }
357
358
28
  for (unsigned i = 0; i < trash_bin.size(); ++i) {
359
8
    session2cred_.Erase(trash_bin[i]);
360
8
    perf::Dec(no_session_);
361
  }
362
20
}
363
364
365
/**
366
 * Remove cache PIDs with expired cache life time.
367
 * TODO(jblomer): a generalized sweeping can become part of smallhash
368
 */
369
24
void AuthzSessionManager::SweepPids(uint64_t now) {
370
24
  PidKey empty_key;
371
24
  vector<PidKey> trash_bin;
372
528
  for (unsigned i = 0; i < pid2session_.capacity(); ++i) {
373
504
    PidKey this_key = pid2session_.keys()[i];
374
504
    if (this_key != empty_key) {
375
8
      if (now >= this_key.deadline)
376
4
        trash_bin.push_back(this_key);
377
    }
378
  }
379
380
28
  for (unsigned i = 0; i < trash_bin.size(); ++i) {
381
4
    pid2session_.Erase(trash_bin[i]);
382
4
    perf::Dec(no_pid_);
383
  }
384

69
}