source: trunk/Cocoa/Pester/Source/MoreSecurity/MoreSecurity.c@ 118

Last change on this file since 118 was 118, checked in by Nicholas Riley, 21 years ago

Broken, to-be-removed authorization implementation

File size: 52.9 KB
Line 
1/*
2 File: MoreSecurity.c
3
4 Contains: Security utilities.
5
6 Written by: Quinn
7
8 Copyright: Copyright (c) 2002 by Apple Computer, Inc., All Rights Reserved.
9
10 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
11 ("Apple") in consideration of your agreement to the following terms, and your
12 use, installation, modification or redistribution of this Apple software
13 constitutes acceptance of these terms. If you do not agree with these terms,
14 please do not use, install, modify or redistribute this Apple software.
15
16 In consideration of your agreement to abide by the following terms, and subject
17 to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
18 copyrights in this original Apple software (the "Apple Software"), to use,
19 reproduce, modify and redistribute the Apple Software, with or without
20 modifications, in source and/or binary forms; provided that if you redistribute
21 the Apple Software in its entirety and without modifications, you must retain
22 this notice and the following text and disclaimers in all such redistributions of
23 the Apple Software. Neither the name, trademarks, service marks or logos of
24 Apple Computer, Inc. may be used to endorse or promote products derived from the
25 Apple Software without specific prior written permission from Apple. Except as
26 expressly stated in this notice, no other rights or licenses, express or implied,
27 are granted by Apple herein, including but not limited to any patent rights that
28 may be infringed by your derivative works or by other works in which the Apple
29 Software may be incorporated.
30
31 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
32 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
33 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
35 COMBINATION WITH YOUR PRODUCTS.
36
37 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
38 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
39 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
41 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
42 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
43 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
45 Change History (most recent first):
46
47$Log: MoreSecurity.c,v $
48Revision 1.5 2002/12/12 23:15:23 eskimo1
49Switch EUID back to RUID after we return from the commandProc, just in case the client left it set the wrong way.
50
51Revision 1.4 2002/12/12 15:41:53 eskimo1
52Eliminate MoreAuthCopyRightCFString because it makes no sense. A helper tool should always have the right name hard-wired into it, and hardwiring a C string is even easier than hardwiring a CFString. Also added some more debugging printfs.
53
54Revision 1.3 2002/11/25 16:42:25 eskimo1
55Significant changes. Handle more edge cases better (for example, volumes with the "ignore permissions" flag turned on). Also brought MoreSecurity more into the CoreServices world.
56
57Revision 1.2 2002/11/14 20:27:59 eskimo1
58Compare time stamps in MoreSecCopyHelperToolURLRef to decide whether to throw away the tool and revert to the backup. This greatly improves the debugging experience. Also, in MoreSecExecuteRequestInHelperTool, add code to dispose of a partial response if we get an error (prevents a memory leak in some very specific error conditions). Finally, in MoreSecGetErrorFromResponse, eliminate an unnecessary CFRelease.
59
60Revision 1.1 2002/11/09 00:08:36 eskimo1
61First checked in. A module containing security helpers.
62
63
64*/
65
66/////////////////////////////////////////////////////////////////
67
68// Our prototypes
69
70#include "MoreSecurity.h"
71
72// System interfaces
73
74#include <unistd.h>
75#include <fcntl.h>
76#include <sys/time.h>
77#include <sys/resource.h>
78#include <sys/stat.h>
79#include <sys/wait.h>
80#include <sys/param.h>
81#include <sys/socket.h>
82
83// MIB Interfaces
84
85#include "MoreUNIX.h"
86#include "MoreCFQ.h"
87
88/////////////////////////////////////////////////////////////////
89#pragma mark ***** UID Management
90
91extern int MoreSecPermanentlySetNonPrivilegedEUID(void)
92 // See comment in header.
93{
94 int err;
95
96 err = setuid(getuid());
97 if (err != 0) {
98 err = errno;
99 }
100 return err;
101}
102
103extern int MoreSecTemporarilySetNonPrivilegedEUID(void)
104 // See comment in header.
105{
106 int err;
107
108 err = seteuid(getuid());
109 if (err != 0) {
110 err = errno;
111 }
112 return err;
113}
114
115extern int MoreSecSetPrivilegedEUID(void)
116 // See comment in header.
117{
118 int err;
119
120 err = seteuid(0);
121 if (err != 0) {
122 err = errno;
123 }
124 return err;
125}
126
127/////////////////////////////////////////////////////////////////
128#pragma mark ***** MoreSecDestroyInheritedEnvironment
129
130static int GetPathToSelf(char **pathToSelfPtr)
131 // A simple wrapper around MoreGetExecutablePath which returns
132 // the path in a memory block that you must free.
133{
134 int err;
135 size_t pathSize;
136 char junkChar;
137
138 assert( pathToSelfPtr != NULL);
139 assert(*pathToSelfPtr == NULL);
140
141 *pathToSelfPtr = &junkChar;
142
143 pathSize = 0;
144 err = MoreGetExecutablePath(*pathToSelfPtr, &pathSize);
145 if (pathSize == 0) {
146 assert(err != 0);
147 if (err != 0) {
148 err = -1;
149 }
150 *pathToSelfPtr = NULL;
151 } else {
152 pathSize += 1;
153
154 err = 0;
155 *pathToSelfPtr = (char *) malloc(pathSize);
156 if ( *pathToSelfPtr == NULL ) {
157 err = ENOMEM;
158 }
159
160 if (err == 0) {
161 err = MoreGetExecutablePath(*pathToSelfPtr, &pathSize);
162 }
163
164 if (err != 0) {
165 free(*pathToSelfPtr);
166 *pathToSelfPtr = NULL;
167 }
168 }
169
170 assert( (err == 0) == (*pathToSelfPtr != NULL) );
171
172 return err;
173}
174
175static int ResetArgvZero(const char **argv)
176 // Sets argv[0] to be the true path to the executable
177 // rather than a potentially hostile value supplied by
178 // our parent process.
179{
180 int err;
181 char * execPath;
182
183 execPath = NULL;
184
185 err = GetPathToSelf(&execPath);
186
187 // Copy the real executable path into argv[0].
188
189 if (err == 0) {
190 argv[0] = execPath;
191 }
192
193 // If we got an error then free any buffer we may have
194 // allocated. On no error we end up leaking execPath, but
195 // that's acceptable because this function is usually only
196 // called once at process startup.
197
198 if (err != 0) {
199 free(execPath);
200 }
201
202 return err;
203}
204
205extern char **environ;
206
207static int ResetEnvironment(void)
208 // Clears all environment variables. There are circumstances
209 // where your process might depend on certain environment
210 // variables being set correctly, but if that's the case you
211 // shouldn't be relying on inheriting good values from an
212 // untrusted parent. You should, instead, set the environment
213 // variables explicitly after calling
214 // MoreSecDestroyInheritedEnvironment.
215{
216 while ( environ[0] != NULL ) {
217 unsetenv(environ[0]);
218 }
219 return 0;
220}
221
222static int CloseOpenFileDescriptorsInRange(int start, int limit)
223 // Closes all the file descriptor in the range
224 // start <= fd < limit.
225{
226 int err;
227 int fd;
228
229 err = 0;
230 for (fd = start; fd < limit; fd++) {
231 err = close(fd);
232 if (err == -1) {
233 err = errno;
234 }
235 if (err == EBADF) {
236 err = 0;
237 }
238 if (err != 0) {
239 break;
240 }
241 }
242 return err;
243}
244
245static int ResetAllSignalsToDefault(void)
246 // Resets all signals to their default actions and the signal
247 // mask to its default value (empty). If you use signals in
248 // your program you should establish your signal handlers
249 // after calling MoreSecDestroyInheritedEnvironment.
250{
251 int err;
252 int sig;
253 sigset_t empty;
254
255 // First set all of the signals to their default actions.
256
257 err = 0;
258 for (sig = 0; sig < NSIG; sig++) {
259 if ( signal(sig, SIG_DFL) == SIG_ERR ) {
260 if ( sig == 0
261 || sig == SIGKILL
262 || sig == SIGSTOP) {
263 // ignore the error
264 } else {
265 err = errno;
266 break;
267 }
268 }
269 }
270
271 // Then set the signal mask to its default value (empty).
272
273 if (err == 0) {
274 err = sigemptyset(&empty);
275 if (err == -1) {
276 err = errno;
277 }
278 }
279 if (err == 0) {
280 err = sigprocmask(SIG_SETMASK, &empty, NULL);
281 if (err == -1) {
282 err = errno;
283 }
284 }
285
286 return err;
287}
288
289// The following is a table of resource limits established
290// by this program. This table is based on the default values
291// from Mac OS X 10.1.x. Unfortunately there's no way to
292// determine the system-wide defaults programmatically.
293
294typedef struct {
295 int resource;
296 rlim_t rlim_cur;
297 rlim_t rlim_max;
298} ResourceLimitTemplate;
299
300static ResourceLimitTemplate kResourceLimits[9] = {
301 {RLIMIT_CPU, RLIM_INFINITY, RLIM_INFINITY},
302 {RLIMIT_FSIZE, RLIM_INFINITY, RLIM_INFINITY},
303 {RLIMIT_DATA, 0x600000, RLIM_INFINITY},
304 {RLIMIT_STACK, 0x80000, 0x4000000 },
305 {RLIMIT_CORE, 0, RLIM_INFINITY},
306 {RLIMIT_RSS, RLIM_INFINITY, RLIM_INFINITY},
307 {RLIMIT_MEMLOCK, RLIM_INFINITY, RLIM_INFINITY},
308 {RLIMIT_NPROC, 100, RLIM_INFINITY},
309 {RLIMIT_NOFILE, 256, RLIM_INFINITY}
310};
311
312// The upper bound for RLIMIT_NPROC is really 100,
313// or 532 if you're EUID 0.
314//
315// The upper bound for RLIMIT_NOFILE is really 10240,
316// or 12288 if you're EUID 0.
317
318static const int kResourceLimitsCount =
319 sizeof(kResourceLimits) / sizeof(kResourceLimits[0]);
320
321static int ResetAllResourceLimitsToDefault(void)
322 // Resets all resource limits to their defaults,
323 // based on the kResourceLimits table. Note that we
324 // only attempt to set the rlim_max if we're EUID 0
325 // because getrlimit sometimes does not return the
326 // real maximum limit [2941095].
327{
328 int err;
329 int i;
330
331 err = 0;
332 for (i = 0; i < kResourceLimitsCount; i++) {
333 struct rlimit thisLimit;
334
335 err = getrlimit(kResourceLimits[i].resource,
336 &thisLimit);
337 if (err == -1) {
338 err = errno;
339 }
340 if (err == 0) {
341 thisLimit.rlim_cur = kResourceLimits[i].rlim_cur;
342 if (geteuid() == 0) {
343 thisLimit.rlim_max = kResourceLimits[i].rlim_max;
344 }
345 err = setrlimit(kResourceLimits[i].resource,
346 &thisLimit);
347 if (err == -1 ) {
348 err = errno;
349 }
350 }
351 if (err != 0) {
352 break;
353 }
354 }
355
356 return err;
357}
358
359static int ResetAllTimers(void)
360 // Disables all interval timers that might have
361 // been inherited from the parent process.
362{
363 int err;
364 struct itimerval disable;
365
366 timerclear(&disable.it_interval);
367 timerclear(&disable.it_value);
368
369 err = setitimer(ITIMER_REAL, &disable, NULL);
370 if (err == -1) {
371 err = errno;
372 }
373 err = setitimer(ITIMER_VIRTUAL, &disable, NULL);
374 if (err == -1) {
375 err = errno;
376 }
377 err = setitimer(ITIMER_PROF, &disable, NULL);
378 if (err == -1) {
379 err = errno;
380 }
381 return err;
382}
383
384extern int MoreSecDestroyInheritedEnvironment(int whatToDubya, const char **argv)
385 // See comment in header.
386{
387 int err;
388
389 assert( (argv != NULL)
390 || ((whatToDubya & kMoreSecKeepArg0Mask) != 0) );
391
392 err = 0;
393
394 if ( (err == 0)
395 && !(whatToDubya & kMoreSecKeepArg0Mask) ) {
396 err = ResetArgvZero(argv);
397 }
398 if ( (err == 0)
399 && !(whatToDubya & kMoreSecKeepEnvironmentMask) ) {
400 err = ResetEnvironment();
401 }
402 if ( (err == 0)
403 && !(whatToDubya & kMoreSecKeepStandardFilesMask) ) {
404 err = CloseOpenFileDescriptorsInRange(0, 3);
405 }
406 if ( (err == 0)
407 && !(whatToDubya & kMoreSecKeepOtherFilesMask) ) {
408 err = CloseOpenFileDescriptorsInRange(
409 3,
410 getdtablesize());
411 }
412 if ( (err == 0)
413 && !(whatToDubya & kMoreSecKeepSignalsMask) ) {
414 err = ResetAllSignalsToDefault();
415 }
416 if ( (err == 0)
417 && !(whatToDubya & kMoreSecKeepUmaskMask) ) {
418 (void) umask(S_IRWXG | S_IRWXO);
419 }
420 if ( (err == 0)
421 && !(whatToDubya & kMoreSecKeepNiceMask) ) {
422 err = nice(0);
423 if (err == -1) {
424 err = errno;
425 }
426 }
427 if ( (err == 0)
428 && !(whatToDubya & kMoreSecKeepResourceLimitsMask) ) {
429 err = ResetAllResourceLimitsToDefault();
430 }
431 if ( (err == 0)
432 && !(whatToDubya & kMoreSecKeepCurrentDirMask) ) {
433 err = chdir("/");
434 if (err == -1) {
435 err = errno;
436 }
437 }
438 if ( (err == 0)
439 && !(whatToDubya & kMoreSecKeepTimersMask) ) {
440 err = ResetAllTimers();
441 }
442
443 return err;
444}
445
446/////////////////////////////////////////////////////////////////
447#pragma mark ***** Helper Tool Common
448
449static OSStatus CopyDictionaryFromDescriptor(int fdIn, CFDictionaryRef *dictResult)
450 // Create a CFDictionary by reading the XML data from fdIn.
451 // It first reads the size of the XML data, then allocates a
452 // buffer for that data, then reads the data in, and finally
453 // unflattens the data into a CFDictionary.
454 //
455 // See also the companion routine, WriteDictionaryToDescriptor, below.
456{
457 OSStatus err;
458 CFIndex dictSize;
459 UInt8 * dictBuffer;
460 CFDataRef dictData;
461 CFPropertyListRef dict;
462
463 assert(fdIn >= 0);
464 assert( dictResult != NULL);
465 assert(*dictResult == NULL);
466
467 dictBuffer = NULL;
468 dictData = NULL;
469 dict = NULL;
470
471 // Read the data size and allocate a buffer.
472
473 err = EXXXToOSStatus( MoreUNIXRead(fdIn, &dictSize, sizeof(dictSize), NULL) );
474 if (err == noErr) {
475 // Abitrary limit to prevent potentially hostile client overwhelming us with data.
476 if (dictSize > (1 * 1024 * 1024)) {
477 err = memFullErr;
478 }
479 }
480 if (err == noErr) {
481 dictBuffer = (UInt8 *) malloc( (size_t) dictSize);
482 if (dictBuffer == NULL) {
483 err = memFullErr;
484 }
485 }
486
487 // Read the data and unflatten.
488
489 if (err == noErr) {
490 err = EXXXToOSStatus( MoreUNIXRead(fdIn, dictBuffer, (size_t) dictSize, NULL) );
491 }
492 if (err == noErr) {
493 dictData = CFDataCreateWithBytesNoCopy(NULL, dictBuffer, dictSize, kCFAllocatorNull);
494 err = CFQError(dictData);
495 }
496 if (err == noErr) {
497 dict = CFPropertyListCreateFromXMLData(NULL, dictData, kCFPropertyListImmutable, NULL);
498 err = CFQError(dict);
499 }
500 if ( (err == noErr) && (CFGetTypeID(dict) != CFDictionaryGetTypeID()) ) {
501 err = paramErr; // only CFDictionaries need apply
502 }
503 // CFShow(dict);
504
505 // Clean up.
506
507 if (err != noErr) {
508 CFQRelease(dict);
509 dict = NULL;
510 }
511 *dictResult = (CFDictionaryRef) dict;
512
513 free(dictBuffer);
514 CFQRelease(dictData);
515
516 assert( (err == noErr) == (*dictResult != NULL) );
517
518 return err;
519}
520
521static OSStatus WriteDictionaryToDescriptor(CFDictionaryRef dict, int fdOut)
522 // Write a dictionary to a file descriptor by flattening
523 // it into XML. Send the size of the XML before sending
524 // the data so that CopyDictionaryFromDescriptor knows
525 // how much to read.
526{
527 OSStatus err;
528 CFDataRef dictData;
529 CFIndex dictSize;
530 UInt8 * dictBuffer;
531
532 assert(dict != NULL);
533 assert(fdOut >= 0);
534
535 dictData = NULL;
536 dictBuffer = NULL;
537
538 dictData = CFPropertyListCreateXMLData(NULL, dict);
539 err = CFQError(dictData);
540
541 // Allocate sizeof(size_t) extra bytes in the buffer so that we can
542 // prepend the dictSize. This allows us to write the entire
543 // dict with one MoreUNIXWrite call, which definitely speeds
544 // things up, especially if this is was going over a real wire.
545 // Of course, if I was to send this over a real wire, I would
546 // have to guarantee that dictSize was sent in network byte order (-:
547
548 if (err == noErr) {
549 dictSize = CFDataGetLength(dictData);
550 dictBuffer = (UInt8 *) malloc( sizeof(size_t) + dictSize );
551 if (dictBuffer == NULL) {
552 err = memFullErr;
553 }
554 }
555
556 if (err == noErr) {
557 // Copy dictSize into the first size_t bytes of the buffer.
558
559 *((size_t *) dictBuffer) = (size_t) dictSize;
560
561 // Copy the data into the remaining bytes.
562 //
563 // Can't use CFDataGetBytePtr because there's no guarantee that
564 // it will succeed. If it doesn't, we have to copy the bytes anyway,
565 // so the allocation code has to exist. Given that this isn't a
566 // performance critical path, I might as well minimise my code size by
567 // always running the allocation code.
568
569 CFDataGetBytes(dictData, CFRangeMake(0, dictSize), dictBuffer + sizeof(size_t));
570
571 err = EXXXToOSStatus( MoreUNIXWrite(fdOut, dictBuffer, sizeof(size_t) + dictSize, NULL) );
572 }
573
574 free(dictBuffer);
575 CFQRelease(dictData);
576
577 return err;
578}
579
580extern int MoreSecErrorToHelperToolResult(int errNum)
581 // See comment in header.
582{
583 int result;
584
585 switch (errNum) {
586 case 0:
587 result = 0;
588 break;
589 case errAuthorizationDenied:
590 result = (kMoreSecResultPrivilegesErr - kMoreSecResultBase);
591 break;
592 case errAuthorizationCanceled:
593 result = (kMoreSecResultCanceledErr - kMoreSecResultBase);
594 break;
595 default:
596 if ( (errNum >= kMoreSecFirstResultErr) && (errNum <= kMoreSecLastResultErr) ) {
597 result = (errNum - kMoreSecResultBase);
598 } else {
599 result = (kMoreSecResultInternalErrorErr - kMoreSecResultBase);
600 }
601 break;
602 }
603 return result;
604}
605
606extern int MoreSecHelperToolResultToError(int toolResult)
607 // See comment in header.
608{
609 int err;
610
611 if (toolResult == 0) {
612 err = 0;
613 } else {
614 if ( (toolResult > 0) && (toolResult <= (kMoreSecLastResultErr - kMoreSecResultBase)) ) {
615 err = (toolResult + kMoreSecResultBase);
616 } else {
617 err = kMoreSecResultInternalErrorErr;
618 }
619 }
620 return err;
621}
622
623/////////////////////////////////////////////////////////////////
624#pragma mark ***** Implementation Helper Tool
625
626// Notes on Code Signing
627// ---------------------
628// I've decided *not* to implementing a digital signature verification
629// as part of this library. There are a number of technical reasons
630// that would make digitally signing the code tricky (such as prebinding),
631// but I believe that all of those are surmountable. The reasons I didn't
632// implementing digital signatures are:
633//
634// A) it doesn't improve the security if I implement it here,
635// B) it's extra work for me, and
636// C) its presence might lead folks to believe that this is more
637// secure than it really is.
638//
639// The most critical point is point A. I'll spend a little time explaining
640// that here.
641//
642// No matter what you do, the current AuthorizationExecuteWithPrivileges
643// model allows for security violations [3093666]. Specifically, AEWP lets
644// you run a non-privileged helper tool as if it was privileged. However, in
645// the time between the point where you call AEWP (at the point, SigCheck1,
646// below) and the point where the helper tool runs and changes its own
647// permissions to prevent tampering (SigCheck2), there's a window of opportunity
648// where an attacker can modify the tool at will, and the modified tool will be
649// run as root. They could, for example, open a read/write file descriptor
650// that allows them to modify the tool. Even if the tool checks its own
651// integrity with a digital signature (at the point SigCheck2, below), there's
652// no way it can revoke the read/write file descriptor, so the attacker could
653// just modify the tool after the digital signature check.
654//
655// Moreover, there are even simpler attacks. For example, an attacker
656// could just delete the application's current helper tool and replace
657// the application's template copy of the tool with its own. The application
658// will quite happily launch that tool, at which point the tool can use
659// AEWP to prompt for the admin password and can launch any program in
660// privileged mode. The user is not going to be be able to distinguish
661// between the attacker's tool call AEWP and the real helper tool.
662//
663// You could defeat this second attack by digitally signing the helper tool,
664// but that doesn't really help because if the attacker can change the
665// application program (which is a precondition of being able to substitute
666// a helper tool), they can replace your digital signature with theirs.
667// One way around this would be to use a certificate to verify the
668// authenticity of the digital signature, but that's beyond what I'm
669// prepared to do for sample code.
670//
671// So, rather than write a lot of code to provide a false sense of security,
672// I've decided to simply ignore the issue of digital signatures altogether.
673
674// File Modes
675// ----------
676// The following is a declaration of constants that I use when setting and checking
677// the file mode (ie permissions). It's an extreme use of whitespace, but
678// I found the layout helpful.
679
680enum {
681 // The mode_t that we set for the helper tool via fchmod.
682
683 kSetHelperToolPerms = 0 // r-sr-xr-x
684 | S_ISUID
685// | S_ISGID
686// | S_ISVTX
687
688 | S_IRUSR
689// | S_IWUSR
690 | S_IXUSR
691
692 | S_IRGRP
693// | S_IWGRP
694 | S_IXGRP
695
696 | S_IROTH
697// | S_IWOTH
698 | S_IXOTH
699 ,
700
701 // The mode_t that we check, via stat, to see if the helper tool is valid.
702
703 kRequiredHelperToolPerms = S_IFREG | kSetHelperToolPerms,
704
705 kRequiredHelperToolMask = 0
706 | S_IFMT
707
708 | S_ISUID
709 | S_ISGID
710 | S_ISVTX
711
712 | S_IRWXU
713 | S_IRWXG
714 | S_IRWXO
715};
716
717static int RepairOurPrivileges(const char *pathToSelf)
718 // Self-repair code. We ran ourselves using AuthorizationExecuteWithPrivileges
719 // so we need to make ourselves setuid root to avoid the need for this the
720 // next time around.
721{
722 int err;
723 int junk;
724 int fd;
725
726 assert(pathToSelf != NULL);
727
728 // We don't supply O_EXLOCK to open because that's only an advisory
729 // lock so it doesn't buy us anything. [AuthSample makes the claim
730 // that this lock is mandatory, but that's just wrong [3090303].]
731
732 fd = open(pathToSelf, O_RDONLY, 0);
733 err = MoreUNIXErrno(fd);
734
735 if (err == 0) {
736 // SigCheck2
737 //
738 // If I was to implement digital signing of the code, this is the
739 // second place I would check the signature. See note above for
740 // more information on this.
741 }
742
743 if (err == 0) {
744 // Switch to EUID 0 to do the chown/chmod.
745
746 err = MoreSecSetPrivilegedEUID();
747
748 // Make it owned by root.
749
750 if (err == 0) {
751 // GID = -1 implies no change
752 err = fchown(fd, 0, -1);
753 err = MoreUNIXErrno(err);
754 }
755
756 // Force the mode flags.
757
758 if (err == 0) {
759 err = fchmod(fd, kRequiredHelperToolPerms);
760 err = MoreUNIXErrno(err);
761 }
762
763 // Switch back to EUID != 0 once we're done.
764
765 junk = MoreSecTemporarilySetNonPrivilegedEUID();
766 assert(junk == 0);
767 }
768
769 // Clean up.
770
771 if (fd != -1) {
772 junk = close(fd);
773 assert(junk == 0);
774 }
775
776 return err;
777}
778
779static int ExecuteSelfInPrivilegedSelfRepairMode(int fdIn, int fdOut, AuthorizationRef auth, const char *pathToSelf)
780 // Execute another copy of the tool in privileged mode via
781 // AuthorizationExecuteWithPrivileges. Route the command request
782 // from fdIn to the second instance of the tool, and route the
783 // command response from the second instance of the tool to fdOut.
784{
785 int err;
786 int err2;
787 int status;
788 int junk;
789 FILE * fileConnToChild;
790 int fdConnToChild;
791 pid_t childPID;
792 static const char * const kSelfRepairArguments[] = { "--self-repair", NULL };
793
794 assert(fdIn >= 0);
795 assert(fdOut >= 0);
796 assert(auth != NULL);
797 assert(pathToSelf != NULL);
798
799 fileConnToChild = NULL;
800 childPID = -1;
801
802 err = 0;
803
804 // SigCheck1
805 //
806 // If I was to implement digital signing of the code, this is the
807 // first place I would check the signature. See note above for
808 // more information on this.
809
810 if (err == 0) {
811 #if MORE_DEBUG
812 fprintf(stderr, "MoreSecurity: Calling AEWP\n");
813 #endif
814 err = OSStatusToEXXX( AuthorizationExecuteWithPrivileges(auth, pathToSelf,
815 kAuthorizationFlagDefaults, (char * const *) kSelfRepairArguments, &fileConnToChild) );
816 #if MORE_DEBUG
817 fprintf(stderr, "MoreSecurity: AEWP returned %d\n", err);
818 #endif
819
820 // The cast for kSelfRepairArguments is required because of a bug in the prototype
821 // for AuthorizationExecuteWithPrivileges [3090294].
822 }
823
824 if (err == 0) {
825 // Extract the descriptor for the returned FILE *. As we
826 // never use the FILE * again and there is no data buffered
827 // in it, it's safe for us to use the descriptor as if it had
828 // never been embedded in a FILE *.
829
830 fdConnToChild = fileno(fileConnToChild);
831 err = MoreUNIXErrno(fdConnToChild);
832
833 // Get the PID sent to us by the child. We need to do this because
834 // AuthorizationExecuteWithPrivileges does not return us the child's
835 // PID [3090277], and we need the child PID in order to properly wait
836 // for the child to terminate.
837
838 if (err == 0) {
839 err = MoreUNIXRead(fdConnToChild, &childPID, sizeof(childPID), NULL);
840 }
841
842 // At this point we're just acting as a router between the application
843 // and the second instance of the tool we launched using AEWP. All we
844 // do is copy the request data to the tool, and then copy the result
845 // back to the app. This works because we implement a simple
846 // request/response protocol. If the protocol was more complex
847 // (for example, if the tool handled multiple requests per
848 // session), we would have to implement a more complex copying
849 // algorithm using "select".
850
851 if (err == 0) {
852 err = MoreUNIXCopyDescriptorToDescriptor(fdIn, fdConnToChild);
853 }
854
855 // Close the write side of our connection to the child. We do this so that
856 // if the child makes a mistake and tries to do a blocking read on its
857 // input for more data that we're sending it, it will see the closed socket
858 // and get EPIPE instead of blocking forever.
859
860 if (err == 0) {
861 err = shutdown(fdConnToChild, 1);
862 err = MoreUNIXErrno(err);
863 }
864
865 // Copy the response back to the app.
866
867 if (err == 0) {
868 err = MoreUNIXCopyDescriptorToDescriptor(fdConnToChild, fdOut);
869 }
870
871 // Close the connection to the child, which also closes fdConnToChild.
872
873 if (fileConnToChild != NULL) {
874 junk = fclose(fileConnToChild);
875 assert(junk == 0);
876 }
877
878 // Wait for the child to terminate. We have to do this, regardless
879 // of whether we get an error, in order to clear the zombie process.
880 //
881 // Note that we don't get the pid of the child back from
882 // AuthorizationExecuteWithPrivileges, so we have to have the child
883 // send us its PID via fdConnToChild (see the MoreUNIXRead above).
884 // Also note that there's no guarantee that the MoreUNIXRead will
885 // execute without error, thus there's no guarantee that childPID
886 // will be valid. We handle that by initialising childPID to -1
887 // in the error case, which makes "waitpid" work just like "wait",
888 // that is, wait for any child to terminate. Of course, there's
889 // no guarantee that in that case the terminating child will actually
890 // be the child we launched with AEWP. That's sad, but its
891 // the best we can do given the current problems with AEWP [3090277].
892
893 err2 = waitpid(childPID, &status, 0);
894 err2 = MoreUNIXErrno(err);
895 if (err == 0) {
896 err = err2;
897 }
898
899 // If we successfully got a wait status from the client (or
900 // our communications with the client failed because of
901 // a generic communications error), let's go see whether
902 // the child's wait status is a more appropriate source of
903 // error information.
904
905 if ( (err == 0) || (err == EPIPE) ) {
906 if ( ! WIFEXITED(status) ) {
907 // If we got a wait status but it's not a valid exit status (perhaps
908 // WIFSIGNALED, indication that the child terminated because of a signal),
909 // that's an unexpected error we can't handle.
910
911 err = kMoreSecResultInternalErrorErr;
912 } else {
913 // If we got a valid exit status from the child, map its exit status
914 // into our range so that we return an equivalent status. The helper
915 // tool's main function can use MoreSecErrorToHelperToolResult to map
916 // this back to a status code.
917
918 err = MoreSecHelperToolResultToError(WEXITSTATUS(status));
919 }
920 }
921 }
922
923 return err;
924}
925
926static int ReadAndDispatchCommand(int fdIn, int fdOut, AuthorizationRef auth, MoreSecCommandProc commandProc)
927 // Read a command from fdIn, execute the command by calling commandProc,
928 // and then return the response via fdOut.
929{
930 int err;
931 int junk;
932 CFDictionaryRef request;
933 CFDictionaryRef response;
934
935 assert(fdIn >= 0);
936 assert(fdOut >= 0);
937 assert(auth != NULL);
938 assert(commandProc != NULL);
939
940 request = NULL;
941 response = NULL;
942
943 // Read the request and convert it a CFDictionary.
944
945 err = OSStatusToEXXX( CopyDictionaryFromDescriptor(fdIn, &request) );
946
947 // Call the client's commandProc to actually execute the request.
948 // Note that we automatically put the commandProc's function result
949 // into a response dictionary if the commandProc hasn't already done so.
950 // An error from the commandProc does not indicate a failure of the
951 // helper tool itself.
952
953 if (err == 0) {
954 OSStatus commandErr;
955 CFStringRef errorKey = kMoreSecErrorNumberKey;
956
957 commandErr = commandProc(auth, (CFDictionaryRef) request, (CFDictionaryRef *) &response);
958 // fprintf(stderr, "commandErr = %ld\n", commandErr);
959
960 // If the commandProc switched to EUID 0, let's go back to EUID == RUID.
961
962 junk = MoreSecTemporarilySetNonPrivilegedEUID();
963 assert(junk == 0);
964
965 assert( (response == NULL) || (CFGetTypeID(response) == CFDictionaryGetTypeID()) );
966
967 if ( (response == NULL) || ! CFDictionaryContainsKey(response, errorKey) ) {
968 CFNumberRef commandErrNum;
969
970 commandErrNum = CFNumberCreate(NULL, kCFNumberSInt32Type, &commandErr);
971 err = OSStatusToEXXX( CFQError(commandErrNum) );
972
973 if (err == 0) {
974 if (response == NULL) {
975 response = CFDictionaryCreate(NULL, (const void **) &errorKey, (const void **) &commandErrNum, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
976 err = OSStatusToEXXX( CFQError(response) );
977 } else {
978 CFMutableDictionaryRef temp;
979
980 temp = CFDictionaryCreateMutableCopy(NULL, 0, response);
981 err = OSStatusToEXXX( CFQError(temp) );
982 if (err == 0) {
983 CFDictionarySetValue(temp, errorKey, commandErrNum);
984
985 CFQRelease(response);
986 response = temp;
987 }
988 }
989 }
990
991 CFQRelease(commandErrNum);
992 }
993
994 }
995
996 assert( (err == 0) == (response != NULL) );
997
998 // Pass the response back.
999
1000 if (err == 0) {
1001 err = OSStatusToEXXX( WriteDictionaryToDescriptor(response, fdOut) );
1002 }
1003
1004 CFQRelease(request);
1005 CFQRelease(response);
1006
1007 return err;
1008}
1009
1010extern AuthorizationRef MoreSecHelperToolCopyAuthRef(void)
1011 // See comment in header.
1012{
1013 AuthorizationRef result;
1014
1015 result = NULL;
1016
1017 (void) AuthorizationCopyPrivilegedReference(&result, kAuthorizationFlagDefaults);
1018
1019 return result;
1020}
1021
1022extern int MoreSecHelperToolMain(int fdIn, int fdOut, AuthorizationRef auth, MoreSecCommandProc commandProc, int argc, const char *argv[])
1023 // See comment in header.
1024{
1025 int err;
1026 OSStatus junk;
1027 char * pathToSelf;
1028 Boolean privileged;
1029
1030 assert(fdIn >= 0);
1031 assert(fdOut >= 0);
1032 assert(commandProc != NULL);
1033 assert( (argc == 1) || (argc == 2) );
1034 assert(argv != NULL);
1035 assert(argv[0] != NULL);
1036
1037 err = 0;
1038 pathToSelf = NULL;
1039
1040 // Note whether we're privileged, and then switch the EUID to the RUID
1041 // so that the rest of the tool runs with a non-zero EUID unless it
1042 // specifically requests privileges via MoreSecSetPrivilegedEUID.
1043 // This makes things just a little bit safer.
1044
1045 privileged = (geteuid() == 0);
1046 err = MoreSecTemporarilySetNonPrivilegedEUID();
1047
1048 // We need pathToSelf in both of the following cases, so let's get it here.
1049
1050 if (err == 0) {
1051 err = GetPathToSelf(&pathToSelf);
1052 }
1053
1054 // There are three cases:
1055 //
1056 // 1a. No command line arguments, privileged -- We can just call
1057 // ReadAndDispatchCommand to execute the request.
1058 //
1059 // 1b. No command line arguments, not privileged -- We need to self
1060 // repair by calling ExecuteSelfInPrivilegedSelfRepairMode.
1061 // This will launch another copy of the tool, and that copy
1062 // of the tool will actually execute the request using
1063 // ReadAndDispatchCommand.
1064 //
1065 // 2. --self-repair command line argument -- We got here by
1066 // step 1b above. We must be privileged. If not, bail out.
1067 // We make ourselve setuid root (RepairOurPrivileges) and then
1068 // execute the command via ReadAndDispatchCommand.
1069
1070 if (err == 0) {
1071 if (argc == 1) {
1072 AuthorizationExternalForm extAuth;
1073
1074 // The caller gave us a AuthorizationRef, but we're don't use
1075 // it in this case. Just throw it away.
1076
1077 if (auth != NULL) {
1078 junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
1079 assert(junk == noErr);
1080
1081 auth = NULL;
1082 }
1083
1084 // Started directly by the application. Read the authorization
1085 // "byte blob" from our input, and use that to create our
1086 // AuthorizationRef.
1087
1088 err = MoreUNIXRead(fdIn, &extAuth, sizeof(extAuth), NULL);
1089 if (err == 0) {
1090 err = OSStatusToEXXX( AuthorizationCreateFromExternalForm(&extAuth, &auth) );
1091 }
1092
1093 // If we're running as root, we can just read the command
1094 // and execute it. Otherwise, we have to self-repair.
1095 // Note that this will launch a second instance of this
1096 // tool, which is the one that actually reads and executes
1097 // the command.
1098
1099 if (err == 0) {
1100 if (privileged) {
1101 err = ReadAndDispatchCommand(fdIn, fdOut, auth, commandProc);
1102 } else {
1103 err = ExecuteSelfInPrivilegedSelfRepairMode(fdIn, fdOut, auth, pathToSelf);
1104 }
1105 }
1106 } else if ( (argc == 2) && (strcmp(argv[1], "--self-repair") == 0) ) {
1107 pid_t myPID;
1108
1109 // We get here if we've been launched in self-repair mode by
1110 // ourselves (see ExecuteSelfInPrivilegedSelfRepairMode). First we
1111 // send our parent our PID. This is needed for reasons that are
1112 // explained above (in ExecuteSelfInPrivilegedSelfRepairMode). Then we
1113 // verify that we're actually been run with EUID 0. Then we grab our
1114 // AuthorizationRef from our parent. Next we self-repair, that is,
1115 // make our executable setuid root so that the next time around we won't
1116 // need to run this code path. Finally, we actually read and
1117 // dispatch the command.
1118 //
1119 // Note that we don't read the authorization "byte
1120 // blob" because it's already been read by our parent.
1121
1122 myPID = getpid();
1123 err = MoreUNIXWrite(fdOut, &myPID, sizeof(myPID), NULL);
1124
1125 if (err == 0 && ! privileged ) {
1126 err = kMoreSecResultParamErr;
1127 }
1128 if ( (err == 0) && (auth == NULL) ) {
1129 err = kMoreSecResultParamErr;
1130 }
1131 if (err == 0) {
1132 err = RepairOurPrivileges(pathToSelf);
1133 }
1134 if (err == 0) {
1135 err = ReadAndDispatchCommand(fdIn, fdOut, auth, commandProc);
1136 }
1137 } else {
1138 err = kMoreSecResultParamErr;
1139 }
1140 }
1141
1142 // Clean up and pass results back to caller.
1143
1144 free(pathToSelf);
1145 if (auth != NULL) {
1146 junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
1147 assert(junk == noErr);
1148 }
1149 return err;
1150}
1151
1152/////////////////////////////////////////////////////////////////
1153#pragma mark ***** Calling Helper Tool
1154
1155extern OSStatus MoreSecIsFolderIgnoringPrivileges(const FSRef *folder, Boolean *ignoringPrivs)
1156 // See comment in header.
1157{
1158 OSStatus err;
1159 OSStatus junk;
1160 int tries;
1161 FSRef fileRef;
1162 FSCatalogInfo info;
1163 static int kPermissionsGroupIDIndex = 1;
1164 static gid_t kPermissionsUnknownGroupID = 99;
1165 static gid_t kPermissionsStaffGroupID = 20;
1166
1167 assert(folder != NULL);
1168 assert(ignoringPrivs != NULL);
1169
1170 // Create a temporary file.
1171
1172 tries = 1;
1173 do {
1174 AbsoluteTime now;
1175 CFStringRef tmpStr;
1176 HFSUniStr255 tmpStrU;
1177
1178 now = UpTime();
1179 tmpStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("MoreSecIsFolderIgnoringPrivileges Temp %lx%lx"), now.hi, now.lo);
1180 err = CFQError(tmpStr);
1181
1182 if (err == noErr) {
1183 assert(CFStringGetLength(tmpStr) <= (sizeof(tmpStrU.unicode) / sizeof(UniChar)) );
1184 tmpStrU.length = (UInt16) CFStringGetLength(tmpStr);
1185 CFStringGetCharacters(tmpStr, CFRangeMake(0, tmpStrU.length), tmpStrU.unicode);
1186
1187 err = FSCreateFileUnicode(folder, tmpStrU.length, tmpStrU.unicode, kFSCatInfoNone, NULL, &fileRef, NULL);
1188 }
1189
1190 CFQRelease(tmpStr);
1191
1192 tries += 1;
1193 } while ( (tries < 1000) && (err == dupFNErr) );
1194
1195 // Probe that temporary file to see if permissions are being ignored.
1196
1197 if (err == noErr) {
1198 err = FSGetCatalogInfo(&fileRef, kFSCatInfoPermissions, &info, NULL, NULL, NULL);
1199 if (err == noErr) {
1200
1201 // If the FGID is not "unknown", then we already know that the volume
1202 // is not ignoring privileges. Otherwise we have to test.
1203
1204 if (info.permissions[kPermissionsGroupIDIndex] != kPermissionsUnknownGroupID) {
1205 *ignoringPrivs = false;
1206 } else {
1207
1208 // Change the FGID to "staff". If that change is effective, we're
1209 // not ignoring privileges.
1210
1211 info.permissions[kPermissionsGroupIDIndex] = kPermissionsStaffGroupID;
1212 err = FSSetCatalogInfo(&fileRef, kFSCatInfoPermissions, &info);
1213 if (err == noErr) {
1214 err = FSGetCatalogInfo(&fileRef, kFSCatInfoPermissions, &info, NULL, NULL, NULL);
1215 }
1216 if (err == noErr) {
1217 *ignoringPrivs = (info.permissions[kPermissionsGroupIDIndex] != kPermissionsStaffGroupID);
1218 }
1219 }
1220 assert( (err != noErr) || (*ignoringPrivs == (info.permissions[kPermissionsGroupIDIndex] == kPermissionsUnknownGroupID)) );
1221 }
1222 junk = FSDeleteObject(&fileRef);
1223 assert(junk == noErr);
1224 }
1225
1226 return err;
1227}
1228
1229static OSStatus MoreSecCheckHelperTool(CFURLRef templateTool, CFURLRef tool, Boolean *looksOK)
1230 // Checks that the working tool is a reasonably accurate copy of the
1231 // templateTool. This checks that the tool exists, is setuid root,
1232 // and has the same size and modification date as the template tool.
1233{
1234 OSStatus err;
1235 char toolPath[MAXPATHLEN]; // 2K on the stack!
1236 char templateToolPath[MAXPATHLEN]; // I'm going to burn in hell.
1237 struct stat toolStat;
1238 struct stat templateToolStat;
1239 struct timeval toolStamp;
1240 struct timeval templateToolStamp;
1241
1242 assert(templateTool != NULL);
1243 assert(tool != NULL);
1244 assert(looksOK != NULL);
1245
1246 // Check that the template tool is present. If the template tool is missing,
1247 // we're doooommmmeeeedddd!
1248
1249 err = CFQErrorBoolean( CFURLGetFileSystemRepresentation(templateTool, true, (UInt8 *)templateToolPath, sizeof(templateToolPath)) );
1250 if (err == noErr) {
1251 err = stat(templateToolPath, &templateToolStat);
1252 err = MoreUNIXErrno(err);
1253 }
1254
1255 // If we successfully found the template tool, go looking for the primary tool.
1256
1257 err = CFQErrorBoolean( CFURLGetFileSystemRepresentation(tool, true, (UInt8 *)toolPath, sizeof(toolPath)) );
1258 if (err == 0) {
1259 err = stat(toolPath, &toolStat);
1260 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1261
1262 // If the primary tool is either missing, has dropped its
1263 // owner or setuid or permissions, or is the wrong size,
1264 // or the wrong time stamp (the last two checks help debugging),
1265 // then try to restore the tool.
1266
1267 TIMESPEC_TO_TIMEVAL(&templateToolStamp, &templateToolStat.st_mtimespec);
1268 TIMESPEC_TO_TIMEVAL(&toolStamp, &toolStat.st_mtimespec);
1269
1270 *looksOK = (err == noErr)
1271 && (toolStat.st_uid == 0)
1272 && ((toolStat.st_mode & kRequiredHelperToolMask) == kRequiredHelperToolPerms)
1273 && (toolStat.st_size == templateToolStat.st_size)
1274 && (toolStamp.tv_sec == templateToolStamp.tv_sec)
1275 && (toolStamp.tv_usec == toolStamp.tv_usec);
1276 err = noErr;
1277 }
1278
1279 return err;
1280}
1281
1282static OSStatus MoreSecCheckAndFixHelperTool(CFURLRef templateTool, CFURLRef tool)
1283 // Checks that the working tool is a reasonably accurate copy of the
1284 // templateTool, using MoreSecCheckHelperTool, and if these checks fail,
1285 // restores tool from the template tool.
1286 //
1287 // the tool referenced by templateTool must exist
1288 //
1289 // tool must not be NULL; if tool does not exist, the directory in
1290 // which tool would be contained must exist
1291{
1292 int err;
1293 char toolPath[MAXPATHLEN]; // 2K on the stack!
1294 char templateToolPath[MAXPATHLEN]; // I'm going to burn in hell.
1295 struct stat toolStat;
1296 Boolean looksOK;
1297
1298 assert(templateTool != NULL);
1299 assert(tool != NULL);
1300
1301 err = MoreSecCheckHelperTool(templateTool, tool, &looksOK);
1302 if (err == noErr && !looksOK) {
1303 err = CFQErrorBoolean( CFURLGetFileSystemRepresentation(templateTool, true, (UInt8 *)templateToolPath, sizeof(templateToolPath)) );
1304 if (err == noErr) {
1305 err = CFQErrorBoolean( CFURLGetFileSystemRepresentation(tool, true, (UInt8 *)toolPath, sizeof(toolPath)) );
1306 }
1307 if (err == noErr) {
1308 if ( stat(toolPath, &toolStat) == 0 ) {
1309 err = unlink(toolPath);
1310 err = MoreUNIXErrno(err);
1311 if (err == EPERM) { // just in case the file name is being used by a directory
1312 err = rmdir(toolPath);
1313 err = MoreUNIXErrno(err);
1314 }
1315 }
1316 }
1317
1318 if (err == noErr) {
1319 err = MoreUNIXCopyFile(templateToolPath, toolPath);
1320 }
1321 }
1322
1323 return err;
1324}
1325
1326static OSStatus MoreSecCopyHelperToolURL(short domain, OSType folder, CFStringRef subFolderName, CFStringRef toolName, Boolean createFolder, CFURLRef *tool)
1327 // Create a URL that points to a helper tool named toolName within the
1328 // Folder Manager folder specified by domain and folder. If subFolderName is
1329 // not NULL, the URL points to the tool within that folder, otherwise it
1330 // points to the tool directly within the Folder Manager folder.
1331 // The URL might point to a file that does not exist, but if the call
1332 // is successful then the parent folder will exist.
1333 //
1334 // If createFolder is false, this routine will not create any folders;
1335 // if it needs to create a folder, it will error instead.
1336{
1337 OSStatus err;
1338 FSRef folderRef;
1339 CFURLRef folderURL;
1340
1341 assert(toolName != NULL);
1342 assert( tool != NULL);
1343 assert(*tool == NULL);
1344
1345 folderURL = NULL;
1346
1347 err = FSFindFolder(domain, folder, createFolder, &folderRef);
1348 if (err == noErr && subFolderName != NULL) {
1349 FSRef tmp;
1350 HFSUniStr255 subFolderNameU;
1351
1352 tmp = folderRef;
1353
1354 // Extract the Unicode characters from subFolderName.
1355
1356 assert(CFStringGetLength(subFolderName) <= (sizeof(subFolderNameU.unicode) / sizeof(UniChar)) );
1357 subFolderNameU.length = (UInt16) CFStringGetLength(subFolderName);
1358 CFStringGetCharacters(subFolderName, CFRangeMake(0, subFolderNameU.length), subFolderNameU.unicode);
1359
1360 // If the sub-folder doesn't exist, try to create it. We can't just create it
1361 // and ignore the dupFNErr if it already exists because we need to set up
1362 // folderRef.
1363
1364 err = FSMakeFSRefUnicode(&tmp, subFolderNameU.length, subFolderNameU.unicode, kTextEncodingUnknown, &folderRef);
1365 if (err != noErr && createFolder) {
1366 err = FSCreateDirectoryUnicode(&tmp, subFolderNameU.length, subFolderNameU.unicode, kFSCatInfoNone, NULL,
1367 &folderRef, NULL, NULL);
1368 }
1369 }
1370
1371 // Create a URL to the parent folder, then append the tool name.
1372
1373 if (err == noErr) {
1374 folderURL = CFURLCreateFromFSRef(NULL, &folderRef);
1375 err = CFQError(folderURL);
1376 }
1377 if (err == noErr) {
1378 *tool = CFURLCreateCopyAppendingPathComponent(NULL, folderURL, toolName, false);
1379 err = CFQError(*tool);
1380 }
1381
1382 CFQRelease(folderURL);
1383
1384 assert( (err == noErr) == (*tool != NULL) );
1385
1386 return err;
1387}
1388
1389extern OSStatus MoreSecCopyHelperToolURLAndCheck(CFURLRef templateTool,
1390 OSType folder, CFStringRef subFolderName, CFStringRef toolName,
1391 CFURLRef *tool, Boolean *toolFound)
1392 // See comment in header.
1393{
1394 OSStatus err;
1395 CFURLRef result;
1396 UInt32 domainIndex;
1397 Boolean found;
1398 static const SInt16 kFolderDomains[] = {kUserDomain, kLocalDomain, kNetworkDomain, kSystemDomain, 0};
1399
1400 assert(templateTool != NULL);
1401 assert(toolName != NULL);
1402 assert( tool != NULL);
1403 assert(*tool == NULL);
1404
1405 result = NULL;
1406
1407 // For each folder domain, check whether there's an appropriate helper tool
1408 // present. This allows a sysadmin to put the helper tool in any of the
1409 // "Application Support" folders (ie ~/Library, /Library, /Network/Library,
1410 // /System/Library) and we'll find it and run without trying to create
1411 // another copy of the tool.
1412
1413 found = false;
1414 domainIndex = 0;
1415 do {
1416 assert(result == NULL);
1417
1418 // Note that we pass false to the createFolder parameter of the
1419 // MoreSecCopyHelperToolURL so that it doesn't create any folders
1420 // that are missing. That's because at this stage we're just looking
1421 // for the file, not trying to create it.
1422
1423 err = MoreSecCopyHelperToolURL(kFolderDomains[domainIndex], folder, subFolderName, toolName, false, &result);
1424 if (err == noErr) {
1425 err = MoreSecCheckHelperTool(templateTool, result, &found);
1426 }
1427 err = noErr; // we don't care about the specific error, just whether we found the tool or not
1428
1429 if (!found) {
1430 CFQRelease(result);
1431 result = NULL;
1432 }
1433
1434 domainIndex += 1;
1435 } while (!found && kFolderDomains[domainIndex] != 0);
1436
1437 // At this point either we found the tool, and result is set to its
1438 // URL, or we haven't found the tool and result is NULL.
1439
1440 assert( err == noErr );
1441 assert( found == (result != NULL) );
1442
1443 if (found) {
1444 // Do nothing; result will be copied out to *tool during clean up (below).
1445 } else {
1446 FSRef folderRef;
1447 Boolean ignoringPrivs;
1448
1449 // Let's try to create the tool in the user's "Application Support" folder
1450 // (ie ~/Library/Application Support). However, before we do that, make sure
1451 // that the user isn't ignoring privileges on the volume containing that folder.
1452 // If so, we have no idea where to put the tool, so we fail with a very specific
1453 // error code.
1454
1455 err = FSFindFolder(kUserDomain, folder, true, &folderRef);
1456 if (err == noErr) {
1457 err = MoreSecIsFolderIgnoringPrivileges(&folderRef, &ignoringPrivs);
1458 }
1459 if (err == noErr && ignoringPrivs) {
1460 err = kMoreSecIgnoringPrivsInFolderErr;
1461 }
1462
1463 // Now we pass true to the createFolder parameter of MoreSecCopyHelperToolURL
1464 // because we want to create the tool (and any enclosing folders).
1465
1466 if (err == noErr) {
1467 err = MoreSecCopyHelperToolURL(kUserDomain, folder, subFolderName, toolName, true, &result);
1468 }
1469 if (err == noErr) {
1470 err = MoreSecCheckAndFixHelperTool(templateTool, result);
1471 }
1472 }
1473
1474 // Clean up.
1475
1476 if (err == noErr) {
1477 *tool = result;
1478 } else {
1479 CFQRelease(result);
1480 }
1481 assert( (err == 0) == (*tool != NULL) );
1482
1483 if (toolFound != NULL) *toolFound = found;
1484
1485 return err;
1486}
1487
1488extern OSStatus MoreSecCopyHelperToolURLAndCheckBundled(CFBundleRef inBundle, CFStringRef templateToolName,
1489 OSType folder, CFStringRef subFolderName, CFStringRef toolName,
1490 CFURLRef *tool, Boolean *toolFound)
1491 // See comment in header.
1492{
1493 OSStatus err;
1494 CFURLRef templateTool;
1495
1496 assert(inBundle != NULL);
1497 assert(templateToolName != NULL);
1498 assert(toolName != NULL);
1499 assert( tool != NULL);
1500 assert(*tool == NULL);
1501
1502 // Lots of folks use CFBundleCopyResourceURL, but CFBundleCopyAuxiliaryExecutableURL
1503 // is preferred if the resource is an executable because it allows for bundles
1504 // to contain multiple different types of executable (Mach-O and CFM, for example).
1505
1506 templateTool = CFBundleCopyAuxiliaryExecutableURL(inBundle, templateToolName);
1507 err = CFQError(templateTool);
1508 if (err == noErr) {
1509 err = MoreSecCopyHelperToolURLAndCheck(templateTool, folder, subFolderName, toolName, tool, toolFound);
1510 }
1511
1512 // Clean up.
1513
1514 CFQRelease(templateTool);
1515
1516 assert( (err == 0) == (*tool != NULL) );
1517
1518 return err;
1519}
1520
1521extern OSStatus MoreSecExecuteRequestInHelperTool(CFURLRef helperTool, AuthorizationRef auth, CFDictionaryRef request, CFDictionaryRef *response)
1522 // See comment in header.
1523{
1524 OSStatus err;
1525 OSStatus err2;
1526 int junk;
1527 char toolPath[MAXPATHLEN];
1528 AuthorizationExternalForm extAuth; // spot the Mac OS type!
1529 int fdChild;
1530 int fdParent;
1531 int childPID;
1532 int status;
1533
1534 assert(helperTool != NULL);
1535 assert(auth != NULL);
1536 assert(request != NULL);
1537 assert( response != NULL);
1538 assert(*response == NULL);
1539
1540 childPID = -1;
1541 fdChild = -1;
1542 fdParent = -1;
1543
1544 // Preparatory work. Stuff we want to do before forking, like getting the
1545 // tool's path and creating auth's external form. If either of these fail,
1546 // we want to bail out before the fork.
1547
1548 err = CFQErrorBoolean( CFURLGetFileSystemRepresentation(helperTool, true, (UInt8 *)toolPath, sizeof(toolPath)) );
1549 if (err == noErr) {
1550 err = AuthorizationMakeExternalForm(auth, &extAuth);
1551 }
1552
1553 // Create a pair of anonymous UNIX domain sockets for communication between
1554 // the us and the tool. Name them fdChild and fdParent, just to make things
1555 // clear. It does't make any difference which is which because UNIX domain
1556 // sockets are bidirectional.
1557
1558 if (err == noErr) {
1559 int comm[2];
1560
1561 err = socketpair(AF_UNIX, SOCK_STREAM, 0, comm);
1562 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1563
1564 if (err == noErr) {
1565 fdChild = comm[0];
1566 fdParent = comm[1];
1567 }
1568 }
1569
1570 // Fork. In the child, replace stdin and stdout with fdChild
1571 // (bidirectional, remember). Then close the child's extra
1572 // copy of fdChild and fdParent. Finally, exec the helper tool.
1573 // Execution continues in the child's "main" function, which
1574 // calls MoreSecHelperToolMain (defined above).
1575
1576 if (err == noErr) {
1577 childPID = fork();
1578
1579 if (childPID == 0) { // Child
1580 err = dup2(fdChild, STDIN_FILENO);
1581 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1582
1583 if (err == noErr) {
1584 err = dup2(fdChild, STDOUT_FILENO);
1585 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1586 }
1587
1588 if (err == noErr) {
1589 junk = close(fdChild);
1590 assert(junk == 0);
1591 junk = close(fdParent);
1592 assert(junk == 0);
1593
1594 err = execl(toolPath, toolPath, NULL);
1595 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1596 }
1597 assert(err != noErr); // otherwise we wouldn't be here
1598
1599 // Use "_exit" rather than "exit" because we're still in the
1600 // same address space as the parent, so exit's closing
1601 // of stdio streams will not be helpful.
1602
1603 _exit(MoreSecErrorToHelperToolResult(OSStatusToEXXX(err)));
1604
1605 assert(false); // unreached
1606 } else if (childPID == -1) { // Error, Parent
1607 err = EXXXToOSStatus( MoreUNIXErrno(childPID) );
1608 } else {
1609 assert(childPID > 0); // Parent
1610 }
1611 }
1612
1613 // In the parent, things are a little more complex. First we
1614 // close our redundant copy of fdChild. Then we sent the
1615 // authorization external form to the child via the socket.
1616 // Finally, we send the request to the child, also via the
1617 // socket.
1618
1619 if (fdChild != -1) {
1620 junk = close(fdChild);
1621 assert(junk == 0);
1622 fdChild = -1;
1623 }
1624 if (err == noErr) {
1625 err = EXXXToOSStatus( MoreUNIXWrite(fdParent, &extAuth, sizeof(extAuth), NULL) );
1626 }
1627 if (err == noErr) {
1628 err = WriteDictionaryToDescriptor(request, fdParent);
1629 }
1630
1631 // Close the write side of our connection to the child.
1632 // We need to this because it self repair mode
1633 // (ExecuteSelfInPrivilegedSelfRepairMode) the child simply
1634 // copies its input to its output, and doesn't leave its copy
1635 // loop until EOF on input. This shutdown triggers that EOF.
1636
1637 if (err == noErr) {
1638 err = shutdown(fdParent, 1);
1639 err = EXXXToOSStatus( MoreUNIXErrno(err) );
1640 }
1641
1642 // Read the response back from the child.
1643
1644 if (err == noErr) {
1645 err = CopyDictionaryFromDescriptor(fdParent, response);
1646 }
1647
1648 // We're all done with our socket, so close it. It's important
1649 // that we do this here, before the waitpid, so that, if the child
1650 // is broken and is block waiting on input, it'll get an EPIPE
1651 // and quit.
1652
1653 if (fdParent != -1) {
1654 junk = close(fdParent);
1655 junk = MoreUNIXErrno(junk);
1656 assert(junk == 0);
1657 fdParent = -1;
1658 }
1659
1660 // If we started a child, we have to reap it, always, regardless of
1661 // whether we have encountered an error so far.
1662
1663 if (childPID != -1) {
1664 err2 = waitpid(childPID, &status, 0);
1665 err2 = EXXXToOSStatus( MoreUNIXErrno(err2) );
1666
1667 if (err == noErr) {
1668 err = err2;
1669 }
1670
1671 // If we successfully got a wait status from the client (or
1672 // our communications with the client failed because of
1673 // a generic communications error), let's go see whether
1674 // the child's wait status is a more appropriate source of
1675 // error information.
1676
1677 if ( (err == noErr) || (err == EPIPE) ) {
1678 if ( ! WIFEXITED(status) ) {
1679 // If we got a wait status but it's not a valid exit status (perhaps
1680 // WIFSIGNALED, indication that the child terminated because of a signal),
1681 // that's an unexpected error we can't handle.
1682
1683 err = kMoreSecResultInternalErrorErr;
1684 } else {
1685 // If we got a valid exit status from the child, map its exit status
1686 // into our range so that we return an equivalent status.
1687
1688 err = EXXXToOSStatus( MoreSecHelperToolResultToError(WEXITSTATUS(status)) );
1689 }
1690 }
1691 }
1692
1693 if (err != noErr) {
1694 CFQRelease(*response);
1695 *response = NULL;
1696 }
1697
1698 assert( (err == noErr) == (*response != NULL) );
1699
1700 return err;
1701}
1702
1703extern OSStatus MoreSecGetErrorFromResponse(CFDictionaryRef response)
1704 // See comment in header.
1705{
1706 OSStatus err;
1707 CFNumberRef num;
1708 OSStatus tmp;
1709
1710 assert(response != NULL);
1711
1712 num = (CFNumberRef) CFDictionaryGetValue(response, kMoreSecErrorNumberKey);
1713 err = CFQError(num);
1714
1715 if (err == noErr) {
1716 err = CFQErrorBoolean( CFNumberGetValue(num, kCFNumberSInt32Type, &tmp) );
1717 }
1718 if (err == noErr) {
1719 err = OSStatusToEXXX(tmp);
1720 }
1721
1722 return err;
1723}
Note: See TracBrowser for help on using the repository browser.