the Chromium logo

The Chromium Projects

ChromiumOS Configuration

Often times browser and OS programs need to change their behavior at runtime based on how the OS has been configured. Further, many of pieces of code often ask the same question (e.g. "Is dev mode enabled?"), and the way to answer the question might not be clear, or it might be possible to find the answer through different means. Here we'll document those various configuration sources and which ones you should (or should not) be using.

Remember that many of these settings are not finalized at build time, and the same binary might be used in many different configurations. For example, we do not compile programs for a specific channel (i.e. the same binary image is used on stable, beta, dev, and canary), nor do we compile for a specific runtime mode (i.e. programs need to detect dev-mode vs normal-mode).


This file provides a number of fields for the browser, daemons, and users. However, many of these fields are meant purely for users/debugging, and they should not be parsed out directly.

Here are all the known fields and whether people may rely on their value. If it is not marked as Usable, then you must not parse it out for anything other than informational display to users. If a field is not documented here, you must assume it's the same as Usable=No, in which case you must not parse it.

If you want to add more fields, they should go in os-release instead.

Usable Field Meaning Example Values
No CHROMEOS_RELEASE_APPID The release Omaha appid {DC2BBB48-BC2C-493E-82DA-89BEE8321A5A}
No CHROMEOS_BOARD_APPID This boards Omaha appid {DC2BBB48-BC2C-493E-82DA-89BEE8321A5A}
No CHROMEOS_CANARY_APPID The canary channel Omaha appid {90F229CE-83E2-4FAF-8479-E368A34938B1}
Yes CHROMEOS_ARC_VERSION The ARC++ version in the system 4979549
Yes CHROMEOS_ARC_ANDROID_SDK_VERSION The Android SDK version supported by ARC++ 25
No CHROMEOS_RELEASE_BOARD Human readable board name (and keyset used for signing) betty eve-signed-mpkeys
Yes CHROMEOS_RELEASE_BUILD_NUMBER Major OS version number 11012
Yes CHROMEOS_RELEASE_BRANCH_NUMBER Minor OS version number used by branches 0
Yes CHROMEOS_RELEASE_CHROME_MILESTONE Chrome major version/release number 70
Yes CHROMEOS_RELEASE_PATCH_NUMBER Patch OS version number (datestamps for developer builds) 0 2018_08_28_1422
Yes CHROMEOS_RELEASE_TRACK OS release channel stable-channel testimage-channel
No CHROMEOS_RELEASE_DESCRIPTION Human readable description for this build 11012.0.2018_08_28_1422 (Test Build - vapier) developer-build betty
No CHROMEOS_RELEASE_BUILD_TYPE Human readable build type Official Build Test Build - vapier
No CHROMEOS_RELEASE_NAME Human readable OS Product name ChromeOS ChromiumOS
Yes CHROMEOS_RELEASE_UNIBUILD Set to 1 for unified builds. 1
Yes CHROMEOS_RELEASE_VERSION The full OS version number 11012.0.2018_08_28_1422
Yes GOOGLE_RELEASE The full OS version number 11012.0.2018_08_28_1422
Yes CHROMEOS_AUSERVER URI used to get OS updates
Yes CHROMEOS_DEVSERVER URI to access the dev_server


See the lsb-release document for more details.


This file provides a number of fields for the browser, daemons, and users. However, many of these fields are meant purely for users/debugging, and they should not be parsed out directly.

More details on this file can be found in the os-release man page. It's a standard file among Linux distros and we aim to follow the spec.

Here are all the known fields and whether people may rely on their value. If it is not marked as Usable, then you must not parse it out for anything other than informational display to users. If a field is not documented here, you must assume it's the same as Usable=No, in which case you must not parse it.

Usable Field Meaning Example Value
Yes BUG_REPORT_URL Site where to report bugs
Yes BUILD_ID Full OS version 11012.0.2018_08_28_1422
Yes GOOGLE_CRASH_ID ID used when reporting crashes (crash-reporter) ChromeOS
Yes HOME_URL Site where people can find info about this project
Yes ID Unique ID for the OS chromeos
Yes ID_LIKE Unique IDs that the OS is related to chromiumos
Yes NAME For name for the OS ChromeOS
Yes VERSION Major release version 70
Yes VERSION_ID Full release version 70


Overrides and fragments can be dropped in here. These have higher precedence than the /etc/os-release file. The format is simple: the filename is the key and the content is the value.

