]>
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 | ||
d8f41ccd A |
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 | { | |
fa7225c8 | 119 | secinfo("notify", "%p notification created domain 0x%x event %d seq %d", |
d8f41ccd A |
120 | this, domain, event, sequence); |
121 | } | |
122 | ||
123 | Listener::Notification::~Notification() | |
124 | { | |
fa7225c8 | 125 | secinfo("notify", "%p notification done domain 0x%x event %d seq %d", |
d8f41ccd A |
126 | this, domain, event, sequence); |
127 | } | |
128 | ||
6b200bc3 A |
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 | } | |
d8f41ccd A |
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 { | |
fa7225c8 | 143 | secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering", |
d8f41ccd A |
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 | |
fa7225c8 | 158 | secinfo("notify-jit", "%p retrieved from jitter buffer", result.get()); |
d8f41ccd A |
159 | return result; // return it |
160 | } | |
161 | } | |
162 | ||
6b200bc3 A |
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 | ||
d8f41ccd A |
172 | /* |
173 | * Shared memory listener | |
174 | */ | |
175 | ||
176 | ||
6b200bc3 | 177 | SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize, uid_t uid, gid_t gid) : |
d8f41ccd | 178 | Listener (kNotificationDomainAll, kNotificationAllEvents), |
6b200bc3 | 179 | SharedMemoryServer (segmentName, segmentSize, uid, gid), |
79b9da22 | 180 | mActive (false), mMutex() |
d8f41ccd | 181 | { |
d8f41ccd A |
182 | } |
183 | ||
184 | SharedMemoryListener::~SharedMemoryListener () | |
185 | { | |
186 | } | |
187 | ||
6b200bc3 A |
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: | |
6b200bc3 A |
275 | case kSecKeychainListChangedEvent: |
276 | case kSecTrustSettingsChangedEvent: | |
277 | return false; | |
b54c578e | 278 | case kSecDataAccessEvent: |
6b200bc3 A |
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 | ||
d8f41ccd A |
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 | { | |
6b200bc3 A |
329 | const void* data = notification->data.data(); |
330 | size_t length = notification->data.length(); | |
822b670c A |
331 | /* enforce a maximum size of 16k for notifications */ |
332 | if (length > 16384) return; | |
333 | ||
6b200bc3 A |
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 | ||
fa7225c8 | 341 | WriteMessage (notification->domain, notification->event, data, int_cast<size_t, UInt32>(length)); |
822b670c | 342 | |
79b9da22 | 343 | StLock<Mutex> lock(mMutex); |
6b200bc3 A |
344 | if (!mActive) |
345 | { | |
346 | Server::active().setTimer (this, Time::Interval(kServerWait)); | |
347 | mActive = true; | |
348 | } | |
d8f41ccd A |
349 | } |
350 | ||
351 | void SharedMemoryListener::action () | |
352 | { | |
79b9da22 A |
353 | StLock<Mutex> lock(mMutex); |
354 | notify_post (mSegmentName.c_str ()); | |
fa7225c8 | 355 | secinfo("notify", "Posted notification to clients."); |
6b200bc3 | 356 | secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID); |
d8f41ccd A |
357 | mActive = false; |
358 | } | |
6b200bc3 A |
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 | } |