]> git.saurik.com Git - apple/security.git/blame - OSX/libsecurity_utilities/lib/machserver.cpp
Security-59306.140.5.tar.gz
[apple/security.git] / OSX / libsecurity_utilities / lib / machserver.cpp
CommitLineData
b1ab9ed8 1/*
d8f41ccd 2 * Copyright (c) 2000-2007,2011-2013 Apple Inc. All Rights Reserved.
b1ab9ed8
A
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// machserver - C++ shell for writing Mach 3 servers
27//
28#include "machserver.h"
29#include <servers/bootstrap.h>
30#include <mach/kern_return.h>
31#include <mach/message.h>
32#include <mach/mig_errors.h>
b54c578e 33#include "mach_notifyServer.h"
b1ab9ed8
A
34#include <security_utilities/debugging.h>
35#include <malloc/malloc.h>
36
37#if defined(USECFCURRENTTIME)
38# include <CoreFoundation/CFDate.h>
39#else
40# include <sys/time.h>
41#endif
42
0e1db9d1
A
43#define SEC_MACH_AUDIT_TOKEN_PID (5)
44
b1ab9ed8
A
45namespace Security {
46namespace MachPlusPlus {
47
48
49//
50// Global per-thread information
51//
52ModuleNexus< ThreadNexus<MachServer::PerThread> > MachServer::thread;
53
54
55//
56// Create a server object.
57// The resulting object is not "active", and any number of server objects
58// can be in this "prepared" state at the same time.
59//
60MachServer::MachServer()
61{ setup("(anonymous)"); }
62
63MachServer::MachServer(const char *name)
64 : mServerPort(name, bootstrap)
65{ setup(name); }
66
67MachServer::MachServer(const char *name, const Bootstrap &boot)
68 : bootstrap(boot), mServerPort(name, bootstrap)
69{ setup(name); }
70
71void MachServer::setup(const char *name)
72{
73 workerTimeout = 60 * 2; // 2 minutes default timeout
74 maxWorkerCount = 100; // sanity check limit
75 useFloatingThread = false; // tight thread management
76
77 mPortSet += mServerPort;
78}
79
80MachServer::~MachServer()
81{
82 // The ReceivePort members will clean themselves up.
83 // The bootstrap server will clear us from its map when our receive port dies.
84}
85
86
87//
88// Add and remove extra listening ports.
89// Messages directed to those ports are dispatched through the main handler.
90// To get automatic call-out to another handler, use the Handler class.
91//
92void MachServer::add(Port receiver)
93{
fa7225c8 94 secinfo("machserver", "port add: %d", receiver.port());
b1ab9ed8
A
95 mPortSet += receiver;
96}
97
98void MachServer::remove(Port receiver)
99{
fa7225c8 100 secinfo("machserver", "port remove: %d", receiver.port());
b1ab9ed8
A
101 mPortSet -= receiver;
102}
103
104
105//
106// Register for mach port notifications
107//
108void MachServer::notifyIfDead(Port port, bool doNotify) const
109{
110 if (doNotify)
111 port.requestNotify(mServerPort);
112 else
113 port.cancelNotify();
114}
115
116void MachServer::notifyIfUnused(Port port, bool doNotify) const
117{
118 if (doNotify)
119 port.requestNotify(port, MACH_NOTIFY_NO_SENDERS, true);
120 else
121 port.cancelNotify(MACH_NOTIFY_NO_SENDERS);
122}
123
124
125//
126// Initiate service.
127// This call will take control of the current thread and use it to service
128// incoming requests. The thread will not be released until an error happens, which
129// will cause an exception to be thrown. In other words, this never returns normally.
130// We may also be creating additional threads to service concurrent requests
131// as appropriate.
132// @@@ Msg-errors in additional threads are not acted upon.
133//
427c49bc 134void MachServer::run(mach_msg_size_t maxSize, mach_msg_options_t options)
b1ab9ed8
A
135{
136 // establish server-global (thread-shared) parameters
137 mMaxSize = maxSize;
138 mMsgOptions = options;
139
140 // establish the thread pool state
141 // (don't need managerLock since we're the only thread as of yet)
142 idleCount = workerCount = 1;
143 nextCheckTime = Time::now() + workerTimeout;
144 leastIdleWorkers = 1;
145 highestWorkerCount = 1;
146
147 // run server loop in initial (immortal) thread
fa7225c8 148 secinfo("machserver", "start thread");
b1ab9ed8 149 runServerThread(false);
fa7225c8 150 secinfo("machserver", "end thread");
b1ab9ed8
A
151
152 // primary server thread exited somehow (not currently possible)
153 assert(false);
154}
155
156
157//
158// This is the core of a server thread at work. It takes over the thread until
159// (a) an error occurs, throwing an exception
160// (b) low-load timeout happens, causing a normal return (doTimeout only)
161// This code was once based on mach_msg_server.c, but it is getting harder to notice
162// the lingering resemblance.
163//
164extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out);
165
166void MachServer::runServerThread(bool doTimeout)
167{
168 // allocate request/reply buffers
169 Message bufRequest(mMaxSize);
170 Message bufReply(mMaxSize);
171
172 // all exits from runServerThread are through exceptions
173 try {
174 // register as a worker thread
175 perThread().server = this;
176
177 for (;;) {
178 // progress hook
179 eventDone();
180
181 // process all pending timers
427c49bc 182 while (processTimer()) {}
b1ab9ed8
A
183
184 // check for worker idle timeout
185 { StLock<Mutex> _(managerLock);
186 // record idle thread low-water mark in scan interval
187 if (idleCount < leastIdleWorkers)
188 leastIdleWorkers = idleCount;
189
190 // perform self-timeout processing
191 if (doTimeout) {
192 if (workerCount > maxWorkerCount) // someone reduced maxWorkerCount recently...
193 break; // ... so release this thread immediately
194 Time::Absolute rightNow = Time::now();
195 if (rightNow >= nextCheckTime) { // reaping period complete; process
196 UInt32 idlers = leastIdleWorkers;
fa7225c8 197 secinfo("machserver", "reaping workers: %d %d", (uint32_t) workerCount, (uint32_t) idlers);
b1ab9ed8
A
198 nextCheckTime = rightNow + workerTimeout;
199 leastIdleWorkers = INT_MAX;
200 if (idlers > 1) // multiple idle threads throughout measuring interval...
201 break; // ... so release this thread now
202 }
203 }
204 }
205
206 // determine next timeout (if any)
207 bool indefinite = false;
208 Time::Interval timeout = workerTimeout;
209 { StLock<Mutex> _(managerLock);
210 if (timers.empty()) {
211 indefinite = !doTimeout;
212 } else {
213 timeout = max(Time::Interval(0), timers.next() - Time::now());
214 if (doTimeout && workerTimeout < timeout)
215 timeout = workerTimeout;
216 }
217 }
fa7225c8 218
b1ab9ed8
A
219 // receive next IPC request (or wait for timeout)
220 mach_msg_return_t mr = indefinite ?
221 mach_msg_overwrite(bufRequest,
222 MACH_RCV_MSG | mMsgOptions,
223 0, mMaxSize, mPortSet,
224 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
225 (mach_msg_header_t *) 0, 0)
226 :
227 mach_msg_overwrite(bufRequest,
228 MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_INTERRUPT | mMsgOptions,
229 0, mMaxSize, mPortSet,
230 mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL,
231 (mach_msg_header_t *) 0, 0);
232
233 switch (mr) {
234 case MACH_MSG_SUCCESS:
235 // process received request message below
236 break;
237 default:
fa7225c8 238 secinfo("machserver", "received error: %d", mr);
b1ab9ed8
A
239 continue;
240 }
241
79b9da22
A
242 // reset the buffer each time, handlers don't consistently set out params
243 bufReply.clearBuffer();
244
b1ab9ed8
A
245 // process received message
246 if (bufRequest.msgId() >= MACH_NOTIFY_FIRST &&
247 bufRequest.msgId() <= MACH_NOTIFY_LAST) {
248 // mach kernel notification message
249 // we assume this is quick, so no thread arbitration here
0e1db9d1
A
250 mach_msg_audit_trailer_t *tlr = bufRequest.auditTrailer();
251 if (tlr == NULL || tlr->msgh_audit.val[SEC_MACH_AUDIT_TOKEN_PID] != 0) {
252 secnotice("machserver", "ignoring invalid notify message");
253 continue;
254 }
b1ab9ed8
A
255 cdsa_notify_server(bufRequest, bufReply);
256 } else {
257 // normal request message
258 StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this);
fa7225c8 259 secinfo("machserver", "begin request: %d, %d", bufRequest.localPort().port(), bufRequest.msgId());
b1ab9ed8
A
260
261 // try subsidiary handlers first
262 bool handled = false;
263 for (HandlerSet::const_iterator it = mHandlers.begin();
264 it != mHandlers.end(); it++)
265 if (bufRequest.localPort() == (*it)->port()) {
266 (*it)->handle(bufRequest, bufReply);
267 handled = true;
268 }
269 if (!handled) {
270 // unclaimed, send to main handler
271 handle(bufRequest, bufReply);
272 }
273
fa7225c8 274 secinfo("machserver", "end request");
b1ab9ed8
A
275 }
276
277 // process reply generated by handler
278 if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) &&
279 bufReply.returnCode() != KERN_SUCCESS) {
280 if (bufReply.returnCode() == MIG_NO_REPLY)
281 continue;
282 // don't destroy the reply port right, so we can send an error message
283 bufRequest.remotePort(MACH_PORT_NULL);
284 mach_msg_destroy(bufRequest);
285 }
286
287 if (bufReply.remotePort() == MACH_PORT_NULL) {
288 // no reply port, so destroy the reply
289 if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX)
290 bufReply.destroy();
291 continue;
292 }
293
294 /*
295 * We don't want to block indefinitely because the client
296 * isn't receiving messages from the reply port.
297 * If we have a send-once right for the reply port, then
298 * this isn't a concern because the send won't block.
299 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
300 * To avoid falling off the kernel's fast RPC path unnecessarily,
301 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
302 */
b54c578e 303 mr = mach_msg_overwrite(bufReply,
b1ab9ed8
A
304 (MACH_MSGH_BITS_REMOTE(bufReply.bits()) ==
305 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
306 MACH_SEND_MSG | mMsgOptions :
307 MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions,
308 bufReply.length(), 0, MACH_PORT_NULL,
309 0, MACH_PORT_NULL, NULL, 0);
b54c578e
A
310 switch (mr) {
311 case MACH_MSG_SUCCESS:
312 break;
313 case MACH_SEND_INVALID_DEST:
314 case MACH_SEND_TIMED_OUT:
fa7225c8 315 secinfo("machserver", "send error: %d %d", mr, bufReply.remotePort().port());
b54c578e
A
316 bufReply.destroy();
317 break;
318 default:
319 secinfo("machserver", "send error: %d %d", mr, bufReply.remotePort().port());
320 break;
321 }
b1ab9ed8
A
322
323
324 // clean up after the transaction
325 releaseDeferredAllocations();
326 }
327 perThread().server = NULL;
328
329 } catch (...) {
330 perThread().server = NULL;
331 throw;
332 }
333}
334
335
336//
337// Manage subsidiary port handlers
338//
339void MachServer::add(Handler &handler)
340{
341 assert(mHandlers.find(&handler) == mHandlers.end());
342 assert(handler.port() != MACH_PORT_NULL);
343 mHandlers.insert(&handler);
344 mPortSet += handler.port();
345}
346
347void MachServer::remove(Handler &handler)
348{
349 assert(mHandlers.find(&handler) != mHandlers.end());
350 mHandlers.erase(&handler);
351 mPortSet -= handler.port();
352}
353
354
355//
356// Abstract auxiliary message handlers
357//
358MachServer::Handler::~Handler()
359{ /* virtual */ }
360
361
362//
363// Implement a Handler that sends no reply
364//
365boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out)
366{
367 // set up reply message to be valid (enough) and read "do not send reply"
368 out->msgh_bits = 0;
369 out->msgh_remote_port = MACH_PORT_NULL;
370 out->msgh_size = sizeof(mig_reply_error_t);
371 ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY;
372
373 // call input-only handler
374 return handle(in);
375}
376
377
378//
379// Register a memory block for deferred release.
380//
381void MachServer::releaseWhenDone(Allocator &alloc, void *memory)
382{
383 if (memory) {
384 set<Allocation> &releaseSet = perThread().deferredAllocations;
385 assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end());
fa7225c8 386 secinfo("machserver", "allocing register %p with alloc %p", memory, &alloc);
b1ab9ed8
A
387 releaseSet.insert(Allocation(memory, alloc));
388 }
389}
390
391
392//
393// Run through the accumulated deferred allocations and release them.
394// This is done automatically on every pass through the server loop;
395// it must be called by subclasses that implement their loop in some
396// other way.
397// @@@X Needs to be thread local
398//
399void MachServer::releaseDeferredAllocations()
400{
401 set<Allocation> &releaseSet = perThread().deferredAllocations;
402 for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) {
fa7225c8
A
403 secinfo("machserver", "releasing alloc at %p with %p", it->addr, it->allocator);
404
b1ab9ed8
A
405 // before we release the deferred allocation, zap it so that secrets aren't left in memory
406 size_t memSize = malloc_size(it->addr);
407 bzero(it->addr, memSize);
408 it->allocator->free(it->addr);
409 }
410 releaseSet.erase(releaseSet.begin(), releaseSet.end());
411}
412
413
414//
415// The handler function calls this if it realizes that it might be blocked
416// (or doing something that takes a long time). We respond by ensuring that
417// at least one more thread is ready to serve requests.
418// Calls the threadLimitReached callback in the server object if the thread
419// limit has been exceeded and a needed new thread was not created.
420//
421void MachServer::longTermActivity()
422{
423 if (!useFloatingThread) {
424 StLock<Mutex> _(managerLock);
425 ensureReadyThread();
426 }
427}
428
429void MachServer::busy()
430{
431 StLock<Mutex> _(managerLock);
432 idleCount--;
433 if (useFloatingThread)
434 ensureReadyThread();
435}
436
437void MachServer::idle()
438{
439 StLock<Mutex> _(managerLock);
440 idleCount++;
441}
442
443
444void MachServer::ensureReadyThread()
445{
446 if (idleCount == 0) {
447 if (workerCount >= maxWorkerCount) {
448 this->threadLimitReached(workerCount); // call remedial handler
449 }
450 if (workerCount < maxWorkerCount) { // threadLimit() may have raised maxWorkerCount
451 (new LoadThread(*this))->run();
452 }
453 }
454}
455
456
457//
458// The callback hook for our subclasses.
459// The default does nothing, thereby denying further thread creation.
460// You could do something like maxThreads(limit+1) here to grant an variance;
461// or throw an exception to avoid possible deadlocks (this would abort the current
462// request but not otherwise impact the server's operation).
463//
464void MachServer::threadLimitReached(UInt32 limit)
465{
466}
467
468
469//
470// What our (non-primary) load threads do
471//
472void MachServer::LoadThread::action()
473{
474 //@@@ race condition?! can server exit before helpers thread gets here?
475
476 // register the worker thread and go
477 server.addThread(this);
478 try {
fa7225c8 479 secinfo("machserver", "start thread");
b1ab9ed8 480 server.runServerThread(true);
fa7225c8 481 secinfo("machserver", "end thread");
b1ab9ed8
A
482 } catch (...) {
483 // fell out of server loop by error. Let the thread go quietly
fa7225c8 484 secinfo("machserver", "end thread (due to error)");
b1ab9ed8
A
485 }
486 server.removeThread(this);
487}
488
489
490//
491// Thread accounting
492//
493void MachServer::addThread(Thread *thread)
494{
495 StLock<Mutex> _(managerLock);
496 workerCount++;
497 idleCount++;
498 workers.insert(thread);
499}
500
501void MachServer::removeThread(Thread *thread)
502{
503 StLock<Mutex> _(managerLock);
504 workerCount--;
505 idleCount--;
506 workers.erase(thread);
507}
508
509
510//
511// Timer management
512//
513MachServer::Timer::~Timer()
514{ }
515
516void MachServer::Timer::select()
517{ }
518
519void MachServer::Timer::unselect()
520{ }
521
522bool MachServer::processTimer()
523{
524 Timer *top;
525 { StLock<Mutex> _(managerLock); // could have multiple threads trying this
526 if (!(top = static_cast<Timer *>(timers.pop(Time::now()))))
527 return false; // nothing (more) to be done now
528 } // drop lock; work has been retrieved
529 try {
fa7225c8 530 secinfo("machserver", "timer start: %p, %d, %f", top, top->longTerm(), Time::now().internalForm());
b1ab9ed8
A
531 StLock<MachServer::Timer,
532 &MachServer::Timer::select, &MachServer::Timer::unselect> _t(*top);
533 if (top->longTerm()) {
534 StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this);
535 top->action();
536 } else {
537 top->action();
538 }
fa7225c8 539 secinfo("machserver", "timer end (false)");
b1ab9ed8 540 } catch (...) {
fa7225c8 541 secinfo("machserver", "timer end (true)");
b1ab9ed8
A
542 }
543 return true;
544}
545
546void MachServer::setTimer(Timer *timer, Time::Absolute when)
547{
548 StLock<Mutex> _(managerLock);
549 timers.schedule(timer, when);
550}
551
552void MachServer::clearTimer(Timer *timer)
553{
554 StLock<Mutex> _(managerLock);
555 if (timer->scheduled())
556 timers.unschedule(timer);
557}
558
559
560//
561// Notification hooks and shims. Defaults do nothing.
562//
79b9da22 563kern_return_t cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port)
b1ab9ed8
A
564{
565 try {
566 MachServer::active().notifyDeadName(port);
567 } catch (...) {
568 }
dbe77505
A
569 // the act of receiving a dead name notification allocates a dead-name
570 // right that must be deallocated
571 mach_port_deallocate(mach_task_self(), port);
79b9da22 572 return KERN_SUCCESS;
b1ab9ed8
A
573}
574
575void MachServer::notifyDeadName(Port) { }
576
79b9da22 577kern_return_t cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port)
b1ab9ed8
A
578{
579 try {
580 MachServer::active().notifyPortDeleted(port);
581 } catch (...) {
582 }
79b9da22 583 return KERN_SUCCESS;
b1ab9ed8
A
584}
585
586void MachServer::notifyPortDeleted(Port) { }
587
79b9da22 588kern_return_t cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port)
b1ab9ed8
A
589{
590 try {
591 MachServer::active().notifyPortDestroyed(port);
592 } catch (...) {
593 }
79b9da22 594 return KERN_SUCCESS;
b1ab9ed8
A
595}
596
597void MachServer::notifyPortDestroyed(Port) { }
598
79b9da22 599kern_return_t cdsa_mach_notify_send_once(mach_port_t port)
b1ab9ed8
A
600{
601 try {
602 MachServer::active().notifySendOnce(port);
603 } catch (...) {
604 }
79b9da22 605 return KERN_SUCCESS;
b1ab9ed8
A
606}
607
608void MachServer::notifySendOnce(Port) { }
609
79b9da22 610kern_return_t cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count)
b1ab9ed8
A
611{
612 try {
613 MachServer::active().notifyNoSenders(port, count);
614 } catch (...) {
615 }
79b9da22 616 return KERN_SUCCESS;
b1ab9ed8
A
617}
618
619void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { }
620
621void MachServer::eventDone() { }
622
623
624} // end namespace MachPlusPlus
625
626} // end namespace Security