diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-30 18:16:18 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-30 18:16:18 +0000 |
commit | 526657d45abd72067ff774c552cc4df5825fc742 (patch) | |
tree | cb5da516b3b90c0057eaff34b51697bd1ff9ea4a /chrome/browser/cocoa | |
parent | ac68bb0756f5bcb47cd79e832ef9ec6e5cb8bda0 (diff) | |
download | chromium_src-526657d45abd72067ff774c552cc4df5825fc742.zip chromium_src-526657d45abd72067ff774c552cc4df5825fc742.tar.gz chromium_src-526657d45abd72067ff774c552cc4df5825fc742.tar.bz2 |
Allow authenticated installs from the disk image.
When launching from a read-only disk image, if no installed copy exists in
/Applications, and the user chooses to install from the disk image:
If it appears that there isn't permission to write to /Applications, prompt
the user to authenticate as an administrator. If authentication is
successful, copy the application as root, set its permissions, and promote
Keystone to use a system ticket.
BUG=40265
TEST=Remove the application from /Applications and test, as both an admin
user and a normal user, installation directly from the disk image. When
installation occurs after authentication, Chrome should be on a system
ticket.
Also, make sure that normal in-app Keystone promotion continues to work
as expected.
Review URL: http://codereview.chromium.org/1792013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46091 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r-- | chrome/browser/cocoa/authorization_util.h | 8 | ||||
-rw-r--r-- | chrome/browser/cocoa/authorization_util.mm (renamed from chrome/browser/cocoa/authorization_util.cc) | 65 | ||||
-rwxr-xr-x | chrome/browser/cocoa/install.sh | 123 | ||||
-rw-r--r-- | chrome/browser/cocoa/install_from_dmg.mm | 176 | ||||
-rw-r--r-- | chrome/browser/cocoa/keystone_glue.h | 20 | ||||
-rw-r--r-- | chrome/browser/cocoa/keystone_glue.mm | 100 | ||||
-rwxr-xr-x | chrome/browser/cocoa/keystone_promote_postflight.sh | 5 |
7 files changed, 355 insertions, 142 deletions
diff --git a/chrome/browser/cocoa/authorization_util.h b/chrome/browser/cocoa/authorization_util.h index 2d7c09d..9370949 100644 --- a/chrome/browser/cocoa/authorization_util.h +++ b/chrome/browser/cocoa/authorization_util.h @@ -23,12 +23,20 @@ // http://developer.apple.com/mac/library/samplecode/BetterAuthorizationSample/listing1.html // (Look for "What's This About Zombies?") +#include <CoreFoundation/CoreFoundation.h> #include <Security/Authorization.h> #include <stdio.h> #include <sys/types.h> namespace authorization_util { +// Obtains an AuthorizationRef that can be used to run commands as root. If +// necessary, prompts the user for authentication. If the user is prompted, +// |prompt| will be used as the prompt string and an icon appropriate for the +// application will be displayed in a prompt dialog. Note that the system +// appends its own text to the prompt string. Returns NULL on failure. +AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt); + // Calls straight through to AuthorizationExecuteWithPrivileges. If that // call succeeds, |pid| will be set to the pid of the executed tool. If the // pid can't be determined, |pid| will be set to -1. |pid| must not be NULL. diff --git a/chrome/browser/cocoa/authorization_util.cc b/chrome/browser/cocoa/authorization_util.mm index 434f1a3..709fcc1 100644 --- a/chrome/browser/cocoa/authorization_util.cc +++ b/chrome/browser/cocoa/authorization_util.mm @@ -4,16 +4,81 @@ #include "chrome/browser/cocoa/authorization_util.h" +#import <Foundation/Foundation.h> #include <sys/wait.h> #include <string> +#include "base/basictypes.h" #include "base/eintr_wrapper.h" #include "base/logging.h" +#import "base/mac_util.h" #include "base/string_util.h" +#include "chrome/browser/cocoa/scoped_authorizationref.h" namespace authorization_util { +AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { + // Create an empty AuthorizationRef. + scoped_AuthorizationRef authorization; + OSStatus status = AuthorizationCreate(NULL, + kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, + &authorization); + if (status != errAuthorizationSuccess) { + LOG(ERROR) << "AuthorizationCreate: " << status; + return NULL; + } + + // Specify the "system.privilege.admin" right, which allows + // AuthorizationExecuteWithPrivileges to run commands as root. + AuthorizationItem right_items[] = { + {kAuthorizationRightExecute, 0, NULL, 0} + }; + AuthorizationRights rights = {arraysize(right_items), right_items}; + + // product_logo_32.png is used instead of app.icns because Authorization + // Services can't deal with .icns files. + NSString* icon_path = + [mac_util::MainAppBundle() pathForResource:@"product_logo_32" + ofType:@"png"]; + const char* icon_path_c = [icon_path fileSystemRepresentation]; + size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0; + + // The OS will append " Type an administrator's name and password to allow + // <CFBundleDisplayName> to make changes." + NSString* prompt_ns = reinterpret_cast<const NSString*>(prompt); + const char* prompt_c = [prompt_ns UTF8String]; + size_t prompt_length = prompt_c ? strlen(prompt_c) : 0; + + AuthorizationItem environment_items[] = { + {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0}, + {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0} + }; + + AuthorizationEnvironment environment = {arraysize(environment_items), + environment_items}; + + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPreAuthorize; + + status = AuthorizationCopyRights(authorization, + &rights, + &environment, + flags, + NULL); + if (status != errAuthorizationSuccess) { + if (status != errAuthorizationCanceled) { + LOG(ERROR) << "AuthorizationCopyRights: " << status; + } + return NULL; + } + + return authorization.release(); +} + OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, const char* tool_path, AuthorizationFlags options, diff --git a/chrome/browser/cocoa/install.sh b/chrome/browser/cocoa/install.sh new file mode 100755 index 0000000..dc73fae --- /dev/null +++ b/chrome/browser/cocoa/install.sh @@ -0,0 +1,123 @@ +#!/bin/bash -p + +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Called by the application to install in a new location. Generally, this +# means that the application is running from a disk image and wants to be +# copied to /Applications. The application, when running from the disk image, +# will call this script to perform the copy. +# +# This script will be run as root if the application determines that it would +# not otherwise have permission to perform the copy. +# +# When running as root, this script will be invoked with the real user ID set +# to the user's ID, but the effective user ID set to 0 (root). bash -p is +# used on the first line to prevent bash from setting the effective user ID to +# the real user ID (dropping root privileges). + +set -e + +# This script may run as root, so be paranoid about things like ${PATH}. +export PATH="/usr/bin:/usr/sbin:/bin:/sbin" + +# If running as root, output the pid to stdout before doing anything else. +# See chrome/browser/cocoa/authorization_util.h. +if [ ${EUID} -eq 0 ] ; then + echo "${$}" +fi + +if [ ${#} -ne 2 ] ; then + echo "usage: ${0} SRC DEST" >& 2 + exit 2 +fi + +SRC=${1} +DEST=${2} + +# Make sure that SRC is an absolute path and that it exists. +if [ -z "${SRC}" ] || [ "${SRC:0:1}" != "/" ] || [ ! -d "${SRC}" ] ; then + echo "${0}: source ${SRC} sanity check failed" >& 2 + exit 3 +fi + +# Make sure that DEST is an absolute path and that it doesn't yet exist. +if [ -z "${DEST}" ] || [ "${DEST:0:1}" != "/" ] || [ -e "${DEST}" ] ; then + echo "${0}: destination ${DEST} sanity check failed" >& 2 + exit 4 +fi + +# Do the copy. +rsync -lrpt "${SRC}/" "${DEST}" + +# The remaining steps are not considered critical. +set +e + +# Notify LaunchServices. +/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister "${DEST}" + +# If this script is not running as root and the application is installed +# somewhere under /Applications, try to make it writable by all admin users. +# This will allow other admin users to update the application from their own +# user Keystone instances even if the Keystone ticket is not promoted to +# system level. +# +# If the script is not running as root and the application is not installed +# under /Applications, it might not be in a system-wide location, and it +# probably won't be something that other users on the system are running, so +# err on the side of safety and don't make it group-writable. +# +# If this script is running as root, a Keystone ticket promotion is expected, +# and future updates can be expected to be applied as root, so +# admin-writeability is not a concern. Set the entire thing to be owned by +# root in that case, regardless of where it's installed, and drop any group +# and other write permission. +# +# If this script is running as a user that is not a member of the admin group, +# the chgrp operation will not succeed. Tolerate that case, because it's +# better than the alternative, which is to make the application +# world-writable. +CHMOD_MODE="a+rX,u+w,go-w" +if [ ${EUID} -ne 0 ] ; then + if [ "${DEST:0:14}" = "/Applications/" ] && + chgrp -Rh admin "${DEST}" >& /dev/null ; then + CHMOD_MODE="a+rX,ug+w,o-w" + fi +else + chown -Rh root:wheel "${DEST}" >& /dev/null +fi + +chmod -R "${CHMOD_MODE}" "${DEST}" >& /dev/null + +# On the Mac, or at least on HFS+, symbolic link permissions are significant, +# but chmod -R and -h can't be used together. Do another pass to fix the +# permissions on any symbolic links. +find "${DEST}" -type l -exec chmod -h "${CHMOD_MODE}" {} + >& /dev/null + +# Host OS version check, to be able to take advantage of features on newer +# systems and fall back to slow ways of doing things on older systems. +OS_VERSION=$(sw_vers -productVersion) +OS_MAJOR=$(sed -Ene 's/^([0-9]+).*/\1/p' <<< ${OS_VERSION}) +OS_MINOR=$(sed -Ene 's/^([0-9]+)\.([0-9]+).*/\2/p' <<< ${OS_VERSION}) + +# Because this script is launched by the application itself, the installation +# process inherits the quarantine bit (LSFileQuarantineEnabled). Any files or +# directories created during the update will be quarantined in that case, +# which may cause Launch Services to display quarantine UI. That's bad, +# especially if it happens when the outer .app launches a quarantined inner +# helper. Since the user approved the application launch if quarantined, it +# it can be assumed that the installed copy should not be quarantined. Use +# xattr to drop the quarantine attribute. +QUARANTINE_ATTR=com.apple.quarantine +if [ ${OS_MAJOR} -gt 10 ] || + ([ ${OS_MAJOR} -eq 10 ] && [ ${OS_MINOR} -ge 6 ]) ; then + # On 10.6, xattr supports -r for recursive operation. + xattr -d -r "${QUARANTINE_ATTR}" "${DEST}" >& /dev/null +else + # On earlier systems, xattr doesn't support -r, so run xattr via find. + find "${DEST}" -exec xattr -d "${QUARANTINE_ATTR}" {} + >& /dev/null +fi + +# Great success! +exit 0 diff --git a/chrome/browser/cocoa/install_from_dmg.mm b/chrome/browser/cocoa/install_from_dmg.mm index d6e28f0..27ffa38 100644 --- a/chrome/browser/cocoa/install_from_dmg.mm +++ b/chrome/browser/cocoa/install_from_dmg.mm @@ -17,8 +17,11 @@ #include "base/basictypes.h" #include "base/command_line.h" #include "base/logging.h" +#import "base/mac_util.h" #include "base/scoped_nsautorelease_pool.h" -#include "base/sys_info.h" +#include "chrome/browser/cocoa/authorization_util.h" +#include "chrome/browser/cocoa/scoped_authorizationref.h" +#import "chrome/browser/cocoa/keystone_glue.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -220,87 +223,85 @@ bool ShouldInstallDialog() { return result == NSAlertFirstButtonReturn; } -// Copies source_path to target_path and performs any additional on-disk -// bookkeeping needed to be able to launch target_path properly. -bool InstallFromDiskImage(NSString* source_path, NSString* target_path) { +// Potentially shows an authorization dialog to request authentication to +// copy. If application_directory appears to be unwritable, attempts to +// obtain authorization, which may result in the display of the dialog. +// Returns NULL if authorization is not performed because it does not appear +// to be necessary because the user has permission to write to +// application_directory. Returns NULL if authorization fails. +AuthorizationRef MaybeShowAuthorizationDialog(NSString* application_directory) { NSFileManager* file_manager = [NSFileManager defaultManager]; + if ([file_manager isWritableFileAtPath:application_directory]) { + return NULL; + } - // For the purposes of this copy, the file manager's delegate shouldn't be - // consulted at all. Clear the delegate and restore it after the copy is - // done. - id file_manager_delegate = [file_manager delegate]; - [file_manager setDelegate:nil]; - - NSError* copy_error; - bool copy_result = [file_manager copyItemAtPath:source_path - toPath:target_path - error:©_error]; + NSString* prompt = l10n_util::GetNSStringFWithFixup( + IDS_INSTALL_FROM_DMG_AUTHENTICATION_PROMPT, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); + return authorization_util::AuthorizationCreateToRunAsRoot( + reinterpret_cast<CFStringRef>(prompt)); +} - [file_manager setDelegate:file_manager_delegate]; +// Invokes the installer program at installer_path to copy source_path to +// target_path and perform any additional on-disk bookkeeping needed to be +// able to launch target_path properly. If authorization_arg is non-NULL, +// function will assume ownership of it, will invoke the installer with that +// authorization reference, and will attempt Keystone ticket promotion. +bool InstallFromDiskImage(AuthorizationRef authorization_arg, + NSString* installer_path, + NSString* source_path, + NSString* target_path) { + scoped_AuthorizationRef authorization(authorization_arg); + authorization_arg = NULL; + int exit_status; + if (authorization) { + const char* installer_path_c = [installer_path fileSystemRepresentation]; + const char* source_path_c = [source_path fileSystemRepresentation]; + const char* target_path_c = [target_path fileSystemRepresentation]; + const char* arguments[] = {source_path_c, target_path_c, NULL}; + + OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait( + authorization, + installer_path_c, + kAuthorizationFlagDefaults, + arguments, + NULL, // pipe + &exit_status); + if (status != errAuthorizationSuccess) { + LOG(ERROR) << "AuthorizationExecuteWithPrivileges install: " << status; + return false; + } + } else { + NSArray* arguments = [NSArray arrayWithObjects:source_path, + target_path, + nil]; + + NSTask* task; + @try { + task = [NSTask launchedTaskWithLaunchPath:installer_path + arguments:arguments]; + } @catch(NSException* exception) { + LOG(ERROR) << "+[NSTask launchedTaskWithLaunchPath:arguments:]: " + << [[exception description] UTF8String]; + return false; + } - if (!copy_result) { - LOG(ERROR) << "-[NSFileManager copyItemAtPath:toPath:error:]: " - << [[copy_error description] UTF8String]; - return false; + [task waitUntilExit]; + exit_status = [task terminationStatus]; } - // Since the application performed the copy, and the application has the - // quarantine bit (LSFileQuarantineEnabled) set, the installed copy will - // be quarantined. That's bad, because it will cause the quarantine dialog - // to be displayed, possibly after a long delay, when the application is - // relaunched. Use xattr to drop the quarantine attribute. - // - // There are three reasons not to use MDItemRemoveAttribute directly: - // 1. MDItemRemoveAttribute is a private API. - // 2. The operation needs to be recursive, and writing a bunch of code to - // handle the recursion just to call a private API is annoying. - // 3. All of this stuff will likely move into a shell script anyway, and - // the shell script will have no choice but to use xattr. - - int32 os_major, os_minor, os_patch; - base::SysInfo::OperatingSystemVersionNumbers(&os_major, &os_minor, &os_patch); - - const NSString* xattr_path = @"/usr/bin/xattr"; - const NSString* quarantine_attribute = @"com.apple.quarantine"; - NSString* launch_path; - NSArray* arguments; - - if (os_major > 10 || (os_major == 10 && os_minor >= 6)) { - // On 10.6, xattr supports -r for recursive operation. - launch_path = xattr_path; - arguments = [NSArray arrayWithObjects:@"-r", - @"-d", - quarantine_attribute, - target_path, - nil]; - } else { - // On earlier systems, xattr doesn't support -r, so run xattr via find. - launch_path = @"/usr/bin/find"; - arguments = [NSArray arrayWithObjects:target_path, - @"-exec", - xattr_path, - @"-d", - quarantine_attribute, - @"{}", - @"+", - nil]; - } - - NSTask* task; - @try { - task = [NSTask launchedTaskWithLaunchPath:launch_path - arguments:arguments]; - } @catch(NSException* exception) { - LOG(ERROR) << "+[NSTask launchedTaskWithLaunchPath:arguments:]: " - << [[exception description] UTF8String]; + if (exit_status != 0) { + LOG(ERROR) << "install.sh: exit status " << exit_status; return false; } - [task waitUntilExit]; - int status = [task terminationStatus]; - if (status != 0) { - LOG(ERROR) << "/usr/bin/xattr: status " << status; - return false; + if (authorization) { + // As long as an AuthorizationRef is available, promote the Keystone + // ticket. Inform KeystoneGlue of the new path to use. + KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue]; + [keystone_glue setAppPath:target_path]; + [keystone_glue promoteTicketWithAuthorization:authorization.release() + synchronous:YES]; } return true; @@ -319,9 +320,6 @@ bool LaunchInstalledApp(NSString* app_path) { return false; } - // Use an empty dictionary for the environment. - NSDictionary* environment = [NSDictionary dictionary]; - const std::vector<std::string>& argv = CommandLine::ForCurrentProcess()->argv(); NSMutableArray* arguments = @@ -341,7 +339,6 @@ bool LaunchInstalledApp(NSString* app_path) { struct LSApplicationParameters parameters = {0}; parameters.flags = kLSLaunchDefaults; parameters.application = &app_fsref; - parameters.environment = reinterpret_cast<CFDictionaryRef>(environment); parameters.argv = reinterpret_cast<CFArrayRef>(arguments); err = LSOpenApplication(¶meters, NULL); @@ -401,13 +398,6 @@ bool MaybeInstallFromDiskImage() { return false; } - // TODO(mark): When this happens, prompt for authentication. - if (![file_manager isWritableFileAtPath:application_directory]) { - LOG(INFO) << "Non-writable application directory at " - << [application_directory UTF8String]; - return false; - } - NSString* source_path = [[NSBundle mainBundle] bundlePath]; NSString* application_name = [source_path lastPathComponent]; NSString* target_path = @@ -418,11 +408,27 @@ bool MaybeInstallFromDiskImage() { return false; } + NSString* installer_path = + [mac_util::MainAppBundle() pathForResource:@"install" ofType:@"sh"]; + if (!installer_path) { + LOG(INFO) << "Could not locate install.sh"; + return false; + } + if (!ShouldInstallDialog()) { return false; } - if (!InstallFromDiskImage(source_path, target_path) || + scoped_AuthorizationRef authorization( + MaybeShowAuthorizationDialog(application_directory)); + // authorization will be NULL if it's deemed unnecessary or if + // authentication fails. In either case, try to install without privilege + // escalation. + + if (!InstallFromDiskImage(authorization.release(), + installer_path, + source_path, + target_path) || !LaunchInstalledApp(target_path)) { ShowErrorDialog(); return false; diff --git a/chrome/browser/cocoa/keystone_glue.h b/chrome/browser/cocoa/keystone_glue.h index 6ae4c1f..2848681 100644 --- a/chrome/browser/cocoa/keystone_glue.h +++ b/chrome/browser/cocoa/keystone_glue.h @@ -89,6 +89,10 @@ namespace { // carried across threads. scoped_AuthorizationRef authorization_; + // YES if a synchronous promotion operation is in progress (promotion during + // installation). + BOOL synchronousPromotion_; + // YES if an update was ever successfully installed by -installUpdate. BOOL updateSuccessfullyInstalled_; } @@ -146,10 +150,22 @@ namespace { - (BOOL)needsPromotion; - (BOOL)wantsPromotion; -// Requests authorization and promotes the Keystone ticket into the system -// store. System Keystone will be installed if necessary. +// Promotes the Keystone ticket into the system store. System Keystone will +// be installed if necessary. If synchronous is NO, the promotion may occur +// in the background. synchronous should be YES for promotion during +// installation. The KeystoneGlue object assumes ownership of +// authorization_arg. +- (void)promoteTicketWithAuthorization:(AuthorizationRef)authorization_arg + synchronous:(BOOL)synchronous; + +// Requests authorization and calls -promoteTicketWithAuthorization: in +// asynchronous mode. - (void)promoteTicket; +// Sets a new value for appPath. Used during installation to point a ticket +// at the installed copy. +- (void)setAppPath:(NSString*)appPath; + @end // @interface KeystoneGlue @interface KeystoneGlue(ExposedForTesting) diff --git a/chrome/browser/cocoa/keystone_glue.mm b/chrome/browser/cocoa/keystone_glue.mm index 5cb125a..5baa400 100644 --- a/chrome/browser/cocoa/keystone_glue.mm +++ b/chrome/browser/cocoa/keystone_glue.mm @@ -692,65 +692,38 @@ const NSString* const kBrandKey = @"KSBrandID"; return; } - // Create an empty AuthorizationRef. - scoped_AuthorizationRef authorization; - OSStatus status = AuthorizationCreate(NULL, - kAuthorizationEmptyEnvironment, - kAuthorizationFlagDefaults, - &authorization); - if (status != errAuthorizationSuccess) { - LOG(ERROR) << "AuthorizationCreate: " << status; - return; - } - - // Specify the "system.privilege.admin" right, which allows - // AuthorizationExecuteWithPrivileges to run commands as root. - AuthorizationItem rightItems[] = { - {kAuthorizationRightExecute, 0, NULL, 0} - }; - AuthorizationRights rights = {arraysize(rightItems), rightItems}; - - // product_logo_32.png is used instead of app.icns because Authorization - // Services requires an image that NSImage can read. - NSString* iconPath = - [mac_util::MainAppBundle() pathForResource:@"product_logo_32" - ofType:@"png"]; - const char* iconPathC = [iconPath fileSystemRepresentation]; - size_t iconPathLength = iconPathC ? strlen(iconPathC) : 0; - - // The OS will append " Type an administrator's name and password to allow - // <CFBundleDisplayName> to make changes." NSString* prompt = l10n_util::GetNSStringFWithFixup( IDS_PROMOTE_AUTHENTICATION_PROMPT, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); - const char* promptC = [prompt UTF8String]; - size_t promptLength = promptC ? strlen(promptC) : 0; - - AuthorizationItem environmentItems[] = { - {kAuthorizationEnvironmentIcon, iconPathLength, (void*)iconPathC, 0}, - {kAuthorizationEnvironmentPrompt, promptLength, (void*)promptC, 0} - }; - - AuthorizationEnvironment environment = {arraysize(environmentItems), - environmentItems}; - - AuthorizationFlags flags = kAuthorizationFlagDefaults | - kAuthorizationFlagInteractionAllowed | - kAuthorizationFlagExtendRights | - kAuthorizationFlagPreAuthorize; - - status = AuthorizationCopyRights(authorization, - &rights, - &environment, - flags, - NULL); - if (status != errAuthorizationSuccess) { - if (status != errAuthorizationCanceled) { - LOG(ERROR) << "AuthorizationCopyRights: " << status; - } + scoped_AuthorizationRef authorization( + authorization_util::AuthorizationCreateToRunAsRoot( + reinterpret_cast<CFStringRef>(prompt))); + if (!authorization.get()) { return; } + [self promoteTicketWithAuthorization:authorization.release() synchronous:NO]; +} + +- (void)promoteTicketWithAuthorization:(AuthorizationRef)authorization_arg + synchronous:(BOOL)synchronous { + scoped_AuthorizationRef authorization(authorization_arg); + authorization_arg = NULL; + + if ([self asyncOperationPending]) { + // Starting a synchronous operation while an asynchronous one is pending + // could be trouble. + return; + } + if (!synchronous && ![self wantsPromotion]) { + // If operating synchronously, the call came from the installer, which + // means that a system ticket is required. Otherwise, only allow + // promotion if it's wanted. + return; + } + + synchronousPromotion_ = synchronous; + [self updateStatus:kAutoupdatePromoting version:nil]; // TODO(mark): Remove when able! @@ -773,6 +746,8 @@ const NSString* const kBrandKey = @"KSBrandID"; // operation itself is asynchronous too. http://b/2290009. Hopefully, // the Keystone promotion code will just be changed to do what preflight // now does, and then the preflight script can be removed instead. + // However, preflight operation (and promotion) should only be asynchronous + // if the synchronous parameter is NO. NSString* preflightPath = [mac_util::MainAppBundle() pathForResource:@"keystone_promote_preflight" ofType:@"sh"]; @@ -787,7 +762,7 @@ const NSString* const kBrandKey = @"KSBrandID"; const char* arguments[] = {userBrandFile, systemBrandFile, NULL}; int exit_status; - status = authorization_util::ExecuteWithPrivilegesAndWait( + OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait( authorization, preflightPathC, kAuthorizationFlagDefaults, @@ -837,7 +812,15 @@ const NSString* const kBrandKey = @"KSBrandID"; - (void)promotionComplete:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) { - [self changePermissionsForPromotionAsync]; + if (synchronousPromotion_) { + // Short-circuit: if performing a synchronous promotion, the promotion + // came from the installer, which already set the permissions properly. + // Rather than run a duplicate permission-changing operation, jump + // straight to "done." + [self changePermissionsForPromotionComplete]; + } else { + [self changePermissionsForPromotionAsync]; + } } else { authorization_.reset(); [self updateStatus:kAutoupdatePromoteFailed version:nil]; @@ -896,4 +879,11 @@ const NSString* const kBrandKey = @"KSBrandID"; [self updateStatus:kAutoupdatePromoted version:nil]; } +- (void)setAppPath:(NSString*)appPath { + if (appPath != appPath_) { + [appPath_ release]; + appPath_ = [appPath copy]; + } +} + @end // @implementation KeystoneGlue diff --git a/chrome/browser/cocoa/keystone_promote_postflight.sh b/chrome/browser/cocoa/keystone_promote_postflight.sh index 5eb85d2..510e5d2 100755 --- a/chrome/browser/cocoa/keystone_promote_postflight.sh +++ b/chrome/browser/cocoa/keystone_promote_postflight.sh @@ -13,6 +13,11 @@ # user's ID, but the effective user ID set to 0 (root). bash -p is used on # the first line to prevent bash from setting the effective user ID to the # real user ID (dropping root privileges). +# +# WARNING: This script is NOT currently run when the Keystone ticket is +# promoted during application installation directly from the disk image, +# because the installation process itself handles the same permission fix-ups +# that this script normally would. set -e |