Windows Service Silent Update

Overview

Before this task, when a user installs into the default Program Files directory, updates cannot be applied without us first prompting the user for elevated permissions with a User Account Control (UAC) dialog.

 

After this task, Firefox will use a service to execute updates so that UAC prompts are not displayed. The service is on demand and will remain stopped until it is needed, when it is needed it will be started again for the period of the update.

The task related to creating a service and not prompting the user for UAC is in bug 481815.

The feature page is located at: this feature page.

The security related bugs needed for 481815 to land were: bug 708688, bug 708697, bug 708690, bug 708778, bug 708854, bug 699700, bug 704285, bug 709173, bug 709183, and bug 709158.

Windows Service component information

Display name: Mozilla Maintenance Service
Service name: MozillaMaintenance
File name: maintenanceservice.exe
Installation directory: %PROGRAMFILES%\Mozilla Maintenance Service

Other details about the service:

  • The service will be dealing with user tokens and therefore must be run as the SYSTEM account.
  • The service will be located in the tree under /toolkit/components/maintenanceservice

To test and see if the service is installed, from a command prompt you can run:

   sc query MozillaMaintenance

If it is installed you will see output of:

  SERVICE_NAME: MozillaMaintenance 
       TYPE               : 10  WIN32_OWN_PROCESS  
       STATE              : 1  STOPPED 
       WIN32_EXIT_CODE    : 1077  (0x435)
       SERVICE_EXIT_CODE  : 0  (0x0)
       CHECKPOINT         : 0x0
       WAIT_HINT          : 0x0

If it is not installed, you will see output of:

   [SC] EnumQueryServicesStatus:OpenService FAILED 1060:
   The specified service does not exist as an installed service.

How the service works

Since the service is only started when needed, we pass the information to the service via command line on service start. The service security will be modified with a special ACE which allows non elevated processes to start and stop it. Setting these permissions on the service is needed because by default services can only be started and stopped by elevated processes. This is possible via the Win32 API SetServiceObjectSecurity.

The service will log all operations of the last update, as well as backup the 5 update operation logs before it. The install process and update processes will also be logged separately. The logs will live in the all users application data.

Since the service executes an update via updater.exe in session 0, no UI will be displayed. A UI could be displayed by running updater.exe with a user token instead, but to avoid permission problems on files, and inconsistencies in different OS versions and user types, the update will always be run as session 0.

A single service and service versioning

The service is currently only used on Firefox, but we do plan to allow other products to use the service. Only one service will exist across all channels, and once other products are allowed to use the service they will likely also share the service.

  • If a service is already installed, the service will be replaced on updates and installs only if it is newer than what is installed.
  • If the user is running on an x64 computer with a native x64 build, then the service will not be installed nor used by that build. If the user is on an x64 computer with an x86 build, then the service will be used and installed by the build as an x86 process.

The service decides whether or not it is newer by looking at the updater file's version number in comparison to a new one being updated/installed. Only if the newer version number is greater will it be replaced. This means that if the user runs builds like Nightly, then the Nightly service would update all other channels. It will therefore always be backwards compatible.

Service work items

A 'work item' is just a file that contains the info that is usually passed to updater.exe to perform an update. Firefox writes out work items, and the service consumes those work items and performs the action specified within. The only action currently supported is to execute updater.exe.

Later we plan to also support commands for clearing prefetch for faster startup, defraging user profile data, and other actions not yet planned.

The very simple file format of these work items is:

  • A 4 byte command ID.
  • The application path to updater.exe to use
  • The command line string to pass to updater.exe: updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]

The 4 byte command ID is to help identify future commands. A value of 1 is the only accepted value for now and all other values will not perform any action from the service.

Applying an update from Firefox's perspective

  • On Firefox (or any other Mozilla application that uses the service) startup, Firefox detects an update is ready to be applied
  • Whether or not the service should be used, Firefox executes updater.exe unelevated.
  • Firefox shuts itself down.
  • updater.exe unelevated checks to see if the service should be used, and if so tries to start the service.
  • updater.exe unelevated will write out a 'work item' file into the directory being watched with the work item details.
  • If the service can't be started, is disabled, or does not exist, Firefox will execute updater.exe as it used to without the service.
  • If the service was used, updater.exe unelevated waits for a global named event to be set which indicates the update is done.
  • Once done updater.exe unelevated will run the callback application and the post update process.

