ChromeOS uses a fixed GPT layout with 12 partitions. Two sets of rootfs + kernel partitions (A/B) enable atomic OS updates and rollback. The stateful partition is the only persistently writable region across reboots; everything else is either read-only or ephemeral.
cgpt show /dev/nvme0n1 or partx -s /dev/mmcblk0.Each kernel partition carries a ChromeOS-specific GPT attribute bitmask stored in partition entry bytes 48–63. The bootloader (depthcharge) reads three fields per slot:
| Field | Width | Values | Notes |
|---|---|---|---|
| priority | 4 bits | 0–15 | Higher value boots first; 0 = disabled |
| tries_remaining | 4 bits | 0–15 | Decremented on failed boot; 0 = unbootable |
| successful_boot | 1 bit | 0, 1 | Set by chromeos-setgoodkernel after a clean boot |
Update_engine writes a new kernel to the inactive slot, sets priority=1, tries_remaining=6, successful_boot=0, then triggers a reboot. If the new slot boots cleanly, a post-boot hook calls chromeos-setgoodkernel which flips successful_boot=1.
The active rootfs is an ext2 filesystem mounted read-only. Integrity is enforced by dm-verity (device-mapper target), which hashes each 4 KiB data block against a hash tree stored in a contiguous region at the end of the partition. The root hash of this tree is embedded in the signed kernel blob, closing the chain of trust from firmware through kernel to every file on disk.
dm="1 vroot none ro 1,0 <data_blocks> verity \
payload=PARTUUID=<root-guid> \
hashtree=PARTUUID=<root-guid> \
hashstart=<offset_sectors> \
alg=sha256 root_hexdigest=<256-bit-hash> \
salt=<salt>"
The kernel assembles this device at boot and mounts /dev/dm-0 (the verity device) as root. Any attempt to write to / will fail at the VFS level; the mount is MS_RDONLY.
rootfs_verification disable (via crossystem). This re-mounts rootfs rw after removing the verity device, but marks the system as in developer mode and clears the stateful partition.| Path | Description |
|---|---|
| /bin, /sbin, /usr | System binaries; immutable. No package manager modifies these post-build. |
| /etc | Mostly static config. Runtime overrides land in /run or bind-mounts from stateful. |
| /lib/firmware | Kernel firmware blobs; also covered by dm-verity. |
| /opt/google/chrome | Chrome browser binary and resources; largest single directory in rootfs. |
| /usr/share | Locale data, fonts, policy schemas; read-only. |
The STATE partition (GPT partition 1 on most boards) is ext4, formatted during OOBE or Powerwash, and mounted at /mnt/stateful_partition during early boot by chromeos_startup. It is the only persistently writable block device under normal operation.
/mnt/stateful_partition/
├── encrypted/ # ext4 image, dm-crypt, key from TPM
│ ├── chronos/ # non-cryptohome user state
│ ├── var/ # logs, shill state, update_engine state
│ └── usr/local/ # dev-mode installed packages (empty on release)
├── home/
│ ├── .shadow/ # cryptohome vaults keyed by uid/gid hash
│ ├── chronos/ # bind-mount target for /home/chronos
│ └── user/ # symlink → /home/chronos/user
├── unencrypted/ # items that survive Powerwash
│ ├── cros-components/ # DLC payloads (downloadable content)
│ ├── oobe_config_save/ # OOB enrollment state
│ └── preserve/ # enrollment keys, install attributes
└── var_overlay/ # tmpfs overlay; merged onto /var at boot
The encrypted/ subdirectory is not a directory in the traditional sense at rest — it is backed by a sparse file (encrypted.block) formatted as ext4 and encrypted with dm-crypt (AES-256-CBC). The encryption key is derived from the TPM and sealed to PCR values reflecting the current boot state. mount-encrypted (an upstart/init job) handles key retrieval and mounting on every boot.
# Encrypted stateful backing file path
/mnt/stateful_partition/.encrypted.block # sparse ext4 image
/mnt/stateful_partition/.encrypted.key # NVRAM-sealed key blob
Because rootfs is read-only, directories that need to survive reboots are bind-mounted from stateful. This is done by chromeos_startup before reaching the user session:
| Bind source (stateful) | Mount target | Notes |
|---|---|---|
| /mnt/stateful_partition/encrypted/var | /var | Logs, shill, tlsdate, etc. |
| /mnt/stateful_partition/encrypted/chronos | /home/chronos | Chrome profile base dir |
| /mnt/stateful_partition/encrypted/usr/local | /usr/local | Dev-mode only; empty on release builds |
| /mnt/stateful_partition/home/.shadow | /home/.shadow | Cryptohome vault root |
Each Google account that logs into a ChromeOS device gets a cryptohome: an isolated, encrypted storage vault tied to that account's credentials. The cryptohomed daemon (running as root) manages vault lifecycle via the UserDataAuth D-Bus API.
| Format | When used | Encryption | Key derivation |
|---|---|---|---|
| eCryptfs | Legacy; pre-2020 devices without ext4 encryption support | AES-128-CBC per-file | PBKDF2 from passphrase → master key → per-file FEK + FNEK |
| ext4 encryption (fscrypt) | Current default on kernel ≥ 4.14 with encrypt feature | AES-256-XTS (contents), AES-256-CTS (filenames) | key_serial (kernel keyring) fed by cryptohomed from TPM-wrapped blob |
| dmcrypt (dircrypto2) | Enterprise/kiosk; full-volume dm-crypt container per user | AES-256-CBC | TPM-sealed per-user key; requires PCR unsealing |
/home/.shadow/
└── <obfuscated_uid>/ # SHA256(salt + account_id)
├── vault/ # eCryptfs: encrypted directory tree (at rest)
| | # fscrypt: plaintext dir, kernel encrypts pages
├── mount/ # bind-mounted at /home/user/<uid> on login
├── master.0 # keyset: encrypted per-factor (password, PIN, etc.)
├── master.1 # additional auth factor keysets
└── skel/ # copied to vault on first mount
Each auth factor (password, PIN, smart card, fingerprint) wraps a copy of the vault encryption key (VKK). Authentication decrypts the VKK using the factor's specific mechanism, then cryptohomed passes the VKK to the kernel keyring (fscrypt) or uses it to derive the eCryptfs master key.
User passphrase
→ scrypt(passphrase, user_salt) → passphrase_key
Passphrase key
→ AES-unwrap TPM-sealed blob → vault_keyset_key
VKK
→ decrypts serialized KeysetBlob → fek (file encryption key)
→ fnek (filename encryption key)
A guest session provisions a tmpfs-backed cryptohome at login under /home/user/guest-XXXXXX. No data is written to the stateful partition; the entire vault lives in RAM and is destroyed when the session ends.
ChromeOS uses mount namespaces (via CLONE_NEWNS) to isolate storage visibility between system services, the browser, ARC++ (Android container), and Crostini (Linux VM). The root namespace is the parent; most system daemons inherit it, but containers get a private namespace populated at container start.
/ # dm-verity ext2 (rootfs ROOT-A) — ro
├── /proc # procfs
├── /sys # sysfs
├── /dev # devtmpfs + udev
├── /run # tmpfs — ephemeral sockets, pids, state
├── /tmp # tmpfs — session-scoped (size limited)
├── /var # bind → stateful/encrypted/var — rw
├── /usr/local # bind → stateful/encrypted/usr/local — dev only
├── /home/chronos # bind → stateful/encrypted/chronos — rw
├── /home/user/<obfuid> # cryptohome mount (fscrypt or eCryptfs vault) — rw
├── /mnt/stateful_partition # STATE ext4 — rw (root access only)
├── /media # removable media (USB, SD) — tmpfs + FUSE
│ ├── archive/ # Zip/dmg mounts via cros-disks
│ ├── fuse/ # SFTP, Google Drive, SMB (fusebox)
│ └── removable/ # block device mounts (vfat/ext4/exfat)
└── /opt/google/containers # ARC++ / Crostini image paths
cros-disks is a D-Bus service that mediates all removable media and network filesystem mounts. It spawns isolated FUSE helpers (sshfs, exfatfs, drivefs, smbfs) as the fuse user and mounts them under /media/fuse/. The browser never calls mount(2) directly; it issues D-Bus calls to cros-disks and receives a file path in response.
ChromeOS mounts several tmpfs instances early in boot. These are RAM-backed, size-limited, and cleared on every reboot. They serve as the write layer for files that must not persist.
| Mount | Default size | Purpose |
|---|---|---|
| /tmp | 50% RAM (capped ~512 MiB) | General-purpose scratch; Chrome uses this heavily for renderer IPC |
| /run | 10% RAM | Runtime state: PID files, D-Bus sockets, network config |
| /run/lock | 5 MiB | POSIX lock files |
| /dev/shm | 50% RAM | POSIX shared memory; GPU process IPC, media buffers |
| /run/imageloader | 128 KiB | DLC (Downloadable Content) squashfs loop-mount tree |
/tmp on ChromeOS is noticeably larger than on typical Linux distros because Chrome's renderer processes use memfd_create(2) and /tmp for IPC buffers. On low-RAM devices (<4 GiB), tmpfs pressure can contribute to OOM kills if Chrome opens many tabs.ARC++ runs Android inside a container (or lightweight VM on newer boards). Android's storage model maps onto ChromeOS paths via a combination of bind mounts and FUSE:
| Android path | ChromeOS backing | Mechanism |
|---|---|---|
| /data | /opt/google/containers/android/rootfs/android-data/data | bind mount into container; inside user's cryptohome |
| /sdcard | MediaProvider → FUSE (arcfs) | Fused view merging Downloads, Photos, Play files |
| /storage/emulated/0 | same as /sdcard | Android storage emulation over FUSE |
ARC++ data is stored inside the active user's cryptohome. A Powerwash destroys it along with all other user data.
Crostini runs a Debian container inside a crosvm VM (Termina). Storage is layered:
/home/user/<uid>/crosvm/<vm-name>.img inside the cryptohome.btrfs filesystem inside the VM image; containers are btrfs subvolumes (penguin is the default).Downloads and MyFiles paths are exposed to the container via virtiofs (replacing the older 9P/SFTP bridge).# VM image location (inside cryptohome, on-device)
/home/user/<obfuid>/crosvm/termina.img
# Inside the VM: btrfs subvolume for default container
/mnt/stateful/btrfs/<container-id>/ # Termina internal path
# Virtiofs shared directory (inside container)
/mnt/chromeos/MyFiles/
vmc disk-resize.A Powerwash reformats the stateful partition, destroying all user data, cryptohomes, and system state. It does not reflash the rootfs or firmware. The OS version is preserved.
Files under /mnt/stateful_partition/unencrypted/preserve/ are explicitly preserved before the reformat. These include:
install_attributes.pb)oobe_config_save/)Holding Esc+Refresh+Power at boot forces the device into recovery mode. Depthcharge attempts to boot KERN-C / MiniOS first (if available), then falls back to a USB/network recovery image. Recovery flashes both ROOT-A and ROOT-B, and reformats the stateful partition (full Powerwash).
| Operation | Rootfs | Stateful | Firmware |
|---|---|---|---|
| Powerwash | Unchanged | Reformatted | Unchanged |
| Recovery (MiniOS) | Re-flashed A+B | Reformatted | Unchanged |
| Full recovery (USB) | Re-flashed A+B | Reformatted | Optionally updated |
| Enterprise wipe | Unchanged | Reformatted (preserve kept) | Unchanged |
ChromeOS enables ext4 project quota on the stateful partition to track and enforce per-user and per-system-service disk usage. The cryptohomed daemon and the browser cooperate to report and cap usage.
Each cryptohome vault and Android data directory is assigned a unique project ID in the ext4 filesystem. Kernel quota accounting aggregates block usage by project, so quotactl(Q_XGETQUOTA) returns per-vault usage without scanning the directory tree.
| Project ID range | Assigned to |
|---|---|
| 1 – 999 | Reserved / system use |
| 1000 – 1999 | Per-user cryptohomes (GID-aligned) |
| 2000 – 2999 | ARC++ /data per user |
| 3000+ | DLC (Downloadable Content) component tracking |
The cryptohome D-Bus service exposes GetFreeDiskSpace() and IsLowDiskSpace(). Chrome queries these periodically and surfaces the Storage Management page when free space on the stateful partition falls below ~512 MiB. At critically low levels (<80 MiB), the session manager may refuse new logins.
# Query free space (crosh / dev shell)
dbus-send --system --print-reply \
--dest=org.chromium.CryptohomeUserDataAuth \
/org/chromium/CryptohomeUserDataAuth \
org.chromium.CryptohomeUserDataAuthInterface.GetFreeDiskSpace
Most filesystem inspection requires either a Developer Mode shell (VT-2 or crosh → shell) or SSH access via the test image. Commands below assume root access via sudo or a root shell.
# Show GPT partition table with ChromeOS kernel priority metadata
cgpt show /dev/nvme0n1
# Show kernel slot priorities and tries_remaining
cgpt show -v /dev/nvme0n1 | grep -A3 KERN
# Check which rootfs slot is active
rootdev -s
# dm-verity device status
dmsetup status vroot
# Show all active mounts
cat /proc/mounts
# Encrypted stateful size and use
df -h /mnt/stateful_partition
# Per-project quota usage (requires projectquota ext4 feature)
repquota -Ps /mnt/stateful_partition
# List mounted cryptohomes
cryptohome --action=dump_keyset --user=<email>
# Show vault type and encryption method
ls -la /home/.shadow/
# Free space from cryptohome daemon
cryptohome --action=get_status_string
# Check if a vault uses eCryptfs or fscrypt
file /home/.shadow/<uid>/vault
# List mounts managed by cros-disks
dbus-send --system --print-reply \
--dest=org.chromium.CrosDisks /org/chromium/CrosDisks \
org.chromium.CrosDisks.EnumerateAutoMountableDevices
# Log output from cros-disks (includes mount errors)
journalctl -u cros-disks -f