Skip to content

Commit

Permalink
Bug 394984: Enable any admin user on OSX to update Firefox, native OS…
Browse files Browse the repository at this point in the history
…X changes. r=mstange
  • Loading branch information
Stephen A Pohl committed May 25, 2016
1 parent f38b814 commit 27d8421
Show file tree
Hide file tree
Showing 5 changed files with 433 additions and 64 deletions.
335 changes: 275 additions & 60 deletions toolkit/mozapps/update/updater/launchchild_osx.mm
Original file line number Diff line number Diff line change
Expand Up @@ -10,85 +10,55 @@
#include <stdlib.h>
#include <stdio.h>
#include <spawn.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include "readstrings.h"

// Prefer the currently running architecture (this is the same as the
// architecture that launched the updater) and fallback to CPU_TYPE_ANY if it
// is no longer available after the update.
static cpu_type_t pref_cpu_types[2] = {
#if defined(__i386__)
CPU_TYPE_X86,
#elif defined(__x86_64__)
CPU_TYPE_X86_64,
#elif defined(__ppc__)
CPU_TYPE_POWERPC,
#endif
CPU_TYPE_ANY };

void LaunchChild(int argc, char **argv)
{
// Initialize spawn attributes.
posix_spawnattr_t spawnattr;
if (posix_spawnattr_init(&spawnattr) != 0) {
printf("Failed to init posix spawn attribute.");
return;
}

// Set spawn attributes.
size_t attr_count = 2;
size_t attr_ocount = 0;
if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 ||
attr_ocount != attr_count) {
printf("Failed to set binary preference on posix spawn attribute.");
posix_spawnattr_destroy(&spawnattr);
return;
class MacAutoreleasePool {
public:
MacAutoreleasePool()
{
mPool = [[NSAutoreleasePool alloc] init];
}

// "posix_spawnp" uses null termination for arguments rather than a count.
// Note that we are not duplicating the argument strings themselves.
char** argv_copy = (char**)malloc((argc + 1) * sizeof(char*));
if (!argv_copy) {
printf("Failed to allocate memory for arguments.");
posix_spawnattr_destroy(&spawnattr);
return;
}
for (int i = 0; i < argc; i++) {
argv_copy[i] = argv[i];
}
argv_copy[argc] = NULL;

// Pass along our environment.
char** envp = NULL;
char*** cocoaEnvironment = _NSGetEnviron();
if (cocoaEnvironment) {
envp = *cocoaEnvironment;
~MacAutoreleasePool()
{
[mPool release];
}

int result = posix_spawnp(NULL, argv_copy[0], NULL, &spawnattr, argv_copy, envp);
private:
NSAutoreleasePool* mPool;
};

free(argv_copy);
posix_spawnattr_destroy(&spawnattr);
void LaunchChild(int argc, const char** argv)
{
MacAutoreleasePool pool;

if (result != 0) {
printf("Process spawn failed with code %d!", result);
@try {
NSString* launchPath = [NSString stringWithUTF8String:argv[0]];
NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argc];
for (int i = 1; i < argc; i++) {
[arguments addObject:[NSString stringWithUTF8String:argv[i]]];
}
[NSTask launchedTaskWithLaunchPath:launchPath
arguments:arguments];
} @catch (NSException* e) {
// Ignore any exception.
}
}

void
LaunchMacPostProcess(const char* aAppBundle)
{
MacAutoreleasePool pool;

// Launch helper to perform post processing for the update; this is the Mac
// analogue of LaunchWinPostProcess (PostUpdateWin).
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSString* iniPath = [NSString stringWithUTF8String:aAppBundle];
iniPath =
[iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"];

NSFileManager* fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:iniPath]) {
// the file does not exist; there is nothing to run
[pool release];
return;
}

Expand All @@ -100,14 +70,12 @@ void LaunchChild(int argc, char **argv)
values,
"PostUpdateMac");
if (readResult) {
[pool release];
return;
}

NSString *exeRelPath = [NSString stringWithUTF8String:values[0]];
NSString *exeArg = [NSString stringWithUTF8String:values[1]];
if (!exeArg || !exeRelPath) {
[pool release];
return;
}

Expand All @@ -133,6 +101,253 @@ void LaunchChild(int argc, char **argv)
}
// ignore the return value of the task, there's nothing we can do with it
[task release];
}

