This is the design document for Chromium Updater.
The objective is to create an updater for desktop client software using Chromium code and tools. The updater is an open-source drop-in replacement for Google Update/Omaha and can be customized by 3rd party embedders for updating non-Google client software.
The desktop platforms include Windows, macOS, Linux.
The updater is responsible for:
The behavior of the updater is mostly platform-independent. However, platform- specific modules exist to translate cross-platform concepts (such as IPC interfaces) to platform-specific technologies (such as COM or XPC). Additionally, some updater behavior related to installs and uninstalls are tailored to platform conventions.
The updater is layered atop //components/update_client, which implements a cross-platform mechanism to interact with an Omaha server for the purpose of updating CRXs. //components/update_client is also used by the component and extension updaters in Chrome.
To keep applications up-to-date, the updater periodically polls an Omaha server, communicating the state of installed applications, and receiving update instructions. It applies updates the server instructs it to, and then reports the results of the operation back to Omaha servers.
Once installed, the updater operates as a collection of processes that are launched on-demand and orchestrate their operations over IPC. The server process hosts an engine that conducts most of the work of updating software, and it is driven by client processes that issue commands to it and potentially display UI to the user.
The updater may be installed per-user or system-wide. If installed per-user, the updater can only update applications owned by that user, whereas a system- wide updater can update applications owned by any entity on the system. In multi-user systems, it is efficient for software such as the browser to be installed system-wide, owned by root (or the system user) and run by individual users, but this requires the updater to maintain root privileges in order to update it. Therefore, in a system-wide installation, the server process runs as root (or at high integrity). A system-wide installation of the updater and any number of per-user installations of the updater can coexist and operate independently on the same system.
Different versions of the updater can coexist even within the same installation of the updater, but only one such instance is active. Inactive versions of the updater periodically attempt to qualify themselves by running self-tests and (if they pass) take over as the active updater; or uninstall themselves as out of date once a newer version of the updater has activated.
The functionality of the server process is split into two interfaces:
Where necessary, the updater acquires a single cross-process lock to prevent races between activations and ongoing update operations.
Instances of the updater are installed in one of the following ways:
The updater is distributed via the following installer types:
An online installer installs the updater and then acts as a client process and commands the server process to install the application the user desired.
Online installers are composed of three parts: a tag, a metainstaller, and an updater resource. The metainstaller is a lightweight executable that uncompresses its updater resource into a safe temporary directory and then launches the updater's setup client process (--install). It passes along the tag, an unsigned (and untrusted) piece of data that is embedded in the executable which communicates the installation parameters for the software the user is trying to install.
TODO(crbug.com/1035895): Document tagging format, process, contents.
TODO(crbug.com/1035895): Document updater resource compression.
Online installers are typically run by the end user after being downloaded from the Internet. When the installer is executed, the OS launches it at medium integrity.
However, depending on the tag, the updater and application may need to be installed system-wide. To achieve this, the metainstaller must re-launch itself at high integrity. This will result in an UAC prompt on Windows.
The metainstaller determines whether to relaunch before unpacking the updater resources, since unpacking to a user-writable temp directory is not secure for a system-wide installation.
The metainstaller appends the --expect-elevated switch to the relaunch command line, to allow the relaunched process to exit with an error if it is not running at the correct integrity level.
Offline install performs the installation with no update check or file download against the server in the process. All data is read from the files in the specified directory instead.
An example offline install command line on Windows platform:
updater.exe /handoff "&appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&appname=MyApp&needsadmin=True&installdataindex =verboselog"
/installsource offline
/sessionid "{E85204C6-6F2F-40BF-9E6C-4952208BB977}"
/offlinedir "C:\Users\chrome-bot\AppData\Local\ForgedPath"]
Please note DOS style command line switch is also supported for backward compatibility.
The offline install process looks for OfflineManifest.gup in the offline directory, and falls back to <app-id>.gup if needed. The .gup file contains the update check response in XML format. An example of the XML file:
<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0">
<app appid="{CDABE316-39CD-43BA-8440-6D1E0547AEE6}" status="ok">
<updatecheck status="ok">
<urls>
<url codebase="http://dl.google.com/foo/install/1.2.3.4/"/>
</urls>
<manifest version="1.2.3.4">
<packages>
<package hash="abcdef" hash_sha256="sha256hash_foobar"
name="my_installer.exe" required="true" size="12345678"/>
</packages>
<actions>
<action event="install" needsadmin="false" run="my_installer.exe"
arguments="--baz"/>
<action event="postinstall" onsuccess="exitsilentlyonlaunchcmd"/>
</actions>
</manifest>
</updatecheck>
<!-- "system_level" is not included in any of the definitions. needsadmin
controls this. -->
<data index="verboselog" name="install" status="ok">
{
"distribution": {
"verbose_logging": true
}
}
</data>
</app>
</response>
The manifest file is parsed to extract the installer command. The above manifest leads to installation command:
C:\Users\chrome-bot\AppData\Local\ForgedPath\my_installer.exe --baz
The example handoff install command line also specifies installdataindex=verboselog, and the manifest has a matching install data. That means the install data is extracted and passed to the app installer. See installdataindex below for details.
TODO(crbug.com/1035895): Document the standalone installer.
TODO(crbug.com/1035895): Document bundling the updater with apps.
Instances are installed in the Unqualified state. Whenever an instance’s server starts up, it checks whether it should transition to a new state. Only the server instance in the Active state is permitted to make changes to the various applications managed by the updater. When an updater enters the Active state, previously active updaters will discover when they next start up that they should transition out of the Active state and uninstall themselves.
Before activating, unqualified instances of the updater first perform a self-test. The instance registers a “qualification app” with itself and checks for updates to that application with the production update server. The production server responds with an update, and the updater downloads the update and applies it, running the qualification app installer. If this all succeeds, the updater transitions to the qualified state and exits.
The qualification app installer is a no-op installer that simply exits with no error.
Qualification is skipped (along with any other pre-active states) if there is no active instance of the updater.
When an instance transitions to the active state, it acquires the lock on the global prefs file, writes and flushes the “swapping” bit to the file, and then replaces any non-side-by-side elements of the installation (such as COM or launchd registrations, Omaha 3 or Keystone shims, and more) with its own. Then, it clears the “swapping” bit and starts listening for instructions on the RPC channels. The swapping bit ensures that the an updater will recover and repair the system even if the program is interrupted mid-activation. The full algorithm is:
on_startup:
acquire_scoped_global_prefs_lock()
if global_prefs.has_active_version():
if global_prefs.active_version > this_version:
uninstall_self()
exit
if !local_prefs.qualified:
if qualify():
local_prefs.qualified = true
exit
if this_version > global_prefs.active_version or global_prefs.swapping:
activate()
else
activate()
activate:
global_prefs.swapping = true
global_prefs.flush()
replace_active_version()
if !global_prefs.imported_legacy_updaters:
import_data_from_legacy_updaters()
global_prefs.imported_legacy_updaters = true
global_prefs.active_version = this.version
global_prefs.swapping = false
global_prefs.flush()
The updater maintains its state in a set of “prefs” files, which are dictionaries serialized to the disk as JSON.
The global prefs file is shared across all instances of the updater. It contains data about the registered applications and the active updater's state.
Each instance of the updater also has its own separate local prefs file. Local prefs store information specific to the instance that owns them.
Read and write access to the global prefs file is controlled by a lock. The lock must also be held while the updater performs any non-side-by-side operation (such as installing an update for a registered application). The lock is per- process. The updater will periodically poll for the lock, sleeping for a short time between polls.
On Windows, the lock is implemented as a kernel mutex.
On macOS, the lock is implemented using bootstrap_check_in(), interpreting ownership of receive rights on a Mach service name as ownership of a lock.
TODO(crbug.com/1035895): Describe app installs.
TODO(crbug.com/1035895): Describe installer APIs (per platform).
TODO(crbug.com/1035895): Document UI, accessibility, internationalization.
TODO(crbug.com/1035895): Document relevant enterprise policies.
TODO(crbug.com/1035895): Document registration API.
needsadminneedsadmin is one of the install parameters that can be specified for first installs via the metainstaller tag. needsadmin is used to indicate whether the application needs admin rights to install.
For example, here is a command line for the Updater on Windows that includes:
UpdaterSetup.exe --install --tag="appguid=YourAppID&needsadmin=False"
In this case, the updater client understands that the application installer needs to install the application on a per-user basis for the current user.
needsadmin has the following supported values:
true: the application supports being installed systemwide and once installed, is available to all users on the system.false: the application supports only user installs.prefers: the application installation is first attempted systemwide. If the user refuses the UAC prompt however, the application is then only installed for the current user. The application installer needs to be able to support the installation as system, or per-user, or both modes.When UpdaterSetup encounters needsadmin, it does the following, based on the needsadmin value:
true: runs the application installer as system, with any elevation prompts that may be required to run as system first.false: runs the application installer as the current user.prefers: attempts to run the application installation as system first. If elevation fails however, runs the application installer as the current user.installdataindexinstalldataindex is one of the install parameters that can be specified for first installs on the command line or via the metainstaller tag.
For example, here is a typical command line for the Updater on Windows:
UpdaterSetup.exe /install "appguid=YourAppID&appname=YourAppName&needsadmin=False&lang=en&installdataindex =verboselog"
In this case, the updater client sends the installdataindex of verboselog to the update server.
This involves:
This is how a JSON request from update_client may look like:
{
"request":{
"@os":"win",
"@updater":"updater",
"acceptformat":"crx3",
"app":[
{
"appid":"YourAppID",
"data":[
{
"index":"verboselog",
"name":"install"
}
],
"enabled":true,
"installsource":"ondemand",
"ping":{
"r":-2
},
"updatecheck":{
"sameversionupdate":true
},
"version":"0.1"
}
],
"arch":"x86",
"dedup":"cr",
"domainjoined":true,
"hw":{
"avx":true,
"physmemory":32,
"sse":true,
"sse2":true,
"sse3":true,
"sse41":true,
"sse42":true,
"ssse3":true
},
"ismachine":false,
"lang":"en-US",
"nacl_arch":"x86-64",
"os":{
"arch":"x86_64",
"platform":"Windows",
"version":"10.0.19042.1586"
},
"prodversion":"101.0.4949.0",
"protocol":"3.1",
"requestid":"{6b417770-1f68-4d52-8843-356760c84d33}",
"sessionid":"{37775211-4487-48d5-845d-35a1d71b03bc}",
"updaterversion":"101.0.4949.0",
"wow64":true
}
}
The server retrieves the data corresponding to installdataindex=verboselog and returns it back to update_client.
This is how a JSON response from the update server may look like:
"response":{
"protocol":"3.1",
"app":[
{"appid":"12345",
"data":[{
"status":"ok",
"name":"install",
"index":"verboselog",
"#text":"{\"logging\":{\"verbose\":true}}"
}],
"updatecheck":{
"status":"ok",
"urls":{"url":[{"codebase":"http://example.com/"},
{"codebasediff":"http://diff.example.com/"}]},
"manifest":{
"version":"1.2.3.4",
"prodversionmin":"2.0.143.0",
"run":"UpdaterSetup.exe",
"arguments":"--arg1 --arg2",
"packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
}
}
]
}
update_client provides this response data back to the Updater.
The updater client writes this data to a temporary file in the same directory as the application installer. This is for security reasons, since writing the data to the temp directory could potentially allow a man-in-the-middle attack.
The updater client provides the temporary file as a parameter to the application installer.
Let's say, as shown above, that the update server responds with these example file contents:
{"logging":{"verbose":true}}
The updater client will now create a temporary file, say c:\my path\temporaryfile.dat (assuming the application installer is running from c:\my path\YesExe.exe), with the following file contents:
\xEF\xBB\xBF{"logging":{"verbose":true}}
and then provide the file as a parameter to the application installer:
"c:\my path\YesExe.exe" --installerdata="c:\my path\temporaryfile.dat"
EF BB BF.INSTALLERDATA="pathtofile".--installerdata="pathtofile".INSTALLERDATA="pathtofile".For every updater install, there is a timed scheduler that invokes the updater to check for updates. The default period is for the scheduler to call --wake on the updater every hour. Once the updater wakes, it will check if it has been long enough since the last update check to try again. The default is every 5 hours to do an update check.
Windows utilizes the Task Scheduler and the Windows Task Scheduler API to create a scheduler.
On Mac, the scheduler is implemented via LaunchAgents (for user-level installs) and LaunchDaemons (for system-level installs). The scheduled task is defined by the org.chromium.ChromiumUpdater.wake.1.2.3.4.plist, which contains a Label corresponding to the name of the plist, program arguments, which contains the path to the executable and the arguments it'll run with, and a StartInterval, which denotes interval for when launchctl should invoke the program. An example:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>AbandonProcessGroup</key> <true/> <key>Label</key> <string>org.chromium.ChromiumUpdater.wake.1.2.3.4</string> <key>LimitLoadToSessionType</key> <string>Aqua</string> <key>ProgramArguments</key> <array> <string>/Users/user/Library/Chromium/ChromiumUpdater/1.2.3.4/ChromiumUpdater.app/Contents/MacOS/ChromiumUpdater</string> <string>--wake</string> <string>--vmodule=*/chrome/updater/*=2,*/components/update_client/*=2</string> <string>--enable-logging</string> </array> <key>StartInterval</key> <integer>3600</integer> </dict> </plist>
TODO(crbug.com/1035895): Document differential updates.
TODO(crbug.com/1035895): Document recovery.
TODO(crbug.com/1035895): Document actives API & user count telemetry.
TODO(crbug.com/1035895): Document relevant enterprise policies.
TODO(crbug.com/1035895): Document ticket promotion (macOS).
TODO(crbug.com/1035895): Document usage-stats opt-in.
TODO(crbug.com/1035895): Document EULA/ToS acceptance.
TODO(crbug.com/1035895): Document uninstallation API.
TODO(crbug.com/1035895): Document file-based existence checker on macOS.
TODO(crbug.com/1035895): Document registry-based existence checker on Windows.
TODO(crbug.com/1035895): Document updater uninstallation.
TODO(crbug.com/1035895): Document prefs (global & local).
TODO(crbug.com/1035895): Document usage of registry on Windows.
TODO(crbug.com/1035895): Document usage of Keystone tickets on macOS.
TODO(crbug.com/1035895): Document COM.
On Mac, the IPC utilized for the updater is XPC, which is the macOS native IPC system controlled by launchd. The main portion to utilize XPC is to create launch agent plists under ${HOME}/Library/LaunchAgents for user level installs and launch daemon plists under /Library/LaunchDaemons for system level installs.
The updater uses multiple layers of XPC to start-up separate processes that are responsible for different actions. There are two XPC plists that the updater creates for the server. The first is the .internal plist, which goes through some control tasks first, and then there's the .service plist, which actually performs the update check and the update itself.
The XPC interface is also utilized to communicate between the browser and the updater for on-demand updates. The protocol utilized by the browser has to be in sync with the updater's protocol, or else the XPC call will fail. The same .service launchd plist is utilized to communicate between the browser and the updater for the on-demand updates.
Lastly, the XPC interface connects the browser and updater to promote the updater to a system-level updater. Promotion in this context means that the browser will install a system-level updater. This process entails the browser showing UI to the user on the About Page to prompt the user to promote. When the user accepts, the browser will then ask the user to elevate to allow root privileges so that a system-level updater can be installed. Inside of the browser, there exists a Privileged Helper tool executable. This is installed from the browser if promotion is selected via SMJobBless. When the Privileged Helper tool is installed, the browser can make an XPC connection to it and invoke a call to start the system-level updater installation process.
TODO(crbug.com/1035895): Document any IPC fallbacks that exist.
TODO(crbug.com/1035895): Document network stack (per platform).
TODO(crbug.com/1035895): Document CUP.
TODO(crbug.com/1035895): Document anti-DoS headers.
TODO(crbug.com/1035895): Document X-Retry-After behavior.
TODO(crbug.com/1035895): Document proxy behavior.
TODO(crbug.com/1035895): Document testing strategies, framework.
TODO(crbug.com/1035895): Document test build.
Testing is possible by programmatically controlling certain parameters of the updater. These parameters are hardcoded in the production builds but they can be modified in test builds. For this reason, the updater build scripts define two separate set of targets. One target produces binaries suitable to releasing. The other target produces test binaries, which are suffixed by _test. The test binaries link the test hooks code, which slightly alters the behavior of the updater to facilitate testing it.
TODO(crbug.com/1035895): Document external constants override.
TODO(crbug.com/1035895): Document builders & test machines.
TODO(crbug.com/1035895): Document old updater in third_party.
TODO(crbug.com/1035895): Document manual testing tools.
TODO(crbug.com/1035895): Document build.
TODO(crbug.com/1035895): Document signing.
TODO(crbug.com/1035895): Document branching & versioning.
TODO(crbug.com/1035895): Document A/B release qualification process.
TODO(crbug.com/1035895): Document tagger.
The strings for the metainstaller live in the //chrome/app/chromium_strings.grd and //chrome/app/google_chrome_strings.grd files. This allows the updater strings to utilize the Chromium repo's translation process instead of generating its own. Having it in existing grd files also eliminates the need to onboard updater specific grd files.
During the build process, the updater strings are embedded directly into the metainstaller binary via generate_embedded_i18n. generate_embedded_i18n also allows an extractor_datafile, which can define specific strings to pick out from the originating grd file. This way, the metainstaller only has the strings specific to the updater and not any of the other strings within the grd file. When the generate_embedded_i18n is complete, it generates an updater_installer_strings.h header, which contains macro definitions of the message ids and the offsets. The strings are mapped with their var name appended with _BASE. Then the _BASE appended macros are defined to be the first localization id in the list, in which case it is _AF.
An example from the updater_installer_strings.h
#define IDS_BUNDLE_INSTALLED_SUCCESSFULLY_AF 1600 #define IDS_BUNDLE_INSTALLED_SUCCESSFULLY_AM 1601 ... #define IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE IDS_BUNDLE_INSTALLED_SUCCESSFULLY_AF ... #define DO_STRING_MAPPING \ HANDLE_STRING(IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE, IDS_BUNDLE_INSTALLED_SUCCESSFULLY) \
Within the metainstaller, an l10_util.h/cc has three functions to get localized strings.
GetLocalizedString(int base_message_id) GetLocalizedStringF(int base_message_id, const std::wstring& replacement) GetLocalizedStringF(int base_message_id, std::vector<std::wstring> replacements)
One function for getting the literal string and two functions to get formatted strings. GetLocalizedString() uses the base id plus the offset based on the language to look through the binary's string table to get the correct, localized string. The formatted strings utilize GetLocalizedString() to get the string and then uses base::ReplaceStringPlaceholders() to remove the $i placeholders within the string. With regards to picking the correct language to utilize for the localized string, base::win::i18n::GetUserPreferredUILanguageList() is used to get the preferred UI languages from MUI. If there are multiple languages in the list, the first language in the list is picked.
The Application Command feature allows installed Updater-managed applications to pre-register and then later run command lines (elevated for system applications). The command lines can also include replaceable parameters substituted at runtime.
For legacy reasons:
IAppCommandWeb because it was meant to be used from an ActiveX control on a webpage at one point, andIDispatch for the same reason, to be used from script.For more information, please see the functional spec.