For example, if there is an /etc/os-release.d/NAME file, then it will be used instead of the NAME=... line in the /etc/os-release file.


If you want to access fields in this file, please do not parse it yourself.

Compiled Code

In platform code, ChromiumOS provides a generic brillo::OsReleaseReader in brillo/osrelease_reader.h that can process this file. This handles both /etc/os-release and /etc/os-release.d/ automatically.

#include <brillo/osrelease_reader.h>
  brillo::OsReleaseReader reader;

  std::string value;
  if (!reader.GetString(kSomeKey, &value)) {
    // kSomeKey isn't defined.
    return false;

  // Do something with |value| now.

Shell Code

If you're writing shell code, you shouldn't be accessing this file. Please rewrite your code in a better language.


This vboot_reference crossystem program is used to query NVRAM and other low level system/firmware functionality.

While there are many fields, here are the only ones developers should rely upon in normal use. There are a few more, but only for specific usage, which we'll cover next.

All other fields not covered here should be avoided. Low level firmware projects (like firmware updater or vboot_reference) are a bit of an exception as they're tightly integrated.

Common Fields

Field R/W Description
block_devmode R/W Block all use of developer mode
clear_tpm_owner_request R/W Clear TPM owner on next boot
cros_debug RO OS should allow debug features. NB: This has no relationship to the OS channel.
dev_boot_legacy R/W Enable developer mode boot Legacy OSes
dev_boot_signed_only R/W Enable developer mode boot only from official kernels
dev_boot_usb R/W Enable developer mode boot from USB/SD
devsw_boot RO Developer switch position at boot. NB: Most code should check cros_debug instead.
devsw_cur RO Developer switch current position. NB: Most code should check cros_debug instead.
disable_dev_request R/W Disable virtual dev-mode on next boot
fwid RO Active firmware ID
inside_vm RO Running in a VM?
mainfw_act RO Active main firmware
mainfw_type RO Active main firmware type
nvram_cleared RO Have NV settings been lost?
recovery_reason RO Recovery mode reason for current boot
recovery_request R/W Recovery mode request
recoverysw_boot RO Recovery switch position at boot
recoverysw_cur RO Recovery switch current position
ro_fwid RO Read-only firmware ID
wpsw_cur RO Firmware write protect hardware switch current position

Specific-Use Fields

These fields may be used, but generally they're used only in highly specific code and projects.

Field R/W Description
debug_build RO OS image built for debug features. NB: Code should almost always check cros_debug instead. This has no relationship to the OS channel.
dev_enable_udc RO Enable USB Device Controller
fw_vboot2 RO If firmware was selected by vboot2
kernel_max_rollforward R/W Max kernel version to store into TPM
tpm_attack R/W TPM was interrupted since this flag was cleared
tpm_fwver RO Firmware version stored in TPM
tpm_kernver RO Kernel version stored in TPM
vdat_flags RO Raw bit flags from VbSharedData
wipeout_request R/W Firmware requested factory reset (wipeout)

Banned Fields

There are some fields that specifically should not be used. We'll document their alternatives.


Keep in mind that all fields are not guaranteed to always have a valid value. That means your queries should be constructed to "fail closed". In the case of cros_debug, you want to always check if dev-mode is enabled by using if crossystem 'cros_debug?1'; then and never check if dev-mode is disabled by using if ! crossystem 'cros_debug?0'; then. If the system were broken or failing and cros_debug was not set up properly, then cros_debug might not be 1 or 0, so the latter code will wrongly "succeed"!

Compiled Code

The crossystem.h header provides a few VbGet helpers in the vboot_host library. Be sure to use pkg-config to locate the vboot_host library instead of using -lvboot_host directly.

#include <crossystem.h>
  int value = VbGetSystemPropertyInt("cros_debug");
  if (value < 0) {
    // Unable to read the value.
    return false;

  if (value == 1) {
    // Dev mode is enabled.
    return true;
  } else {
    // Not dev mode.
    return false;

Shell Code

The crossystem command provides a simple interface for testing values.

  crossystem [param1 [param2 [...]]]
    Prints the current value(s) of the parameter(s).
  crossystem [param1=value1] [param2=value2 [...]]]
    Sets the parameter(s) to the specified value(s).
  crossystem [param1?value1] [param2?value2 [...]]]
    Checks if the parameter(s) all contain the specified value(s).

So you can write shell code like:

if crossystem 'cros_debug?1'; then
  # The cros_debug field is equal to 1.
  # The cros_debug field is not equal to 1 (including not set).


The vpd tool provides access to Vital Product Data. Its homepage provides all the details on tools and fields.

Briefly, you'll want to use vpd_get_value <field> in tools.


The chromeos-config project (for unibuild configurations) takes care of storing and accessing device-specific details. Its homepage provides all the details on tools and fields.

Briefly, you'll want to use the cros_config program and libcros_config library in tools. Be sure to use pkg-config to locate the cros_config library instead of using -lcros_config directly.


Many kernel files have system-wide settings that can be changed at runtime via the /proc/sys/ interface. If you want to override a default value, the best place to do this is via the /etc/sysctl.d/ directory using the sysctl.conf(5) syntax. This is processed early in the boot using sysctl(8)'s --system option. It runs before boot-services, and before writable filesystems are mounted. Thus it should be safe to use from a security perspective.

If you only want to set a default value, do not write /proc/sys/ directly. Only use config files under /etc/sysctl.d/.

Some kernel parameters might be set via kernel commandline options, including verbose names like (which would effectively write value to /proc/sys/foo/bar), but that should be avoided unless strictly necessary during early boot (before init/userspace runs). Every option in the kernel commandline requires review and approval by security in order to pass signing, and takes a while to be available to released images. Further, the size of the kernel commandline is limited, going as low as only 512 bytes for some architectures.

Config Files

The general format is documented in the sysctl.conf(5) man page.

If the setting exists on all kernel builds all the time, and it should be set for all devices, then the easiest answer is to update the [00-sysctl.conf file installed by the chromeos-base/chromeos-base package] ( If possible, this is the preferred route.

If the setting is tied to a specific package/USE flag, then the package can install a small config file into the /etc/sysctl.d/ directory itself.

Kernel Commandline

The Linux kernel has a commandline string where many internal kernel settings can be overridden. It also allows for passing arguments to init (e.g. upstart), modifying init's initial environment, and in general can be parsed by any userland program.

This string basically acts as a global variable that anyone can read. This is not a good thing.

Due to its limited size, use of this should be reserved for when there are no other alternatives. If a setting must be configured before userland boots (e.g. the console= or root= options) or if the firmware needs to modify it (e.g. depthcharge rewrites %U for the active rootfs), then it makes sense to live here. Otherwise, please find a different way of passing the value.

In general, this string is meant only for configuring the kernel, and for the firmware to communicate some settings to the kernel. Do not use it for arbitrary userland settings, and certainly not daemon-specific ones.

Because of the way the kernel automatically parses this setting, care must be taken when defining a new option that the kernel does not understand. Otherwise it can be easy to accidentally leak this into userland by setting it as a new environment variable.


The kernel commandline is stored in the verified kernel partition so that it is checked by the firmware before it's allowed to boot. This is to avoid people modifying it and adding malicious settings (e.g. setting init to an arbitrary shell script). This also means any changes to the commandline will invalidate the signature and the firmware will not boot it.

Further, every option requires review and approval by security in order to pass signing. If you want to add a new option that hasn't been approved previously, make sure to document how it's used and why it isn't a "security concern" to let boards override it.

Keep in mind that even once merged, it takes a while (on the order of a week) before the production signer will see the updated configs. If you want to add a new kernel commandline option to a board, it must follow this process before it can be added, otherwise the release builders will fail when the signer rejects the new/unknown option.

See the ~/chromiumos/src/platform/signing/signer-dev/security_test_baselines/ensure_secure_kernelparams.config file for more details.


If you must parse this, please avoid doing naive things like substring searches. The format is a string with arbitrary spacing and ordering, and no limit as to contents.

For example, if you wanted to add a keyword foo, and there are already foobar or kernel.setting="foo" settings, then simply grepping for foo will incorrectly match. Adding spaces around it like \ foo\ wouldn't work if the foo keyword was the first or last item.

This is why kernel settings should be properly tokenized and checked. The syntax largely boils down to key=value pairs with value being optionally quoted with " and separated by whitespace.

Size Limits

The kernel commandline is not infinitely large. It's not even reasonably big, and the exact limit depends on a number of factors, most significantly the architecture of the kernel. This is partially due to the kernel commandline being passed from the bootloader as part of the initial ABI.

The limit is defined in the kernel using the arch-specific COMMAND_LINE_SIZE constant.

Here are the relevant kernel limits for easy reference. Remember, this is the ABI of the kernel itself, not of userland which may be different.

Architecture Size (bytes)
arm (32-bit) 1024
arm64 (aarch64 / arm 64-bit) 2048
x86 32-bit 2048
x86_64 (x86 64-bit) 2048
mips (all bit sizes) 4096
riscv (32-bit & 64-bit) 1024
asm-generic 512

The asm-generic value is the default for new ports that don't define a more specific value. Think of it as "future intentions".

Bootloader Limits

The sizes above are how big of a buffer that the kernel will accept. Anything larger is usually silently truncated.

The other side of the ABI is the caller -- i.e. the bootloader. Even if the kernel were to increase its limits, the bootloader would need need update too.

CrOS uses depthcharge to boot the Linux kernel. It has a 4096 byte limit which means it can support all current architecture ports.

Other bootloaders (e.g. Das U-Boot and Grub and syslinux) should also support the minimum kernel sizes quoted above.

USE Flags

Gentoo USE flags may be used at build time to control high level features. This is useful for a build that wants to enable/disable large functionality like touchscreen support, stylus support, or ARC++ availability.

This will usually take the shape of the ebuild listing the flag in IUSE and then passing that down to the build environment.

These flags can be passed to the runtime as well via libchromeos-use-flags. This is explained in more detail in the login_manager documentation.

USE flags must not be used to set board names (e.g. kevin or link). See the board/device behavior FAQ for alternatives.


No package should be using the cros-board.eclass anymore. This was used in the past to allow packages to vary behavior at build time based on the board, but we no longer want to let packages do that.

You might still find some packages in the tree using them, but it's because we haven't yet migrated them off. is tracking that work.


How do I query dev mode status?

Use cros_debug?1 from crossystem. Never use cros_debug?0.

This tells you whether to enable dev-mode-specific features (like shell access). In general, programs should not change their behavior depending on whether the system is in dev-mode or normal-mode. This makes testing much harder as we do not run any tests in normal-mode.

Do not confuse dev-mode with "rootfs verification enabled" or "verified mode". It is possible to have rootfs verification enabled while in dev-mode.

Do not confuse dev-mode with the keyset used to sign or verify the system. This has no relationship to devkeys, premp keys, or mp keys.

Do not confuse dev-mode with the dev channel release track. They have no relationship at all.

How do I find the current channel?

Use CHROMEOS_RELEASE_TRACK from lsb-release.

This will have values like stable-channel, beta-channel, dev-channel, canary-channel, and testimage-channel. Take note that test images use a testimage prefix and not test.

It is not possible (by design) to build code for a specific channel. Our release process takes an OS image and publishes it in the canary channel. Promoting it to dev/beta/stable only involves modifying the lsb-release file. The code is never recompiled.

However, most code should never change its behavior based on the active channel. If you have a feature you want to keep testing in e.g. canary channel before releasing it in a newer version, then you should look into Finch instead. Among other things, it provides a kill switch in case something goes wrong.

How do I change behavior based on board/device?

If the board is already unibuild-enabled, then you should always use that to change runtime behavior. See the chromeos-config section for more details. If the project you're working on does not yet support unibuild, then you will need to update it to support unibuild. If the setting you want to vary isn't in the unibuild schema yet, then you will need to update it to include/document this new setting. The difficulty of extending existing projects is not an excuse to use deprecated methods for changing settings (i.e. the next paragraphs).

If you're working on an older board that isn't unibuild-enabled, then you have a few options:

How do I find the current board/device?

Please see the previous question about changing behavior based on board/device. If you follow those guidelines, you usually shouldn't need the exact board/device name in the first place.

Use cros_config / name to get the device name.

Put simply, a single board (e.g. coral) can be used across multiple devices (e.g. astronaut, nasher, robo, etc...). The device name is used to disambiguate at runtime in a single board image.

Do not use crossystem hwid or CHROMEOS_RELEASE_BOARD in /etc/lsb-release. These do not handle unibuild where a single board supports multiple devices.

How do I pass different flags to Chrome (the browser)?

See the login_manager documentation for more details.

How do I find out the keyset used to sign the image (PreMP/MP/etc...)?

In short, you don't. Code must not change its behavior based on whether the running image has been signed with "premp" or "mp" keys. Trying to do this significantly & negatively impacts both automated testing and early dogfood testing with users.

Use some other knob to control behavior (such as Finch).