void CleanupElevatedMacUpdate(bool aFailureOccurred)
{
MacAutoreleasePool pool;

id updateServer = nil;
@try {
updateServer = (id)[NSConnection
rootProxyForConnectionWithRegisteredName:
@"org.mozilla.updater.server"
host:nil
usingNameServer:[NSSocketPortNameServer sharedInstance]];
if (aFailureOccurred &&
updateServer &&
[updateServer respondsToSelector:@selector(abort)]) {
[updateServer performSelector:@selector(abort)];
}
else if (updateServer &&
[updateServer respondsToSelector:@selector(shutdown)]) {
[updateServer performSelector:@selector(shutdown)];
}
} @catch (NSException* e) {
// Ignore exceptions.
}
NSFileManager* manager = [NSFileManager defaultManager];
[manager removeItemAtPath:@"/Library/PrivilegedHelperTools/org.mozilla.updater"
error:nil];
[manager removeItemAtPath:@"/Library/LaunchDaemons/org.mozilla.updater.plist"
error:nil];
const char* launchctlArgs[] = {"/bin/launchctl",
"remove",
"org.mozilla.updater"};
// The following call will terminate the current process due to the "remove"
// argument in launchctlArgs.
LaunchChild(3, launchctlArgs);
}

// Note: Caller is responsible for freeing argv.
bool ObtainUpdaterArguments(int* argc, char*** argv)
{
MacAutoreleasePool pool;

id updateServer = nil;
@try {
updateServer = (id)[NSConnection
rootProxyForConnectionWithRegisteredName:
@"org.mozilla.updater.server"
host:nil
usingNameServer:[NSSocketPortNameServer sharedInstance]];
if (!updateServer ||
![updateServer respondsToSelector:@selector(getArguments)] ||
![updateServer respondsToSelector:@selector(shutdown)]) {
NSLog(@"Server doesn't exist or doesn't provide correct selectors.");
// Let's try our best and clean up.
CleanupElevatedMacUpdate(true);
return false; // Won't actually get here due to CleanupElevatedMacUpdate.
}
NSArray* updaterArguments =
[updateServer performSelector:@selector(getArguments)];
*argc = [updaterArguments count];
char** tempArgv = (char**)malloc(sizeof(char*) * (*argc));
for (int i = 0; i < *argc; i++) {
int argLen = [[updaterArguments objectAtIndex:i] length] + 1;
tempArgv[i] = (char*)malloc(argLen);
strncpy(tempArgv[i], [[updaterArguments objectAtIndex:i] UTF8String],
argLen);
}
*argv = tempArgv;
} @catch (NSException* e) {
// Let's try our best and clean up.
CleanupElevatedMacUpdate(true);
return false; // Won't actually get here due to CleanupElevatedMacUpdate.
}
return true;
}

/**
* The ElevatedUpdateServer is launched from a non-elevated updater process.
* It allows an elevated updater process (usually a privileged helper tool) to
* connect to it and receive all the necessary arguments to complete a
* successful update.
*/
@interface ElevatedUpdateServer : NSObject
{
NSArray* mUpdaterArguments;
BOOL mShouldKeepRunning;
BOOL mAborted;
}
- (id)initWithArgs:(NSArray*)args;
- (BOOL)runServer;
- (NSArray*)getArguments;
- (void)abort;
- (BOOL)wasAborted;
- (void)shutdown;
- (BOOL)shouldKeepRunning;
@end

[pool release];
@implementation ElevatedUpdateServer

- (id)initWithArgs:(NSArray*)args
{
self = [super init];
if (!self) {
return nil;
}
mUpdaterArguments = args;
mShouldKeepRunning = YES;
mAborted = NO;
return self;
}

