notifications.cpp
1 /* 2 * Copyright (c) 2000-2004,2006,2008 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 25 // 26 // notifications - handling of securityd-gated notification messages 27 // 28 #include <notify.h> 29 #include <sys/sysctl.h> 30 31 #include "notifications.h" 32 #include "server.h" 33 #include "connection.h" 34 #include "dictionary.h" 35 #include "SharedMemoryClient.h" 36 37 #include <securityd_client/ucspNotify.h> 38 #include <security_utilities/casts.h> 39 40 #include <Security/SecKeychain.h> 41 #include <Security/SecItemInternal.h> 42 43 Listener::ListenerMap& Listener::listeners = *(new Listener::ListenerMap); 44 Mutex Listener::setLock(Mutex::recursive); 45 46 47 // 48 // Listener basics 49 // 50 Listener::Listener(NotificationDomain dom, NotificationMask evs, mach_port_t port) 51 : domain(dom), events(evs) 52 { 53 assert(events); // what's the point? 54 55 // register in listener set 56 StLock<Mutex> _(setLock); 57 listeners.insert(ListenerMap::value_type(port, this)); 58 59 secinfo("notify", "%p created for domain 0x%x events 0x%x port %d", 60 this, dom, evs, port); 61 } 62 63 Listener::~Listener() 64 { 65 secinfo("notify", "%p destroyed", this); 66 } 67 68 69 // 70 // Send a notification to all registered listeners 71 // 72 void Listener::notify(NotificationDomain domain, 73 NotificationEvent event, const CssmData &data) 74 { 75 RefPointer<Notification> message = new Notification(domain, event, 0, data); 76 StLock<Mutex> _(setLock); 77 sendNotification(message); 78 } 79 80 void Listener::notify(NotificationDomain domain, 81 NotificationEvent event, uint32 sequence, const CssmData &data, audit_token_t auditToken) 82 { 83 Connection ¤t = Server::active().connection(); 84 RefPointer<Notification> message = new Notification(domain, event, sequence, data); 85 if (current.inSequence(message)) { 86 StLock<Mutex> _(setLock); 87 88 // This is a total layer violation, but no better place to put it 89 uid_t uid = audit_token_to_euid(auditToken); 90 gid_t gid = audit_token_to_egid(auditToken); 91 SharedMemoryListener::createDefaultSharedMemoryListener(uid, gid); 92 93 sendNotification(message); 94 while (RefPointer<Notification> next = current.popNotification()) 95 sendNotification(next); 96 } 97 } 98 99 void Listener::sendNotification(Notification *message) 100 { 101 secdebug("MDSPRIVACY","Listener::sendNotification for uid/euid: %d/%d", getuid(), geteuid()); 102 103 for (ListenerMap::const_iterator it = listeners.begin(); 104 it != listeners.end(); it++) { 105 Listener *listener = it->second; 106 if (listener->domain == kNotificationDomainAll || 107 (message->domain == listener->domain && listener->wants(message->event))) 108 listener->notifyMe(message); 109 } 110 } 111 112 // 113 // Notification message objects 114 // 115 Listener::Notification::Notification(NotificationDomain inDomain, 116 NotificationEvent inEvent, uint32 seq, const CssmData &inData) 117 : domain(inDomain), event(inEvent), sequence(seq), data(Allocator::standard(), inData) 118 { 119 secinfo("notify", "%p notification created domain 0x%x event %d seq %d", 120 this, domain, event, sequence); 121 } 122 123 Listener::Notification::~Notification() 124 { 125 secinfo("notify", "%p notification done domain 0x%x event %d seq %d", 126 this, domain, event, sequence); 127 } 128 129 std::string Listener::Notification::description() const { 130 return SharedMemoryCommon::notificationDescription(domain, event) + 131 ", Seq: " + std::to_string(sequence) + ", Data: " + std::to_string(this->size()); 132 } 133 134 // 135 // Jitter buffering 136 // 137 bool Listener::JitterBuffer::inSequence(Notification *message) 138 { 139 if (message->sequence == mNotifyLast + 1) { // next in sequence 140 mNotifyLast++; // record next sequence 141 return true; // go ahead 142 } else { 143 secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering", 144 message, mNotifyLast, message->sequence); 145 mBuffer[message->sequence] = message; // save for later 146 return false; // hold your fire 147 } 148 } 149 150 RefPointer<Listener::Notification> Listener::JitterBuffer::popNotification() 151 { 152 JBuffer::iterator it = mBuffer.find(mNotifyLast + 1); // have next message? 153 if (it == mBuffer.end()) 154 return NULL; // nothing here 155 else { 156 RefPointer<Notification> result = it->second; // save value 157 mBuffer.erase(it); // remove from buffer 158 secinfo("notify-jit", "%p retrieved from jitter buffer", result.get()); 159 return result; // return it 160 } 161 } 162 163 bool Listener::testPredicate(const std::function<bool(const Listener& listener)> test) { 164 StLock<Mutex> _(setLock); 165 for (ListenerMap::const_iterator it = listeners.begin(); it != listeners.end(); it++) { 166 if (test(*(it->second))) 167 return true; 168 } 169 return false; 170 } 171 172 /* 173 * Shared memory listener 174 */ 175 176 177 SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize, uid_t uid, gid_t gid) : 178 Listener (kNotificationDomainAll, kNotificationAllEvents), 179 SharedMemoryServer (segmentName, segmentSize, uid, gid), 180 mActive (false), mMutex() 181 { 182 } 183 184 SharedMemoryListener::~SharedMemoryListener () 185 { 186 } 187 188 // Look for a listener for a given user ID 189 bool SharedMemoryListener::findUID(uid_t uid) { 190 return Listener::testPredicate([uid](const Listener& listener) -> bool { 191 try { 192 // There may be elements in the map that are not SharedMemoryListeners 193 const SharedMemoryListener& smlListener = dynamic_cast<const SharedMemoryListener&>(listener); 194 if (smlListener.mUID == uid) 195 return true; 196 } 197 catch (...) { 198 return false; 199 } 200 return false; 201 } 202 ); 203 return false; 204 } 205 206 void SharedMemoryListener::createDefaultSharedMemoryListener(uid_t uid, gid_t gid) { 207 uid_t fuid = SharedMemoryCommon::fixUID(uid); 208 if (fuid != 0) { // already created when securityd started up 209 if (!SharedMemoryListener::findUID(fuid)) { 210 secdebug("MDSPRIVACY","creating SharedMemoryListener for uid/gid: %d/%d", fuid, gid); 211 // A side effect of creation of a SharedMemoryListener is addition to the ListenerMap 212 #ifndef __clang_analyzer__ 213 /* __unused auto sml = */ new SharedMemoryListener(SharedMemoryCommon::kDefaultSecurityMessagesName, kSharedMemoryPoolSize, uid, gid); 214 #endif // __clang_analyzer__ 215 } 216 } 217 } 218 219 // Simpler local version of PrimaryKeyImpl::getUInt32 220 uint32 SharedMemoryListener::getRecordType(const CssmData& val) const { 221 if (val.Length < sizeof(uint32)) 222 return 0; // Not really but good enough for here 223 224 const uint8 *pv = val.Data; 225 // @@@ Assumes data written in big endian. 226 uint32 value = (pv[0] << 24) + (pv[1] << 16) + (pv[2] << 8) + pv[3]; 227 return value; 228 } 229 230 bool SharedMemoryListener::isTrustEvent(Notification *notification) { 231 bool trustEvent = false; 232 233 switch (notification->event) { 234 case kSecDefaultChangedEvent: 235 case kSecKeychainListChangedEvent: 236 case kSecTrustSettingsChangedEvent: 237 trustEvent = true; 238 break; 239 case kSecAddEvent: 240 case kSecDeleteEvent: 241 case kSecUpdateEvent: 242 { 243 NameValueDictionary dictionary (notification->data); 244 const NameValuePair *item = dictionary.FindByName(ITEM_KEY); 245 if (item && (CSSM_DB_RECORDTYPE)getRecordType(item->Value()) == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { 246 trustEvent = true; 247 } 248 } 249 break; 250 default: 251 break; 252 } 253 254 if (trustEvent) { 255 uint32_t result = notify_post(kSecServerCertificateTrustNotification); 256 if (result != NOTIFY_STATUS_OK) { 257 secdebug("MDSPRIVACY","Certificate trust event notification failed: %d", result); 258 } 259 } 260 261 secdebug("MDSPRIVACY","[%03d] Event is %s trust event", mUID, trustEvent?"a":"not a"); 262 return trustEvent; 263 } 264 265 bool SharedMemoryListener::needsPrivacyFilter(Notification *notification) { 266 if (notification->domain == kNotificationDomainPCSC || notification->domain == kNotificationDomainCDSA) 267 return false; 268 269 // kNotificationDomainDatabase = 1, // something happened to a database (aka keychain) 270 switch (notification->event) { 271 case kSecLockEvent: // kNotificationEventLocked 272 case kSecUnlockEvent: // kNotificationEventUnlocked 273 case kSecPasswordChangedEvent: // kNotificationEventPassphraseChanged 274 case kSecDefaultChangedEvent: 275 case kSecKeychainListChangedEvent: 276 case kSecTrustSettingsChangedEvent: 277 return false; 278 case kSecDataAccessEvent: 279 case kSecAddEvent: 280 case kSecDeleteEvent: 281 case kSecUpdateEvent: 282 break; 283 } 284 285 secdebug("MDSPRIVACY","[%03d] Evaluating event %s", mUID, notification->description().c_str()); 286 287 NameValueDictionary dictionary (notification->data); 288 const NameValuePair *item = dictionary.FindByName(ITEM_KEY); 289 290 // If we don't have an item, there is nothing to filter 291 if (!item) { 292 secdebug("MDSPRIVACY","[%03d] Item event did not contain an item", mUID); 293 return false; 294 } 295 296 pid_t thisPid = 0; 297 const NameValuePair *pidRef = dictionary.FindByName(PID_KEY); 298 if (pidRef != 0) { 299 thisPid = n2h(*reinterpret_cast<pid_t*>(pidRef->Value().data())); 300 } 301 302 uid_t out_euid = 0; 303 int rx = SharedMemoryListener::get_process_euid(thisPid, out_euid); 304 if (rx != 0) { 305 secdebug("MDSPRIVACY","[%03d] get_process_euid failed (rx=%d), filtering out item", mUID, rx); 306 return true; 307 } 308 309 if (out_euid == mUID) { 310 return false; // Listener owns this item, so no filtering 311 } 312 313 // Allow processes running as root to pass through certificates 314 if (out_euid == 0) { 315 CSSM_DB_RECORDTYPE recordType = getRecordType(item->Value()); 316 if (recordType == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { 317 return false; 318 } 319 } 320 321 secdebug("MDSPRIVACY","[%03d] Filtering event %s", mUID, notification->description().c_str()); 322 return true; 323 } 324 325 const double kServerWait = 0.005; // time in seconds before clients will be notified that data is available 326 327 void SharedMemoryListener::notifyMe(Notification* notification) 328 { 329 const void* data = notification->data.data(); 330 size_t length = notification->data.length(); 331 /* enforce a maximum size of 16k for notifications */ 332 if (length > 16384) return; 333 334 isTrustEvent(notification); 335 if (needsPrivacyFilter(notification)) { 336 return; // just drop it 337 } 338 339 secdebug("MDSPRIVACY","[%03d] WriteMessage event %s", mUID, notification->description().c_str()); 340 341 WriteMessage (notification->domain, notification->event, data, int_cast<size_t, UInt32>(length)); 342 343 StLock<Mutex> lock(mMutex); 344 if (!mActive) 345 { 346 Server::active().setTimer (this, Time::Interval(kServerWait)); 347 mActive = true; 348 } 349 } 350 351 void SharedMemoryListener::action () 352 { 353 StLock<Mutex> lock(mMutex); 354 notify_post (mSegmentName.c_str ()); 355 secinfo("notify", "Posted notification to clients."); 356 secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID); 357 mActive = false; 358 } 359 360 int SharedMemoryListener::get_process_euid(pid_t pid, uid_t& out_euid) { 361 struct kinfo_proc proc_info = {}; 362 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; 363 size_t len = sizeof(struct kinfo_proc); 364 int ret = sysctl(mib, (sizeof(mib)/sizeof(int)), &proc_info, &len, NULL, 0); 365 366 out_euid = -1; 367 if (ret == 0) { 368 out_euid = proc_info.kp_eproc.e_ucred.cr_uid; 369 } 370 return ret; 371 }