Applying an update from the service's perspective

  • The service gets started
  • The service watches the work item directory for work items.
  • The service detects that a 'work item' is written.
  • The service parses the information from the work item file.
  • The service verifies that updater.exe, and the callback application is our files (See section Signing builds below)
  • The service will execute the update with updater.exe under the context of its own session (session 0) using CreateProcess.
  • The path of updater.exe will be a copy of the updater.exe which exists inside the service directory. (A copy in case the original gets overwritten during the update).
  • The post update process (helper.exe) currently does i) system level stuff, and ii) user level stuff. This component will be executed twice. The system level stuff will also be executed under session 0 using CreateProcess. The user level stuff will be executed by the unelevated updater.exe with its own session ID.
  • The service sets a global named event to let the unelevated udpater.exe know it is done working.

When the service fails

  • If there is an error creating the work item, we will fall back to the old way of updating: using updater.exe.
  • If there is an error parsing the `work item` we assume it is not a valid work item that will be processed by the service.
  • If there is an error creating the process, or during update, or a sign check error, an error is written to update.status and info is reported via telemetry on the next restart of the callback application. The next restart of the callback application will also set the status back to pending so that the update will be applied next time without the service.

Service as an optional component

If the service is not installed, or disabled, we will fall back to the old way of updating: using updater.exe

Limited user accounts

Whether a user is an administrator or a limited user account, they can initiate an update.

Service installation

The service will be installed for users automatically via software update. There are reasons why the service will not be installed though:

  • An x64 native build is being used.
  • An OS lower than XP is being used.
  • The update is being performed as a limited user account.
  • The service was previously installed and manually uninstalled. (registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and value Attempted=1 means it will never be attempted on update again).

If the service is installed, there will be a checkbox in the installer for whether or not to install the service component. The service will be able to be uninstalled separately; it will show up as a new item under add/remove programs.

Signing builds

To ensure we execute our own binary, we will verify the signature of updater.exe to make sure it is signed by us. Currently we do not sign Nightly builds (I don't think Aurora either), we would like to have this fixed so that the udpate process gets more testing before this hits mozilla-central.

Spoofing the wrong session ID

All commands will be executed in the context of session 0. Only the post update process and callback will be executed with the user session ID. These commands will not be executed by the service though so there is no chance of spoofing the session ID via work item file.

The session ID is not included in the work item files because a limited user account could launch a valid Firefox.exe into an admin session with the URL of a dangerous page.

Applying updates faster

Another bug which is not part of this task plans to move updates away from on startup, and to perform them to an alternate directory in the background.

Preferences

  • There will be a new about:config option for whether or not to use the service. It will also be exposed in update preferences.
  • The new setting will be a boolean setting called app.update.service.enabled.
  • This will be defaulted to False if it does not exist, but we will set this to True for Firefox in new profiles. So for other products it will be defaulted to false, but for Firefox it will be defaulted to True.
  • Other products who want to use the service in the future should mark the setting as True.
  • There will be a new about:config option for keeping track of the number of service errors called app.update.service.errors.
  • There will be a new about:config option for keeping track of the max number of service errors to occur before disabling the service via app.update.service.enabled.
  • When app.update.service.errors reaches app.update.service.maxerrors, or 10 if maxerrors does not exist, the service will be disabled and app.update.service.errors will be reset to 0.

Test cases

Below I describe some important things that come to mind that we should be testing. All of the usual update tests and more should also be tested.

  • Test that using a limited user account does not install the service, nor prompt to install the service.
  • Test that if the service is already installed, installing an update of a higher service number will replace the old service.
  • Test that if the service is already installed, installing an update of a lower service number will NOT replace the old service.
  • Test that if the service is already installed, and another product with a higher version number gets installed, it will replace the old service.
  • Test that if the service is already installed, and another product with a lower version number gets installed, it will NOT replace the old service.
  • Test the same above rules cross architecture, from both x86 and x64.
  • Test applying updates from a limited user account.
  • Test applying updates from a Windows 2000 machine.
  • Test applying updates from a Windows XP machine.
  • Test applying updates from a Windows Vista machine at each of the UAC levels.
  • Test applying updates from a Windows 7 machine at each of the UAC levels.
  • Test doing 2 updates at once.
  • Test having a service with the same name that is not ours, we should apply update the old way on the next browser startup after the one that should have updated.
  • Test that limited user accounts have access to all of their files after an upgrade through the service.

QA's test plan can be found here: https://wiki.mozilla.org/Silent_Update_OS_Dialogs/TestPlan

Individual tests (Simona): http://bit.ly/v74fFt

Test slave requirements for automated testing (Ehsan): https://wiki.mozilla.org/Silent_Update_OS_Dialogs/Automated_testing/Test_slaves_requirements