X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/fa7225c82381bac4432a6edf16f53b5370238d85..90dc47c27df1983f6ebc252b0c4b94c8718fe52d:/securityd/src/notifications.cpp diff --git a/securityd/src/notifications.cpp b/securityd/src/notifications.cpp index 7c048134..8ef8872b 100644 --- a/securityd/src/notifications.cpp +++ b/securityd/src/notifications.cpp @@ -26,13 +26,19 @@ // notifications - handling of securityd-gated notification messages // #include +#include #include "notifications.h" #include "server.h" #include "connection.h" +#include "dictionary.h" +#include "SharedMemoryClient.h" + #include #include +#include +#include Listener::ListenerMap& Listener::listeners = *(new Listener::ListenerMap); Mutex Listener::setLock(Mutex::recursive); @@ -72,12 +78,18 @@ void Listener::notify(NotificationDomain domain, } void Listener::notify(NotificationDomain domain, - NotificationEvent event, uint32 sequence, const CssmData &data) + NotificationEvent event, uint32 sequence, const CssmData &data, audit_token_t auditToken) { Connection ¤t = Server::active().connection(); RefPointer message = new Notification(domain, event, sequence, data); if (current.inSequence(message)) { StLock _(setLock); + + // This is a total layer violation, but no better place to put it + uid_t uid = audit_token_to_euid(auditToken); + gid_t gid = audit_token_to_egid(auditToken); + SharedMemoryListener::createDefaultSharedMemoryListener(uid, gid); + sendNotification(message); while (RefPointer next = current.popNotification()) sendNotification(next); @@ -86,10 +98,13 @@ void Listener::notify(NotificationDomain domain, void Listener::sendNotification(Notification *message) { + secdebug("MDSPRIVACY","Listener::sendNotification for uid/euid: %d/%d", getuid(), geteuid()); + for (ListenerMap::const_iterator it = listeners.begin(); it != listeners.end(); it++) { Listener *listener = it->second; - if (listener->domain == kNotificationDomainAll || (message->domain == listener->domain && listener->wants(message->event))) + if (listener->domain == kNotificationDomainAll || + (message->domain == listener->domain && listener->wants(message->event))) listener->notifyMe(message); } } @@ -138,6 +153,10 @@ Listener::Notification::~Notification() this, domain, event, sequence); } +std::string Listener::Notification::description() const { + return SharedMemoryCommon::notificationDescription(domain, event) + + ", Seq: " + std::to_string(sequence) + ", Data: " + std::to_string(this->size()); +} // // Jitter buffering @@ -168,48 +187,210 @@ RefPointer Listener::JitterBuffer::popNotification() } } +bool Listener::testPredicate(const std::function test) { + StLock _(setLock); + for (ListenerMap::const_iterator it = listeners.begin(); it != listeners.end(); it++) { + if (test(*(it->second))) + return true; + } + return false; +} + /* * Shared memory listener */ -SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize) : +SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize, uid_t uid, gid_t gid) : Listener (kNotificationDomainAll, kNotificationAllEvents), - SharedMemoryServer (segmentName, segmentSize), + SharedMemoryServer (segmentName, segmentSize, uid, gid), mActive (false) { - if (segmentName == NULL) - { - secerror("Attempted to start securityd with a NULL segmentName"); - abort(); - } } SharedMemoryListener::~SharedMemoryListener () { } +// Look for a listener for a given user ID +bool SharedMemoryListener::findUID(uid_t uid) { + return Listener::testPredicate([uid](const Listener& listener) -> bool { + try { + // There may be elements in the map that are not SharedMemoryListeners + const SharedMemoryListener& smlListener = dynamic_cast(listener); + if (smlListener.mUID == uid) + return true; + } + catch (...) { + return false; + } + return false; + } + ); + return false; +} + +void SharedMemoryListener::createDefaultSharedMemoryListener(uid_t uid, gid_t gid) { + uid_t fuid = SharedMemoryCommon::fixUID(uid); + if (fuid != 0) { // already created when securityd started up + if (!SharedMemoryListener::findUID(fuid)) { + secdebug("MDSPRIVACY","creating SharedMemoryListener for uid/gid: %d/%d", fuid, gid); + // A side effect of creation of a SharedMemoryListener is addition to the ListenerMap +#ifndef __clang_analyzer__ + /* __unused auto sml = */ new SharedMemoryListener(SharedMemoryCommon::kDefaultSecurityMessagesName, kSharedMemoryPoolSize, uid, gid); +#endif // __clang_analyzer__ + } + } +} + +// Simpler local version of PrimaryKeyImpl::getUInt32 +uint32 SharedMemoryListener::getRecordType(const CssmData& val) const { + if (val.Length < sizeof(uint32)) + return 0; // Not really but good enough for here + + const uint8 *pv = val.Data; + // @@@ Assumes data written in big endian. + uint32 value = (pv[0] << 24) + (pv[1] << 16) + (pv[2] << 8) + pv[3]; + return value; +} + +bool SharedMemoryListener::isTrustEvent(Notification *notification) { + bool trustEvent = false; + + switch (notification->event) { + case kSecDefaultChangedEvent: + case kSecKeychainListChangedEvent: + case kSecTrustSettingsChangedEvent: + trustEvent = true; + break; + case kSecAddEvent: + case kSecDeleteEvent: + case kSecUpdateEvent: + { + NameValueDictionary dictionary (notification->data); + const NameValuePair *item = dictionary.FindByName(ITEM_KEY); + if (item && (CSSM_DB_RECORDTYPE)getRecordType(item->Value()) == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { + trustEvent = true; + } + } + break; + default: + break; + } + + if (trustEvent) { + uint32_t result = notify_post(kSecServerCertificateTrustNotification); + if (result != NOTIFY_STATUS_OK) { + secdebug("MDSPRIVACY","Certificate trust event notification failed: %d", result); + } + } + + secdebug("MDSPRIVACY","[%03d] Event is %s trust event", mUID, trustEvent?"a":"not a"); + return trustEvent; +} + +bool SharedMemoryListener::needsPrivacyFilter(Notification *notification) { + if (notification->domain == kNotificationDomainPCSC || notification->domain == kNotificationDomainCDSA) + return false; + + // kNotificationDomainDatabase = 1, // something happened to a database (aka keychain) + switch (notification->event) { + case kSecLockEvent: // kNotificationEventLocked + case kSecUnlockEvent: // kNotificationEventUnlocked + case kSecPasswordChangedEvent: // kNotificationEventPassphraseChanged + case kSecDefaultChangedEvent: + case kSecDataAccessEvent: + case kSecKeychainListChangedEvent: + case kSecTrustSettingsChangedEvent: + return false; + case kSecAddEvent: + case kSecDeleteEvent: + case kSecUpdateEvent: + break; + } + + secdebug("MDSPRIVACY","[%03d] Evaluating event %s", mUID, notification->description().c_str()); + + NameValueDictionary dictionary (notification->data); + const NameValuePair *item = dictionary.FindByName(ITEM_KEY); + + // If we don't have an item, there is nothing to filter + if (!item) { + secdebug("MDSPRIVACY","[%03d] Item event did not contain an item", mUID); + return false; + } + + pid_t thisPid = 0; + const NameValuePair *pidRef = dictionary.FindByName(PID_KEY); + if (pidRef != 0) { + thisPid = n2h(*reinterpret_cast(pidRef->Value().data())); + } + + uid_t out_euid = 0; + int rx = SharedMemoryListener::get_process_euid(thisPid, out_euid); + if (rx != 0) { + secdebug("MDSPRIVACY","[%03d] get_process_euid failed (rx=%d), filtering out item", mUID, rx); + return true; + } + + if (out_euid == mUID) { + return false; // Listener owns this item, so no filtering + } + + // Allow processes running as root to pass through certificates + if (out_euid == 0) { + CSSM_DB_RECORDTYPE recordType = getRecordType(item->Value()); + if (recordType == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { + return false; + } + } + + secdebug("MDSPRIVACY","[%03d] Filtering event %s", mUID, notification->description().c_str()); + return true; +} + const double kServerWait = 0.005; // time in seconds before clients will be notified that data is available void SharedMemoryListener::notifyMe(Notification* notification) { - const void* data = notification->data.data(); - size_t length = notification->data.length(); + const void* data = notification->data.data(); + size_t length = notification->data.length(); /* enforce a maximum size of 16k for notifications */ if (length > 16384) return; + isTrustEvent(notification); + if (needsPrivacyFilter(notification)) { + return; // just drop it + } + + secdebug("MDSPRIVACY","[%03d] WriteMessage event %s", mUID, notification->description().c_str()); + WriteMessage (notification->domain, notification->event, data, int_cast(length)); - if (!mActive) - { - Server::active().setTimer (this, Time::Interval(kServerWait)); - mActive = true; - } + if (!mActive) + { + Server::active().setTimer (this, Time::Interval(kServerWait)); + mActive = true; + } } void SharedMemoryListener::action () { secinfo("notify", "Posted notification to clients."); + secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID); notify_post (mSegmentName.c_str ()); mActive = false; } + +int SharedMemoryListener::get_process_euid(pid_t pid, uid_t& out_euid) { + struct kinfo_proc proc_info = {}; + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; + size_t len = sizeof(struct kinfo_proc); + int ret = sysctl(mib, (sizeof(mib)/sizeof(int)), &proc_info, &len, NULL, 0); + + out_euid = -1; + if (ret == 0) { + out_euid = proc_info.kp_eproc.e_ucred.cr_uid; + } + return ret; +}