- (BOOL)runServer
{
NSPort* serverPort = [NSSocketPort port];
NSConnection* server = [NSConnection connectionWithReceivePort:serverPort
sendPort:serverPort];
[server setRootObject:self];
if ([server registerName:@"org.mozilla.updater.server"
withNameServer:[NSSocketPortNameServer sharedInstance]] == NO) {
NSLog(@"Unable to register as DirectoryServer.");
NSLog(@"Is another copy running?");
return NO;
}

while ([self shouldKeepRunning] &&
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);
return ![self wasAborted];
}

- (NSArray*)getArguments
{
return mUpdaterArguments;
}

- (void)abort
{
mAborted = YES;
[self shutdown];
}

- (BOOL)wasAborted
{
return mAborted;
}

- (void)shutdown
{
mShouldKeepRunning = NO;
}

- (BOOL)shouldKeepRunning
{
return mShouldKeepRunning;
}

@end

bool ServeElevatedUpdate(int argc, const char** argv)
{
MacAutoreleasePool pool;

NSMutableArray* updaterArguments = [NSMutableArray arrayWithCapacity:argc];
for (int i = 0; i < argc; i++) {
[updaterArguments addObject:[NSString stringWithUTF8String:argv[i]]];
}

ElevatedUpdateServer* updater =
[[ElevatedUpdateServer alloc] initWithArgs:[updaterArguments copy]];
bool didSucceed = [updater runServer];

[updater release];
return didSucceed;
}

bool IsOwnedByGroupAdmin(const char* aAppBundle)
{
MacAutoreleasePool pool;

NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
NSFileManager* fileManager = [NSFileManager defaultManager];

NSDictionary* attributes = [fileManager attributesOfItemAtPath:appDir
error:nil];
bool isOwnedByAdmin = false;
if (attributes &&
[[attributes valueForKey:NSFileGroupOwnerAccountID] intValue] == 80) {
isOwnedByAdmin = true;
}
return isOwnedByAdmin;
}

void SetGroupOwnershipAndPermissions(const char* aAppBundle)
{
MacAutoreleasePool pool;

NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
NSFileManager* fileManager = [NSFileManager defaultManager];
NSError* error = nil;
NSArray* paths =
[fileManager subpathsOfDirectoryAtPath:appDir
error:&error];
if (error) {
return;
}

// Set group ownership of Firefox.app to 80 ("admin") and permissions to
// 0775.
if (![fileManager setAttributes:@{ NSFileGroupOwnerAccountID: @(80),
NSFilePosixPermissions: @(0775) }
ofItemAtPath:appDir
error:&error] || error) {
return;
}

NSArray* permKeys = [NSArray arrayWithObjects:NSFileGroupOwnerAccountID,
NSFilePosixPermissions,
nil];
// For all descendants of Firefox.app, set group ownership to 80 ("admin") and
// ensure write permission for the group.
for (NSString* currPath in paths) {
NSString* child = [appDir stringByAppendingPathComponent:currPath];
NSDictionary* oldAttributes =
[fileManager attributesOfItemAtPath:child
error:&error];
if (error) {
return;
}
// Skip symlinks, since they could be pointing to files outside of the .app
// bundle.
if ([oldAttributes fileType] == NSFileTypeSymbolicLink) {
continue;
}
NSNumber* oldPerms =
(NSNumber*)[oldAttributes valueForKey:NSFilePosixPermissions];
NSArray* permObjects =
[NSArray arrayWithObjects:
[NSNumber numberWithUnsignedLong:80],
[NSNumber numberWithUnsignedLong:[oldPerms shortValue] | 020],
nil];
NSDictionary* attributes = [NSDictionary dictionaryWithObjects:permObjects
forKeys:permKeys];
if (![fileManager setAttributes:attributes
ofItemAtPath:child
error:&error] || error) {
return;
}
}
}
2 changes: 2 additions & 0 deletions toolkit/xre/MacLaunchHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
extern "C" {
void LaunchChildMac(int aArgc, char** aArgv, uint32_t aRestartType = 0,
pid_t *pid = 0);
bool LaunchElevatedUpdate(int argc, char** argv, uint32_t aRestartType = 0,
pid_t* pid = 0);
}

#endif
Loading

0 comments on commit 27d8421

Please sign in to comment.