ChromeOS Filesystem and Storage Model

01

Disk Partition Layout

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.

read-only
read-write
critical / boot
EFI-SYSTEM 32 MiB ESP (FAT32); UEFI bootloader, depthcharge entry points
KERN-A 64 MiB Signed kernel + cmdline blob (priority=1 after update)
ROOT-A 4 GiB Primary rootfs (ext2, dm-verity protected); active slot
KERN-B 64 MiB Alternate kernel; used after rollback or mid-update
ROOT-B 4 GiB Alternate rootfs; inactive until slot swap completes
KERN-C 64 MiB Miniloader / recovery kernel (priority=0 under normal boot)
ROOT-C 4 MiB Unused rootfs slot (reserved; typically empty)
OEM 16 MiB OEM customization data (ro ext4; sourced at first boot)
RESERVED 1 MiB Future partition expansion; not currently mounted
RWFW 8 MiB Read-write firmware region (EC firmware staging)
MINIOS-A/B 2×512 MiB Minimal recovery OS (A/B); boots without network for local repair
STATE remainder Stateful partition (ext4); user data, cryptohomes, system state
Note
Partition sizes above are typical; exact sizes vary by board and storage capacity. Inspect the live layout with cgpt show /dev/nvme0n1 or partx -s /dev/mmcblk0.

A/B Slot Metadata

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:

FieldWidthValuesNotes
priority4 bits0–15Higher value boots first; 0 = disabled
tries_remaining4 bits0–15Decremented on failed boot; 0 = unbootable
successful_boot1 bit0, 1Set 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.

02

Rootfs & dm-verity

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.

RO FLASH (RO_SECTION)Hardware-enforced read-only region; contains root of trust
↓ signs
Kernel Blob (KERN-A)Contains verity root hash in kernel cmdline (dm=…)
↓ verity root hash covers
dm-verity targetVerifies each 4 KiB block on read; I/O error on mismatch
↓ exposes
ext2 rootfs (ROOT-A)Mounted read-only at /; all writes rejected at VFS layer

Kernel cmdline dm= parameter

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.

Warn
Enabling Developer Mode bypasses the write-protect check and allows 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.

Filesystem content highlights

PathDescription
/bin, /sbin, /usrSystem binaries; immutable. No package manager modifies these post-build.
/etcMostly static config. Runtime overrides land in /run or bind-mounts from stateful.
/lib/firmwareKernel firmware blobs; also covered by dm-verity.
/opt/google/chromeChrome browser binary and resources; largest single directory in rootfs.
/usr/shareLocale data, fonts, policy schemas; read-only.
03

Stateful Partition

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.

Directory structure

/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

Encrypted stateful volume

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
Note
Unlike per-user cryptohomes (which use eCryptfs or ext4 encryption), the encrypted stateful volume is a single dm-crypt container shared by all system services on the device. It stores system logs and some Chrome profile state not covered by per-user encryption.

Bind mounts from stateful into rootfs

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 targetNotes
/mnt/stateful_partition/encrypted/var/varLogs, shill, tlsdate, etc.
/mnt/stateful_partition/encrypted/chronos/home/chronosChrome profile base dir
/mnt/stateful_partition/encrypted/usr/local/usr/localDev-mode only; empty on release builds
/mnt/stateful_partition/home/.shadow/home/.shadowCryptohome vault root
04

Cryptohome & User Vaults

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.

Vault storage formats

FormatWhen usedEncryptionKey derivation
eCryptfsLegacy; pre-2020 devices without ext4 encryption supportAES-128-CBC per-filePBKDF2 from passphrase → master key → per-file FEK + FNEK
ext4 encryption (fscrypt)Current default on kernel ≥ 4.14 with encrypt featureAES-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 userAES-256-CBCTPM-sealed per-user key; requires PCR unsealing

Vault directory layout

/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

Auth factor → vault key chain

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)
Warn
Vault keys are never stored in cleartext on disk. If the TPM is cleared (e.g. ownership reset) or PCR values change (rootfs tampered), sealed blobs cannot be unseal-decrypted, and the vault is permanently inaccessible.

Guest sessions

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.

05

Mount Namespace & Tree

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.

Simplified post-login mount tree

/                          # 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 and FUSE mounts

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.

06

tmpfs, /tmp, and RAM Storage

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.

MountDefault sizePurpose
/tmp50% RAM (capped ~512 MiB)General-purpose scratch; Chrome uses this heavily for renderer IPC
/run10% RAMRuntime state: PID files, D-Bus sockets, network config
/run/lock5 MiBPOSIX lock files
/dev/shm50% RAMPOSIX shared memory; GPU process IPC, media buffers
/run/imageloader128 KiBDLC (Downloadable Content) squashfs loop-mount tree
Info
/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.
07

Linux Container Storage (Crostini & ARC++)

ARC++ (Android Runtime for Chrome)

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 pathChromeOS backingMechanism
/data/opt/google/containers/android/rootfs/android-data/databind mount into container; inside user's cryptohome
/sdcardMediaProvider → FUSE (arcfs)Fused view merging Downloads, Photos, Play files
/storage/emulated/0same as /sdcardAndroid 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 (Linux VM)

Crostini runs a Debian container inside a crosvm VM (Termina). Storage is layered:

# 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/
Warn
Crostini VM images can grow large (multi-GiB). They are not covered by ChromeOS storage quota enforcement and will consume stateful partition space until manually pruned via the Storage Management settings page or vmc disk-resize.
08

Powerwash & Recovery

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.

What survives Powerwash

Files under /mnt/stateful_partition/unencrypted/preserve/ are explicitly preserved before the reformat. These include:

Recovery mode

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).

OperationRootfsStatefulFirmware
PowerwashUnchangedReformattedUnchanged
Recovery (MiniOS)Re-flashed A+BReformattedUnchanged
Full recovery (USB)Re-flashed A+BReformattedOptionally updated
Enterprise wipeUnchangedReformatted (preserve kept)Unchanged
09

Disk Quota & Enforcement

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.

Quota project IDs

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 rangeAssigned to
1 – 999Reserved / system use
1000 – 1999Per-user cryptohomes (GID-aligned)
2000 – 2999ARC++ /data per user
3000+DLC (Downloadable Content) component tracking

Low-disk handling

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
10

Debugging & Inspection

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.

Partition & block layer

# 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

Stateful & mount tree

# 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

Cryptohome

# 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

FUSE / removable media

# 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
Note
On release builds without Dev Mode, shell access is blocked. To inspect the filesystem for diagnostic purposes, enable Dev Mode (Esc+Refresh+Power → Ctrl+D) — this Powerwashes the device — or use a USB debug/test image that has sshd enabled by default.