]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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> | |
6b200bc3 | 29 | #include <sys/sysctl.h> |
d8f41ccd A |
30 | |
31 | #include "notifications.h" | |
32 | #include "server.h" | |
33 | #include "connection.h" | |
6b200bc3 A |
34 | #include "dictionary.h" |
35 | #include "SharedMemoryClient.h" | |
36 | ||
d8f41ccd | 37 | #include <securityd_client/ucspNotify.h> |
fa7225c8 | 38 | #include <security_utilities/casts.h> |
d8f41ccd | 39 | |
6b200bc3 A |
40 | #include <Security/SecKeychain.h> |
41 | #include <Security/SecItemInternal.h> | |
d8f41ccd A |
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 | ||
fa7225c8 | 59 | secinfo("notify", "%p created for domain 0x%x events 0x%x port %d", |
d8f41ccd A |
60 | this, dom, evs, port); |
61 | } | |
62 | ||
63 | Listener::~Listener() | |
64 | { | |
fa7225c8 | 65 | secinfo("notify", "%p destroyed", this); |
d8f41ccd A |
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, | |
6b200bc3 | 81 | NotificationEvent event, uint32 sequence, const CssmData &data, audit_token_t auditToken) |
d8f41ccd A |
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); | |
6b200bc3 A |
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 | ||
d8f41ccd A |
93 | sendNotification(message); |
94 | while (RefPointer<Notification> next = current.popNotification()) | |
95 | sendNotification(next); | |
96 | } | |
97 | } | |
98 | ||
99 | void Listener::sendNotification(Notification *message) | |
100 | { | |
6b200bc3 A |
101 | secdebug("MDSPRIVACY","Listener::sendNotification for uid/euid: %d/%d", getuid(), geteuid()); |
102 | ||
d8f41ccd A |
103 | for (ListenerMap::const_iterator it = listeners.begin(); |
104 | it != listeners.end(); it++) { | |
105 | Listener *listener = it->second; | |
6b200bc3 A |
106 | if (listener->domain == kNotificationDomainAll || |
107 | (message->domain == listener->domain && listener->wants(message->event))) | |
d8f41ccd A |
108 | listener->notifyMe(message); |
109 | } | |
110 | } | |
111 | ||
112 | ||
113 | // | |
114 | // Handle a port death or deallocation by removing all Listeners using that port. | |
115 | // Returns true iff we had one. | |
116 | // | |
117 | bool Listener::remove(Port port) | |
118 | { | |
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 | |
124 | ||
125 | assert(range.first != listeners.end()); | |
fa7225c8 | 126 | secinfo("notify", "remove port %d", port.port()); |
d8f41ccd A |
127 | #if !defined(NDEBUG) |
128 | for (Iterator it = range.first; it != range.second; it++) { | |
129 | assert(it->first == port); | |
fa7225c8 | 130 | secinfo("notify", "%p listener removed", it->second.get()); |
d8f41ccd A |
131 | } |
132 | #endif //NDEBUG | |
133 | listeners.erase(range.first, range.second); | |
134 | port.destroy(); | |
135 | return true; // got it | |
136 | } | |
137 | ||
138 | ||
139 | // | |
140 | // Notification message objects | |
141 | // | |
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) | |
145 | { | |
fa7225c8 | 146 | secinfo("notify", "%p notification created domain 0x%x event %d seq %d", |
d8f41ccd A |
147 | this, domain, event, sequence); |
148 | } | |
149 | ||
150 | Listener::Notification::~Notification() | |
151 | { | |
fa7225c8 | 152 | secinfo("notify", "%p notification done domain 0x%x event %d seq %d", |
d8f41ccd A |
153 | this, domain, event, sequence); |
154 | } | |
155 | ||
6b200bc3 A |
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()); | |
159 | } | |
d8f41ccd A |
160 | |
161 | // | |
162 | // Jitter buffering | |
163 | // | |
164 | bool Listener::JitterBuffer::inSequence(Notification *message) | |
165 | { | |
166 | if (message->sequence == mNotifyLast + 1) { // next in sequence | |
167 | mNotifyLast++; // record next sequence | |
168 | return true; // go ahead | |
169 | } else { | |
fa7225c8 | 170 | secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering", |
d8f41ccd A |
171 | message, mNotifyLast, message->sequence); |
172 | mBuffer[message->sequence] = message; // save for later | |
173 | return false; // hold your fire | |
174 | } | |
175 | } | |
176 | ||
177 | RefPointer<Listener::Notification> Listener::JitterBuffer::popNotification() | |
178 | { | |
179 | JBuffer::iterator it = mBuffer.find(mNotifyLast + 1); // have next message? | |
180 | if (it == mBuffer.end()) | |
181 | return NULL; // nothing here | |
182 | else { | |
183 | RefPointer<Notification> result = it->second; // save value | |
184 | mBuffer.erase(it); // remove from buffer | |
fa7225c8 | 185 | secinfo("notify-jit", "%p retrieved from jitter buffer", result.get()); |
d8f41ccd A |
186 | return result; // return it |
187 | } | |
188 | } | |
189 | ||
6b200bc3 A |
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))) | |
194 | return true; | |
195 | } | |
196 | return false; | |
197 | } | |
198 | ||
d8f41ccd A |
199 | /* |
200 | * Shared memory listener | |
201 | */ | |
202 | ||
203 | ||
6b200bc3 | 204 | SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize, uid_t uid, gid_t gid) : |
d8f41ccd | 205 | Listener (kNotificationDomainAll, kNotificationAllEvents), |
6b200bc3 | 206 | SharedMemoryServer (segmentName, segmentSize, uid, gid), |
79b9da22 | 207 | mActive (false), mMutex() |
d8f41ccd | 208 | { |
d8f41ccd A |
209 | } |
210 | ||
211 | SharedMemoryListener::~SharedMemoryListener () | |
212 | { | |
213 | } | |
214 | ||
6b200bc3 A |
215 | // Look for a listener for a given user ID |
216 | bool SharedMemoryListener::findUID(uid_t uid) { | |
217 | return Listener::testPredicate([uid](const Listener& listener) -> bool { | |
218 | try { | |
219 | // There may be elements in the map that are not SharedMemoryListeners | |
220 | const SharedMemoryListener& smlListener = dynamic_cast<const SharedMemoryListener&>(listener); | |
221 | if (smlListener.mUID == uid) | |
222 | return true; | |
223 | } | |
224 | catch (...) { | |
225 | return false; | |
226 | } | |
227 | return false; | |
228 | } | |
229 | ); | |
230 | return false; | |
231 | } | |
232 | ||
233 | void SharedMemoryListener::createDefaultSharedMemoryListener(uid_t uid, gid_t gid) { | |
234 | uid_t fuid = SharedMemoryCommon::fixUID(uid); | |
235 | if (fuid != 0) { // already created when securityd started up | |
236 | if (!SharedMemoryListener::findUID(fuid)) { | |
237 | secdebug("MDSPRIVACY","creating SharedMemoryListener for uid/gid: %d/%d", fuid, gid); | |
238 | // A side effect of creation of a SharedMemoryListener is addition to the ListenerMap | |
239 | #ifndef __clang_analyzer__ | |
240 | /* __unused auto sml = */ new SharedMemoryListener(SharedMemoryCommon::kDefaultSecurityMessagesName, kSharedMemoryPoolSize, uid, gid); | |
241 | #endif // __clang_analyzer__ | |
242 | } | |
243 | } | |
244 | } | |
245 | ||
246 | // Simpler local version of PrimaryKeyImpl::getUInt32 | |
247 | uint32 SharedMemoryListener::getRecordType(const CssmData& val) const { | |
248 | if (val.Length < sizeof(uint32)) | |
249 | return 0; // Not really but good enough for here | |
250 | ||
251 | const uint8 *pv = val.Data; | |
252 | // @@@ Assumes data written in big endian. | |
253 | uint32 value = (pv[0] << 24) + (pv[1] << 16) + (pv[2] << 8) + pv[3]; | |
254 | return value; | |
255 | } | |
256 | ||
257 | bool SharedMemoryListener::isTrustEvent(Notification *notification) { | |
258 | bool trustEvent = false; | |
259 | ||
260 | switch (notification->event) { | |
261 | case kSecDefaultChangedEvent: | |
262 | case kSecKeychainListChangedEvent: | |
263 | case kSecTrustSettingsChangedEvent: | |
264 | trustEvent = true; | |
265 | break; | |
266 | case kSecAddEvent: | |
267 | case kSecDeleteEvent: | |
268 | case kSecUpdateEvent: | |
269 | { | |
270 | NameValueDictionary dictionary (notification->data); | |
271 | const NameValuePair *item = dictionary.FindByName(ITEM_KEY); | |
272 | if (item && (CSSM_DB_RECORDTYPE)getRecordType(item->Value()) == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { | |
273 | trustEvent = true; | |
274 | } | |
275 | } | |
276 | break; | |
277 | default: | |
278 | break; | |
279 | } | |
280 | ||
281 | if (trustEvent) { | |
282 | uint32_t result = notify_post(kSecServerCertificateTrustNotification); | |
283 | if (result != NOTIFY_STATUS_OK) { | |
284 | secdebug("MDSPRIVACY","Certificate trust event notification failed: %d", result); | |
285 | } | |
286 | } | |
287 | ||
288 | secdebug("MDSPRIVACY","[%03d] Event is %s trust event", mUID, trustEvent?"a":"not a"); | |
289 | return trustEvent; | |
290 | } | |
291 | ||
292 | bool SharedMemoryListener::needsPrivacyFilter(Notification *notification) { | |
293 | if (notification->domain == kNotificationDomainPCSC || notification->domain == kNotificationDomainCDSA) | |
294 | return false; | |
295 | ||
296 | // kNotificationDomainDatabase = 1, // something happened to a database (aka keychain) | |
297 | switch (notification->event) { | |
298 | case kSecLockEvent: // kNotificationEventLocked | |
299 | case kSecUnlockEvent: // kNotificationEventUnlocked | |
300 | case kSecPasswordChangedEvent: // kNotificationEventPassphraseChanged | |
301 | case kSecDefaultChangedEvent: | |
6b200bc3 A |
302 | case kSecKeychainListChangedEvent: |
303 | case kSecTrustSettingsChangedEvent: | |
304 | return false; | |
b54c578e | 305 | case kSecDataAccessEvent: |
6b200bc3 A |
306 | case kSecAddEvent: |
307 | case kSecDeleteEvent: | |
308 | case kSecUpdateEvent: | |
309 | break; | |
310 | } | |
311 | ||
312 | secdebug("MDSPRIVACY","[%03d] Evaluating event %s", mUID, notification->description().c_str()); | |
313 | ||
314 | NameValueDictionary dictionary (notification->data); | |
315 | const NameValuePair *item = dictionary.FindByName(ITEM_KEY); | |
316 | ||
317 | // If we don't have an item, there is nothing to filter | |
318 | if (!item) { | |
319 | secdebug("MDSPRIVACY","[%03d] Item event did not contain an item", mUID); | |
320 | return false; | |
321 | } | |
322 | ||
323 | pid_t thisPid = 0; | |
324 | const NameValuePair *pidRef = dictionary.FindByName(PID_KEY); | |
325 | if (pidRef != 0) { | |
326 | thisPid = n2h(*reinterpret_cast<pid_t*>(pidRef->Value().data())); | |
327 | } | |
328 | ||
329 | uid_t out_euid = 0; | |
330 | int rx = SharedMemoryListener::get_process_euid(thisPid, out_euid); | |
331 | if (rx != 0) { | |
332 | secdebug("MDSPRIVACY","[%03d] get_process_euid failed (rx=%d), filtering out item", mUID, rx); | |
333 | return true; | |
334 | } | |
335 | ||
336 | if (out_euid == mUID) { | |
337 | return false; // Listener owns this item, so no filtering | |
338 | } | |
339 | ||
340 | // Allow processes running as root to pass through certificates | |
341 | if (out_euid == 0) { | |
342 | CSSM_DB_RECORDTYPE recordType = getRecordType(item->Value()); | |
343 | if (recordType == CSSM_DL_DB_RECORD_X509_CERTIFICATE) { | |
344 | return false; | |
345 | } | |
346 | } | |
347 | ||
348 | secdebug("MDSPRIVACY","[%03d] Filtering event %s", mUID, notification->description().c_str()); | |
349 | return true; | |
350 | } | |
351 | ||
d8f41ccd A |
352 | const double kServerWait = 0.005; // time in seconds before clients will be notified that data is available |
353 | ||
354 | void SharedMemoryListener::notifyMe(Notification* notification) | |
355 | { | |
6b200bc3 A |
356 | const void* data = notification->data.data(); |
357 | size_t length = notification->data.length(); | |
822b670c A |
358 | /* enforce a maximum size of 16k for notifications */ |
359 | if (length > 16384) return; | |
360 | ||
6b200bc3 A |
361 | isTrustEvent(notification); |
362 | if (needsPrivacyFilter(notification)) { | |
363 | return; // just drop it | |
364 | } | |
365 | ||
366 | secdebug("MDSPRIVACY","[%03d] WriteMessage event %s", mUID, notification->description().c_str()); | |
367 | ||
fa7225c8 | 368 | WriteMessage (notification->domain, notification->event, data, int_cast<size_t, UInt32>(length)); |
822b670c | 369 | |
79b9da22 | 370 | StLock<Mutex> lock(mMutex); |
6b200bc3 A |
371 | if (!mActive) |
372 | { | |
373 | Server::active().setTimer (this, Time::Interval(kServerWait)); | |
374 | mActive = true; | |
375 | } | |
d8f41ccd A |
376 | } |
377 | ||
378 | void SharedMemoryListener::action () | |
379 | { | |
79b9da22 A |
380 | StLock<Mutex> lock(mMutex); |
381 | notify_post (mSegmentName.c_str ()); | |
fa7225c8 | 382 | secinfo("notify", "Posted notification to clients."); |
6b200bc3 | 383 | secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID); |
d8f41ccd A |
384 | mActive = false; |
385 | } | |
6b200bc3 A |
386 | |
387 | int SharedMemoryListener::get_process_euid(pid_t pid, uid_t& out_euid) { | |
388 | struct kinfo_proc proc_info = {}; | |
389 | int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; | |
390 | size_t len = sizeof(struct kinfo_proc); | |
391 | int ret = sysctl(mib, (sizeof(mib)/sizeof(int)), &proc_info, &len, NULL, 0); | |
392 | ||
393 | out_euid = -1; | |
394 | if (ret == 0) { | |
395 | out_euid = proc_info.kp_eproc.e_ucred.cr_uid; | |
396 | } | |
397 | return ret; | |
398 | } |