// ---------------------------------------------------------------------- // File: ProcessCache.cc // Author: Georgios Bitzes - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "ProcessCache.hh" #include "Logbook.hh" thread_local bool execveAlarm { false }; ExecveAlert::ExecveAlert(bool val) { execveAlarm = val; } ExecveAlert::~ExecveAlert() { execveAlarm = false; } //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ ProcessCache::ProcessCache(const CredentialConfig& conf, BoundIdentityProvider& bip, ProcessInfoProvider& pip, JailResolver& jr) : credConfig(conf), cache(16 /* 2^16 shards */, 1000 * 60 /* 1 minute inactivity TTL */), boundIdentityProvider(bip), processInfoProvider(pip), jailResolver(jr) { myJail = jailResolver.resolve(getpid()); } //------------------------------------------------------------------------------ // Discover some bound identity to use matching the given arguments. //------------------------------------------------------------------------------ std::shared_ptr ProcessCache::discoverBoundIdentity(const JailInformation& jail, const ProcessInfo& processInfo, uid_t uid, gid_t gid, bool reconnect, Logbook& logbook) { std::shared_ptr output; //---------------------------------------------------------------------------- // Shortcut: If all authentication methods are disabled, or unix is enabled and uid!=0 just use Unix //---------------------------------------------------------------------------- if ((!credConfig.use_user_krb5cc && !credConfig.use_user_gsiproxy && !credConfig.use_user_sss && !credConfig.use_user_oauth2 && !credConfig.use_user_ztn) || (credConfig.use_user_unix && (uid || credConfig.use_root_unix))) { LogbookScope scope; if (credConfig.use_user_unix && (uid || credConfig.use_root_unix)) { scope = logbook.makeScope("unix enabled - " "using UNIX"); } else { scope = logbook.makeScope("krb5, x509, OAUTH2 and SSS disabled - " "falling back to UNIX"); } Environment env; // in such a case encryptio does not work return boundIdentityProvider.unixAuth(processInfo.getPid(), uid, gid, reconnect, scope, env); } //---------------------------------------------------------------------------- // First thing to consider: Should we check the credentials of the process // itself first, or that of the parent? //---------------------------------------------------------------------------- #define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */ bool checkParentFirst = false; if (execveAlarm) { //-------------------------------------------------------------------------- // Nope, we're certainly in execve, don't check the process itself at all. //-------------------------------------------------------------------------- checkParentFirst = true; } if (credConfig.forknoexec_heuristic && (processInfo.getFlags() & PF_FORKNOEXEC)) { //-------------------------------------------------------------------------- // Process is in FORKNOEXEC.. suspicious. The vast majority of processes // doing an execve are in PF_FORKNOEXEC state, such as processes spawned // by shells. // // First check the parent - this radically decreases the number of times // we have to pay the deadlock timeout penalty. //-------------------------------------------------------------------------- checkParentFirst = true; } LOGBOOK_INSERT(logbook, "execveAlarm = " << execveAlarm << ", PF_FORKNOEXEC = " << (processInfo.getFlags() & PF_FORKNOEXEC) << ", checkParentFirst = " << checkParentFirst); LogbookScope scope = logbook.makeScope("Attempting to discover bound identity " "based on environment variables"); //---------------------------------------------------------------------------- // Check parent? //---------------------------------------------------------------------------- Environment pidEnv; if (checkParentFirst && processInfo.getParentId() != 1) { output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail, processInfo.getParentId(), uid, gid, reconnect, scope, pidEnv); if (output) { return output; } } //---------------------------------------------------------------------------- // Check process itself? // // Don't even attempt to read /proc/pid/environ if we _know_ we're doing an // execve. If execveAlarm is off, there's still the possibility we're doing // an execve due to uncached lookups sent by the kernel before the actual // open! In that case, we'll simply have to pay the deadlock timeout penalty, // but we'll still recover. //---------------------------------------------------------------------------- if (!execveAlarm) { output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail, processInfo.getPid(), uid, gid, reconnect, scope, pidEnv); if (output) { return output; } } //---------------------------------------------------------------------------- // Check parent, if we didn't already //---------------------------------------------------------------------------- if (!checkParentFirst && processInfo.getParentId() != 1) { output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail, processInfo.getParentId(), uid, gid, reconnect, scope, pidEnv); if (output) { return output; } } //---------------------------------------------------------------------------- // Nothing yet.. try global binding from eosfusebind... //---------------------------------------------------------------------------- output = boundIdentityProvider.globalBindingToBoundIdentity(jail, uid, gid, reconnect, scope, pidEnv); if (output) { return output; } //---------------------------------------------------------------------------- // What about default paths, ie /tmp/krb5cc_? //---------------------------------------------------------------------------- output = boundIdentityProvider.defaultPathsToBoundIdentity(jail, uid, gid, reconnect, scope, pidEnv); if (output) { return output; } //---------------------------------------------------------------------------- // No credentials found at all.. fallback to unix authentication. //---------------------------------------------------------------------------- return boundIdentityProvider.unixAuth(processInfo.getPid(), uid, gid, reconnect, scope, pidEnv); } //------------------------------------------------------------------------------ // Major retrieve function, called by the rest of eosxd. //------------------------------------------------------------------------------ ProcessSnapshot ProcessCache::retrieve(pid_t pid, uid_t uid, gid_t gid, bool reconnect) { Logbook disabled(false); return retrieve(pid, uid, gid, reconnect, disabled); } //---------------------------------------------------------------------------- // Major retrieve function, called by the rest of eosxd - using // custom logbook. //---------------------------------------------------------------------------- ProcessSnapshot ProcessCache::retrieve(pid_t pid, uid_t uid, gid_t gid, bool reconnect, Logbook& logbook) { LOGBOOK_INSERT(logbook, "===== Retrieve process snapshot for pid=" << pid << ", uid=" << uid << ", gid=" << gid << ", reconnect=" << reconnect << " ====="); LogbookScope scope(logbook.makeScope(SSTR("/proc/" << pid << "/root lookup"))); //---------------------------------------------------------------------------- // Warn if pid <= 0, something is wrong //---------------------------------------------------------------------------- if (pid <= 0) { std::ostringstream ss; ss << "Received invalid pid: " << pid << " - eosxd running in different pid namespace?"; eos_static_notice(ss.str().c_str()); LOGBOOK_INSERT(scope, ss.str()); } //---------------------------------------------------------------------------- // Retrieve information about the jail in which this pid lives in. Is it the // same as ours? //---------------------------------------------------------------------------- JailInformation jailInfo = jailResolver.resolve(pid); if (!jailInfo.id.ok()) { //-------------------------------------------------------------------------- // Couldn't retrieve jail of this pid.. bad. Assume our jail. //-------------------------------------------------------------------------- eos_static_notice("Could not retrieve jail information for pid=%d: %s", pid, jailInfo.id.describe().c_str()); jailInfo = myJail; LOGBOOK_INSERT(scope, "WARNING: Could not retrieve jail information for pid=" << pid << ", subsituting with my jail"); } LOGBOOK_INSERT(scope, jailInfo.describe()); //---------------------------------------------------------------------------- // First, let's check the cache. Major retrieve function, called by the rest // of eosxd. //---------------------------------------------------------------------------- ProcessCacheKey cacheKey(pid, uid, gid); ProcessSnapshot entry = cache.retrieve(cacheKey); if (entry && reconnect) { LOGBOOK_INSERT(logbook, "Found cached entry in ProcessCache (" << entry->getBoundIdentity()->getLogin().describe() << "), but reconnecting as requested"); } if (entry && !reconnect) { //-------------------------------------------------------------------------- // We have a cache hit, but it could refer to different processes, even if // PID is the same. The kernel could have re-used the same PID, verify. //-------------------------------------------------------------------------- ProcessInfo processInfo; if (!processInfoProvider.retrieveBasic(pid, processInfo)) { //-------------------------------------------------------------------------- // Dead PIDs issue no syscalls... or do they?! // // Release fuse request can be issued even after a process has died - in // this strange case, let's just return the cache info. //-------------------------------------------------------------------------- return entry; } if (processInfo.isSameProcess(entry->getProcessInfo())) { //------------------------------------------------------------------------ // Yep, that's a cache hit.. but credentials could have been invalidated // in the meantime, check. //------------------------------------------------------------------------ if (boundIdentityProvider.checkValidity(jailInfo, *entry->getBoundIdentity())) { return entry; } } //-------------------------------------------------------------------------- // Process has changed, or credentials invalidated - cache miss. //-------------------------------------------------------------------------- } //---------------------------------------------------------------------------- // Retrieve full information about this process, including its jail //---------------------------------------------------------------------------- ProcessInfo processInfo; if (!processInfoProvider.retrieveFull(pid, processInfo)) { return {}; } //---------------------------------------------------------------------------- // Discover which bound identity to attach to this process, and store into // the cache for future requests. //---------------------------------------------------------------------------- std::shared_ptr bdi = discoverBoundIdentity(jailInfo, processInfo, uid, gid, reconnect, logbook); LOGBOOK_INSERT(logbook, ""); LOGBOOK_INSERT(logbook, "===== BOUND IDENTITY: ====="); LOGBOOK_INSERT(logbook, bdi->describe()); ProcessSnapshot result; cache.store(cacheKey, std::unique_ptr(new ProcessCacheEntry(processInfo, jailInfo, bdi)), result); //---------------------------------------------------------------------------- // All done //---------------------------------------------------------------------------- return result; }