2 * Copyright (c) 2000-2004,2006,2008 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 // notifications - handling of securityd-gated notification messages
29 #include <sys/sysctl.h>
31 #include "notifications.h"
33 #include "connection.h"
34 #include "dictionary.h"
35 #include "SharedMemoryClient.h"
37 #include <securityd_client/ucspNotify.h>
38 #include <security_utilities/casts.h>
40 #include <Security/SecKeychain.h>
41 #include <Security/SecItemInternal.h>
43 Listener::ListenerMap
& Listener::listeners
= *(new Listener::ListenerMap
);
44 Mutex
Listener::setLock(Mutex::recursive
);
50 Listener::Listener(NotificationDomain dom
, NotificationMask evs
, mach_port_t port
)
51 : domain(dom
), events(evs
)
53 assert(events
); // what's the point?
55 // register in listener set
56 StLock
<Mutex
> _(setLock
);
57 listeners
.insert(ListenerMap::value_type(port
, this));
59 secinfo("notify", "%p created for domain 0x%x events 0x%x port %d",
60 this, dom
, evs
, port
);
65 secinfo("notify", "%p destroyed", this);
70 // Send a notification to all registered listeners
72 void Listener::notify(NotificationDomain domain
,
73 NotificationEvent event
, const CssmData
&data
)
75 RefPointer
<Notification
> message
= new Notification(domain
, event
, 0, data
);
76 StLock
<Mutex
> _(setLock
);
77 sendNotification(message
);
80 void Listener::notify(NotificationDomain domain
,
81 NotificationEvent event
, uint32 sequence
, const CssmData
&data
, audit_token_t auditToken
)
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
);
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
);
93 sendNotification(message
);
94 while (RefPointer
<Notification
> next
= current
.popNotification())
95 sendNotification(next
);
99 void Listener::sendNotification(Notification
*message
)
101 secdebug("MDSPRIVACY","Listener::sendNotification for uid/euid: %d/%d", getuid(), geteuid());
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
);
114 // Handle a port death or deallocation by removing all Listeners using that port.
115 // Returns true iff we had one.
117 bool Listener::remove(Port port
)
119 typedef ListenerMap::iterator Iterator
;
120 StLock
<Mutex
> _(setLock
);
121 pair
<Iterator
, Iterator
> range
= listeners
.equal_range(port
);
122 if (range
.first
== range
.second
)
123 return false; // not one of ours
125 assert(range
.first
!= listeners
.end());
126 secinfo("notify", "remove port %d", port
.port());
128 for (Iterator it
= range
.first
; it
!= range
.second
; it
++) {
129 assert(it
->first
== port
);
130 secinfo("notify", "%p listener removed", it
->second
.get());
133 listeners
.erase(range
.first
, range
.second
);
135 return true; // got it
140 // Notification message objects
142 Listener::Notification::Notification(NotificationDomain inDomain
,
143 NotificationEvent inEvent
, uint32 seq
, const CssmData
&inData
)
144 : domain(inDomain
), event(inEvent
), sequence(seq
), data(Allocator::standard(), inData
)
146 secinfo("notify", "%p notification created domain 0x%x event %d seq %d",
147 this, domain
, event
, sequence
);
150 Listener::Notification::~Notification()
152 secinfo("notify", "%p notification done domain 0x%x event %d seq %d",
153 this, domain
, event
, sequence
);
156 std::string
Listener::Notification::description() const {
157 return SharedMemoryCommon::notificationDescription(domain
, event
) +
158 ", Seq: " + std::to_string(sequence
) + ", Data: " + std::to_string(this->size());
164 bool Listener::JitterBuffer::inSequence(Notification
*message
)
166 if (message
->sequence
== mNotifyLast
+ 1) { // next in sequence
167 mNotifyLast
++; // record next sequence
168 return true; // go ahead
170 secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering",
171 message
, mNotifyLast
, message
->sequence
);
172 mBuffer
[message
->sequence
] = message
; // save for later
173 return false; // hold your fire
177 RefPointer
<Listener::Notification
> Listener::JitterBuffer::popNotification()
179 JBuffer::iterator it
= mBuffer
.find(mNotifyLast
+ 1); // have next message?
180 if (it
== mBuffer
.end())
181 return NULL
; // nothing here
183 RefPointer
<Notification
> result
= it
->second
; // save value
184 mBuffer
.erase(it
); // remove from buffer
185 secinfo("notify-jit", "%p retrieved from jitter buffer", result
.get());
186 return result
; // return it
190 bool Listener::testPredicate(const std::function
<bool(const Listener
& listener
)> test
) {
191 StLock
<Mutex
> _(setLock
);
192 for (ListenerMap::const_iterator it
= listeners
.begin(); it
!= listeners
.end(); it
++) {
193 if (test(*(it
->second
)))
200 * Shared memory listener
204 SharedMemoryListener::SharedMemoryListener(const char* segmentName
, SegmentOffsetType segmentSize
, uid_t uid
, gid_t gid
) :
205 Listener (kNotificationDomainAll
, kNotificationAllEvents
),
206 SharedMemoryServer (segmentName
, segmentSize
, uid
, gid
),
209 if (segmentName
== NULL
)
211 secerror("Attempted to start securityd with a NULL segmentName");
216 SharedMemoryListener::~SharedMemoryListener ()
220 // Look for a listener for a given user ID
221 bool SharedMemoryListener::findUID(uid_t uid
) {
222 return Listener::testPredicate([uid
](const Listener
& listener
) -> bool {
224 // There may be elements in the map that are not SharedMemoryListeners
225 const SharedMemoryListener
& smlListener
= dynamic_cast<const SharedMemoryListener
&>(listener
);
226 if (smlListener
.mUID
== uid
)
238 void SharedMemoryListener::createDefaultSharedMemoryListener(uid_t uid
, gid_t gid
) {
239 uid_t fuid
= SharedMemoryCommon::fixUID(uid
);
240 if (fuid
!= 0) { // already created when securityd started up
241 if (!SharedMemoryListener::findUID(fuid
)) {
242 secdebug("MDSPRIVACY","creating SharedMemoryListener for uid/gid: %d/%d", fuid
, gid
);
243 // A side effect of creation of a SharedMemoryListener is addition to the ListenerMap
244 #ifndef __clang_analyzer__
245 /* __unused auto sml = */ new SharedMemoryListener(SharedMemoryCommon::kDefaultSecurityMessagesName
, kSharedMemoryPoolSize
, uid
, gid
);
246 #endif // __clang_analyzer__
251 // Simpler local version of PrimaryKeyImpl::getUInt32
252 uint32
SharedMemoryListener::getRecordType(const CssmData
& val
) const {
253 if (val
.Length
< sizeof(uint32
))
254 return 0; // Not really but good enough for here
256 const uint8
*pv
= val
.Data
;
257 // @@@ Assumes data written in big endian.
258 uint32 value
= (pv
[0] << 24) + (pv
[1] << 16) + (pv
[2] << 8) + pv
[3];
262 bool SharedMemoryListener::isTrustEvent(Notification
*notification
) {
263 bool trustEvent
= false;
265 switch (notification
->event
) {
266 case kSecDefaultChangedEvent
:
267 case kSecKeychainListChangedEvent
:
268 case kSecTrustSettingsChangedEvent
:
272 case kSecDeleteEvent
:
273 case kSecUpdateEvent
:
275 NameValueDictionary
dictionary (notification
->data
);
276 const NameValuePair
*item
= dictionary
.FindByName(ITEM_KEY
);
277 if (item
&& (CSSM_DB_RECORDTYPE
)getRecordType(item
->Value()) == CSSM_DL_DB_RECORD_X509_CERTIFICATE
) {
287 uint32_t result
= notify_post(kSecServerCertificateTrustNotification
);
288 if (result
!= NOTIFY_STATUS_OK
) {
289 secdebug("MDSPRIVACY","Certificate trust event notification failed: %d", result
);
293 secdebug("MDSPRIVACY","[%03d] Event is %s trust event", mUID
, trustEvent
?"a":"not a");
297 bool SharedMemoryListener::needsPrivacyFilter(Notification
*notification
) {
298 if (notification
->domain
== kNotificationDomainPCSC
|| notification
->domain
== kNotificationDomainCDSA
)
301 // kNotificationDomainDatabase = 1, // something happened to a database (aka keychain)
302 switch (notification
->event
) {
303 case kSecLockEvent
: // kNotificationEventLocked
304 case kSecUnlockEvent
: // kNotificationEventUnlocked
305 case kSecPasswordChangedEvent
: // kNotificationEventPassphraseChanged
306 case kSecDefaultChangedEvent
:
307 case kSecDataAccessEvent
:
308 case kSecKeychainListChangedEvent
:
309 case kSecTrustSettingsChangedEvent
:
312 case kSecDeleteEvent
:
313 case kSecUpdateEvent
:
317 secdebug("MDSPRIVACY","[%03d] Evaluating event %s", mUID
, notification
->description().c_str());
319 NameValueDictionary
dictionary (notification
->data
);
320 const NameValuePair
*item
= dictionary
.FindByName(ITEM_KEY
);
322 // If we don't have an item, there is nothing to filter
324 secdebug("MDSPRIVACY","[%03d] Item event did not contain an item", mUID
);
329 const NameValuePair
*pidRef
= dictionary
.FindByName(PID_KEY
);
331 thisPid
= n2h(*reinterpret_cast<pid_t
*>(pidRef
->Value().data()));
335 int rx
= SharedMemoryListener::get_process_euid(thisPid
, out_euid
);
337 secdebug("MDSPRIVACY","[%03d] get_process_euid failed (rx=%d), filtering out item", mUID
, rx
);
341 if (out_euid
== mUID
) {
342 return false; // Listener owns this item, so no filtering
345 // Allow processes running as root to pass through certificates
347 CSSM_DB_RECORDTYPE recordType
= getRecordType(item
->Value());
348 if (recordType
== CSSM_DL_DB_RECORD_X509_CERTIFICATE
) {
353 secdebug("MDSPRIVACY","[%03d] Filtering event %s", mUID
, notification
->description().c_str());
357 const double kServerWait
= 0.005; // time in seconds before clients will be notified that data is available
359 void SharedMemoryListener::notifyMe(Notification
* notification
)
361 const void* data
= notification
->data
.data();
362 size_t length
= notification
->data
.length();
363 /* enforce a maximum size of 16k for notifications */
364 if (length
> 16384) return;
366 isTrustEvent(notification
);
367 if (needsPrivacyFilter(notification
)) {
368 return; // just drop it
371 secdebug("MDSPRIVACY","[%03d] WriteMessage event %s", mUID
, notification
->description().c_str());
373 WriteMessage (notification
->domain
, notification
->event
, data
, int_cast
<size_t, UInt32
>(length
));
377 Server::active().setTimer (this, Time::Interval(kServerWait
));
382 void SharedMemoryListener::action ()
384 secinfo("notify", "Posted notification to clients.");
385 secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID
);
386 notify_post (mSegmentName
.c_str ());
390 int SharedMemoryListener::get_process_euid(pid_t pid
, uid_t
& out_euid
) {
391 struct kinfo_proc proc_info
= {};
392 int mib
[] = {CTL_KERN
, KERN_PROC
, KERN_PROC_PID
, pid
};
393 size_t len
= sizeof(struct kinfo_proc
);
394 int ret
= sysctl(mib
, (sizeof(mib
)/sizeof(int)), &proc_info
, &len
, NULL
, 0);
398 out_euid
= proc_info
.kp_eproc
.e_ucred
.cr_uid
;