summaryrefslogtreecommitdiff
path: root/utility/mount-encrypted.c
diff options
context:
space:
mode:
Diffstat (limited to 'utility/mount-encrypted.c')
-rw-r--r--utility/mount-encrypted.c1349
1 files changed, 0 insertions, 1349 deletions
diff --git a/utility/mount-encrypted.c b/utility/mount-encrypted.c
deleted file mode 100644
index 9dbedc2a..00000000
--- a/utility/mount-encrypted.c
+++ /dev/null
@@ -1,1349 +0,0 @@
-/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- *
- * This tool will attempt to mount or create the encrypted stateful partition,
- * and the various bind mountable subdirectories.
- *
- */
-#define _GNU_SOURCE
-#define _FILE_OFFSET_BITS 64
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <grp.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/mount.h>
-#include <linux/fs.h>
-
-#include <glib.h>
-
-#include <openssl/rand.h>
-
-#define CHROMEOS_ENVIRONMENT
-#include "tlcl.h"
-#include "crossystem.h"
-
-#include "mount-encrypted.h"
-#include "mount-helpers.h"
-
-#define STATEFUL_MNT "mnt/stateful_partition"
-#define ENCRYPTED_MNT STATEFUL_MNT "/encrypted"
-#define BUF_SIZE 1024
-#define PROP_SIZE 64
-#define LOCKBOX_SIZE_MAX 0x45
-
-static const gchar * const kKernelCmdline = "/proc/cmdline";
-static const gchar * const kKernelCmdlineOption = " encrypted-stateful-key=";
-static const gchar * const kEncryptedFSType = "ext4";
-static const gchar * const kCryptDevName = "encstateful";
-static const gchar * const kTpmDev = "/dev/tpm0";
-static const gchar * const kNullDev = "/dev/null";
-static const gchar * const kNvramExport = "/tmp/lockbox.nvram";
-static const float kSizePercent = 0.3;
-static const float kMigrationSizeMultiplier = 1.1;
-static const uint32_t kLockboxIndex = 0x20000004;
-static const uint32_t kLockboxSizeV1 = 0x2c;
-static const uint32_t kLockboxSizeV2 = LOCKBOX_SIZE_MAX;
-static const uint32_t kLockboxSaltOffset = 0x5;
-static const uint64_t kSectorSize = 512;
-static const uint64_t kExt4BlockSize = 4096;
-static const uint64_t kExt4MinBytes = 16 * 1024 * 1024;
-static const char * const kStaticKeyDefault = "default unsafe static key";
-static const char * const kStaticKeyFactory = "factory unsafe static key";
-static const char * const kStaticKeyFinalizationNeeded = "needs finalization";
-static const int kModeProduction = 0;
-static const int kModeFactory = 1;
-static const int kCryptAllowDiscard = 1;
-
-enum migration_method {
- MIGRATE_TEST_ONLY,
- MIGRATE_FOR_REAL,
-};
-
-enum bind_dir {
- BIND_SOURCE,
- BIND_DEST,
-};
-
-static struct bind_mount {
- char * src; /* Location of bind source. */
- char * dst; /* Destination of bind. */
- char * previous; /* Migratable prior bind source. */
- char * pending; /* Location for pending deletion. */
- char * owner;
- char * group;
- mode_t mode;
- int submount; /* Submount is bound already. */
-} bind_mounts_default[] = {
- { ENCRYPTED_MNT "/var", "var",
- STATEFUL_MNT "/var", STATEFUL_MNT "/.var",
- "root", "root",
- S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, 0 },
- { ENCRYPTED_MNT "/chronos", "home/chronos",
- STATEFUL_MNT "/home/chronos", STATEFUL_MNT "/home/.chronos",
- "chronos", "chronos",
- S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, 1 },
- { },
-};
-
-#if DEBUG_ENABLED
-struct timeval tick = { };
-struct timeval tick_start = { };
-#endif
-
-static struct bind_mount *bind_mounts = NULL;
-static gchar *rootdir = NULL;
-static gchar *stateful_mount = NULL;
-static gchar *key_path = NULL;
-static gchar *needs_finalization_path = NULL;
-static gchar *block_path = NULL;
-static gchar *encrypted_mount = NULL;
-static gchar *dmcrypt_name = NULL;
-static gchar *dmcrypt_dev = NULL;
-static int has_tpm = 0;
-static int tpm_init_called = 0;
-static uint8_t nvram_data[LOCKBOX_SIZE_MAX];
-static uint32_t nvram_size = 0;
-
-static void tpm_init(void)
-{
- uint32_t result;
-
- if (tpm_init_called)
- return;
-
- DEBUG("Opening TPM");
-
- setenv("TPM_NO_EXIT", "1", 1);
- result = TlclLibInit();
-
- tpm_init_called = 1;
- has_tpm = (result == TPM_SUCCESS);
- INFO("TPM %s", has_tpm ? "ready" : "not available");
-}
-
-/* Returns TPM result status code, and on TPM_SUCCESS, stores ownership
- * flag to "owned".
- */
-static uint32_t tpm_owned(uint8_t *owned)
-{
- uint32_t result;
-
- tpm_init();
- DEBUG("Reading TPM Ownership Flag");
- if (!has_tpm)
- result = TPM_E_NO_DEVICE;
- else
- result = TlclGetOwnership(owned);
- DEBUG("TPM Ownership Flag returned: %s", result ? "FAIL" : "ok");
-
- return result;
-}
-
-static void tpm_close(void)
-{
- if (!has_tpm || !tpm_init_called)
- return;
- TlclLibClose();
- tpm_init_called = 0;
-}
-
-static void sha256(char *string, uint8_t *digest)
-{
- SHA256((unsigned char *)string, strlen(string), digest);
-}
-
-/* Extract the desired system key from the kernel's boot command line. */
-static int get_key_from_cmdline(uint8_t *digest)
-{
- int result = 0;
- gchar *buffer;
- gsize length;
- char *cmdline, *option_end;
- /* Option name without the leading space. */
- const gchar *option = kKernelCmdlineOption + 1;
-
- if (!g_file_get_contents(kKernelCmdline, &buffer, &length, NULL)) {
- PERROR(kKernelCmdline);
- return 0;
- }
-
- /* Find a string match either at start of string or following
- * a space.
- */
- cmdline = buffer;
- if (strncmp(cmdline, option, strlen(option)) == 0 ||
- (cmdline = strstr(cmdline, kKernelCmdlineOption))) {
- /* The "=" exists because it is in kKernelCmdlineOption. */
- cmdline = strstr(cmdline, "=");
- /* strchrnul() cannot return NULL. */
- option_end = strchrnul(cmdline, ' ');
- *option_end = '\0';
- sha256(cmdline, digest);
- debug_dump_hex("system key", digest, DIGEST_LENGTH);
- result = 1;
- }
-
- g_free(buffer);
- return result;
-}
-
-static int get_system_property(const char *prop, char *buf, size_t length)
-{
- const char *rc;
-
- DEBUG("Fetching System Property '%s'", prop);
- rc = VbGetSystemPropertyString(prop, buf, length);
- DEBUG("Got System Property 'mainfw_type': %s", rc ? buf : "FAIL");
-
- return rc != NULL;
-}
-
-static int has_chromefw(void)
-{
- static int state = -1;
- char fw[PROP_SIZE];
-
- /* Cache the state so we don't have to perform the query again. */
- if (state != -1)
- return state;
-
- if (!get_system_property("mainfw_type", fw, sizeof(fw)))
- state = 0;
- else
- state = strcmp(fw, "nonchrome") != 0;
- return state;
-}
-
-static int is_cr48(void)
-{
- static int state = -1;
- char hwid[PROP_SIZE];
-
- /* Cache the state so we don't have to perform the query again. */
- if (state != -1)
- return state;
-
- if (!get_system_property("hwid", hwid, sizeof(hwid)))
- state = 0;
- else
- state = strstr(hwid, "MARIO") != NULL;
- return state;
-}
-
-static uint32_t
-_read_nvram(uint8_t *buffer, size_t len, uint32_t index, uint32_t size)
-{
- uint32_t result;
-
- if (size > len) {
- ERROR("NVRAM size (0x%x > 0x%zx) is too big", size, len);
- return 0;
- }
-
- tpm_init();
- DEBUG("Reading NVRAM area 0x%x (size %u)", index, size);
- if (!has_tpm)
- result = TPM_E_NO_DEVICE;
- else
- result = TlclRead(index, buffer, size);
- DEBUG("NVRAM read returned: %s", result == TPM_SUCCESS ? "ok"
- : "FAIL");
-
- return result;
-}
-
-/*
- * TPM cases:
- * - does not exist at all (disabled in test firmware or non-chrome device).
- * - exists (below).
- *
- * TPM ownership cases:
- * - unowned (OOBE):
- * - expect modern lockbox (no migration allowed).
- * - owned: depends on NVRAM area (below).
- *
- * NVRAM area cases:
- * - no NVRAM area at all:
- * - interrupted install (cryptohome has the TPM password)
- * - ancient device (cr48, cryptohome has thrown away TPM password)
- * - broken device (cryptohome has thrown away/never had TPM password)
- * - must expect worst-case: no lockbox ever, and migration allowed.
- * - defined NVRAM area, but not written to ("Finalized"); interrupted OOBE:
- * - if legacy size, allow migration.
- * - if not, disallow migration.
- * - written ("Finalized") NVRAM area:
- * - if legacy size, allow migration.
- * - if not, disallow migration.
- *
- * When returning 1: (NVRAM area found and used)
- * - *digest populated with NVRAM area entropy.
- * - *migrate is 1 for NVRAM v1, 0 for NVRAM v2.
- * When returning 0: (NVRAM missing or error)
- * - *digest untouched.
- * - *migrate always 1
- */
-static int get_nvram_key(uint8_t *digest, int *migrate)
-{
- uint8_t owned = 0;
- uint8_t value[kLockboxSizeV2], bytes_anded, bytes_ored;
- uint32_t size, result, i;
- uint8_t *rand_bytes;
- uint32_t rand_size;
-
- /* Default to allowing migration (disallow when owned with NVRAMv2). */
- *migrate = 1;
-
- /* Ignore unowned TPM's NVRAM area. */
- result = tpm_owned(&owned);
- if (result != TPM_SUCCESS) {
- INFO("Could not read TPM Permanent Flags: error 0x%02x.",
- result);
- return 0;
- }
- if (!owned) {
- INFO("TPM not Owned, ignoring NVRAM area.");
- return 0;
- }
-
- /* Reading the NVRAM takes 40ms. Instead of querying the NVRAM area
- * for its size (which takes time), just read the expected size. If
- * it fails, then fall back to the older size. This means cleared
- * devices take 80ms (2 failed reads), legacy devices take 80ms
- * (1 failed read, 1 good read), and populated devices take 40ms,
- * which is the minimum possible time (instead of 40ms + time to
- * query NVRAM size).
- */
- size = kLockboxSizeV2;
- result = _read_nvram(value, sizeof(value), kLockboxIndex, size);
- if (result != TPM_SUCCESS) {
- size = kLockboxSizeV1;
- result = _read_nvram(value, sizeof(value), kLockboxIndex, size);
- if (result != TPM_SUCCESS) {
- /* No NVRAM area at all. */
- INFO("No NVRAM area defined: error 0x%02x", result);
- return 0;
- }
- /* Legacy NVRAM area. */
- INFO("Version 1 NVRAM area found.");
- } else {
- *migrate = 0;
- INFO("Version 2 NVRAM area found.");
- }
-
- debug_dump_hex("nvram", value, size);
-
- /* Ignore defined but unwritten NVRAM area. */
- bytes_ored = 0x0;
- bytes_anded = 0xff;
- for (i = 0; i < size; ++i) {
- bytes_ored |= value[i];
- bytes_anded &= value[i];
- }
- if (bytes_ored == 0x0 || bytes_anded == 0xff) {
- INFO("NVRAM area has been defined but not written.");
- return 0;
- }
-
- /* "Export" nvram data for use after the helper. */
- if (size <= sizeof(nvram_data)) {
- nvram_size = size;
- memcpy(nvram_data, value, size);
- }
-
- /* Choose random bytes to use based on NVRAM version. */
- if (*migrate) {
- rand_bytes = value;
- rand_size = size;
- } else {
- rand_bytes = value + kLockboxSaltOffset;
- if (kLockboxSaltOffset + DIGEST_LENGTH > size) {
- INFO("Impossibly small NVRAM area size (%d).", size);
- return 0;
- }
- rand_size = DIGEST_LENGTH;
- }
- if (rand_size < DIGEST_LENGTH) {
- INFO("Impossibly small rand_size (%d).", rand_size);
- return 0;
- }
- debug_dump_hex("rand_bytes", rand_bytes, rand_size);
-
- SHA256(rand_bytes, rand_size, digest);
- debug_dump_hex("system key", digest, DIGEST_LENGTH);
-
- return 1;
-}
-
-/* Find the system key used for decrypting the stored encryption key.
- * ChromeOS devices are required to use the NVRAM area, all the rest will
- * fallback through various places (kernel command line, BIOS UUID, and
- * finally a static value) for a system key.
- */
-static int find_system_key(int mode, uint8_t *digest, int *migration_allowed)
-{
- gchar *key;
- gsize length;
-
- /* By default, do not allow migration. */
- *migration_allowed = 0;
-
- /* Factory mode uses a static system key. */
- if (mode == kModeFactory) {
- INFO("Using factory insecure system key.");
- sha256((char *)kStaticKeyFactory, digest);
- debug_dump_hex("system key", digest, DIGEST_LENGTH);
- return 1;
- }
-
- /* Force ChromeOS devices into requiring the system key come from
- * NVRAM.
- */
- if (has_chromefw()) {
- int rc;
- rc = get_nvram_key(digest, migration_allowed);
-
- if (rc) {
- INFO("Using NVRAM as system key; already populated%s.",
- *migration_allowed ? " (legacy)" : "");
- } else {
- INFO("Using NVRAM as system key; finalization needed.");
- }
- return rc;
- }
-
- if (get_key_from_cmdline(digest)) {
- INFO("Using kernel command line argument as system key.");
- return 1;
- }
- if (g_file_get_contents("/sys/class/dmi/id/product_uuid",
- &key, &length, NULL)) {
- sha256(key, digest);
- debug_dump_hex("system key", digest, DIGEST_LENGTH);
- g_free(key);
- INFO("Using UUID as system key.");
- return 1;
- }
-
- INFO("Using default insecure system key.");
- sha256((char *)kStaticKeyDefault, digest);
- debug_dump_hex("system key", digest, DIGEST_LENGTH);
- return 1;
-}
-
-/* Returns 1 on success, 0 on failure. */
-static int get_random_bytes_tpm(unsigned char *buffer, int wanted)
-{
- uint32_t remaining = wanted;
-
- tpm_init();
- /* Read random bytes from TPM, which can return short reads. */
- while (remaining) {
- uint32_t result, size;
-
- result = TlclGetRandom(buffer + (wanted - remaining),
- remaining, &size);
- if (result != TPM_SUCCESS || size > remaining) {
- ERROR("TPM GetRandom failed: error 0x%02x.", result);
- return 0;
- }
- remaining -= size;
- }
-
- return 1;
-}
-
-/* Returns 1 on success, 0 on failure. */
-static int get_random_bytes(unsigned char *buffer, int wanted)
-{
- if (has_tpm && get_random_bytes_tpm(buffer, wanted))
- return 1;
-
- if (RAND_bytes(buffer, wanted))
- return 1;
- SSL_ERROR("RAND_bytes");
-
- return 0;
-}
-
-static char *choose_encryption_key(void)
-{
- unsigned char rand_bytes[DIGEST_LENGTH];
- unsigned char digest[DIGEST_LENGTH];
-
- if (!get_random_bytes(rand_bytes, sizeof(rand_bytes)))
- ERROR("No entropy source found -- using uninitialized stack");
-
- SHA256(rand_bytes, DIGEST_LENGTH, digest);
- debug_dump_hex("encryption key", digest, DIGEST_LENGTH);
-
- return stringify_hex(digest, DIGEST_LENGTH);
-}
-
-static int check_bind(struct bind_mount *bind, enum bind_dir dir)
-{
- struct passwd *user;
- struct group *group;
- const gchar *target;
-
- if (dir == BIND_SOURCE)
- target = bind->src;
- else
- target = bind->dst;
-
- if (access(target, R_OK) && mkdir(target, bind->mode)) {
- PERROR("mkdir(%s)", target);
- return -1;
- }
-
- /* Destination may be on read-only filesystem, so skip tweaks. */
- if (dir == BIND_DEST)
- return 0;
-
- if (!(user = getpwnam(bind->owner))) {
- PERROR("getpwnam(%s)", bind->owner);
- return -1;
- }
- if (!(group = getgrnam(bind->group))) {
- PERROR("getgrnam(%s)", bind->group);
- return -1;
- }
-
- /* Must do explicit chmod since mkdir()'s mode respects umask. */
- if (chmod(target, bind->mode)) {
- PERROR("chmod(%s)", target);
- return -1;
- }
- if (chown(target, user->pw_uid, group->gr_gid)) {
- PERROR("chown(%s)", target);
- return -1;
- }
-
- return 0;
-}
-
-static int migrate_contents(struct bind_mount *bind,
- enum migration_method method)
-{
- const gchar *previous = NULL;
- const gchar *pending = NULL;
- gchar *dotdir;
-
- /* Skip migration if the previous bind sources are missing. */
- if (bind->pending && access(bind->pending, R_OK) == 0)
- pending = bind->pending;
- if (bind->previous && access(bind->previous, R_OK) == 0)
- previous = bind->previous;
- if (!pending && !previous)
- return 0;
-
- /* Pretend migration happened. */
- if (method == MIGRATE_TEST_ONLY)
- return 1;
-
- check_bind(bind, BIND_SOURCE);
-
- /* Prefer the pending-delete location when doing migration. */
- if (!(dotdir = g_strdup_printf("%s/.", pending ? pending : previous))) {
- PERROR("g_strdup_printf");
- goto mark_for_removal;
- }
-
- INFO("Migrating bind mount contents %s to %s.", dotdir, bind->src);
- const gchar *cp[] = {
- "/bin/cp", "-a",
- dotdir,
- bind->src,
- NULL
- };
-
- if (runcmd(cp, NULL) != 0) {
- /* If the copy failed, it may have partially populated the
- * new source, so we need to remove the new source and
- * rebuild it. Regardless, the previous source must be removed
- * as well.
- */
- INFO("Failed to migrate %s to %s!", dotdir, bind->src);
- remove_tree(bind->src);
- check_bind(bind, BIND_SOURCE);
- }
-
-mark_for_removal:
- g_free(dotdir);
-
- /* The removal of the previous directory needs to happen at finalize
- * time, otherwise /var state gets lost on a migration if the
- * system is powered off before the encryption key is saved. Instead,
- * relocate the directory so it can be removed (or re-migrated).
- */
-
- if (previous) {
- /* If both pending and previous directory exists, we must
- * remove previous entirely now so it stops taking up disk
- * space. The pending area will stay pending to be deleted
- * later.
- */
- if (pending)
- remove_tree(pending);
- if (rename(previous, bind->pending)) {
- PERROR("rename(%s,%s)", previous, bind->pending);
- }
- }
-
- /* As noted above, failures are unrecoverable, so getting here means
- * "we're done" more than "it worked".
- */
- return 1;
-}
-
-static void finalized(void)
-{
- /* TODO(keescook): once ext4 supports secure delete, just unlink. */
- if (access(needs_finalization_path, R_OK) == 0) {
- /* This is nearly useless on SSDs. */
- shred(needs_finalization_path);
- unlink(needs_finalization_path);
- }
-}
-
-static void finalize(uint8_t *system_key, char *encryption_key)
-{
- struct bind_mount *bind;
-
- INFO("Writing keyfile %s.", key_path);
- if (!keyfile_write(key_path, system_key, encryption_key)) {
- ERROR("Failed to write %s -- aborting.", key_path);
- return;
- }
-
- finalized();
-
- for (bind = bind_mounts; bind->src; ++ bind) {
- if (!bind->pending || access(bind->pending, R_OK))
- continue;
- INFO("Removing %s.", bind->pending);
-#if DEBUG_ENABLED
- continue;
-#endif
- remove_tree(bind->pending);
- }
-}
-
-static void needs_finalization(char *encryption_key)
-{
- uint8_t useless_key[DIGEST_LENGTH];
- sha256((char *)kStaticKeyFinalizationNeeded, useless_key);
-
- INFO("Writing finalization intent %s.", needs_finalization_path);
- if (!keyfile_write(needs_finalization_path, useless_key,
- encryption_key)) {
- ERROR("Failed to write %s -- aborting.",
- needs_finalization_path);
- return;
- }
-}
-
-/* This triggers the live encryption key to be written to disk, encrypted
- * by the system key. It is intended to be called by Cryptohome once the
- * TPM is done being set up. If the system key is passed as an argument,
- * use it, otherwise attempt to query the TPM again.
- */
-static int finalize_from_cmdline(char *key)
-{
- uint8_t system_key[DIGEST_LENGTH];
- char *encryption_key;
- int migrate;
-
- /* Early sanity-check to see if the encrypted device exists,
- * instead of failing at the end of this function.
- */
- if (access(dmcrypt_dev, R_OK)) {
- ERROR("'%s' does not exist, giving up.", dmcrypt_dev);
- return EXIT_FAILURE;
- }
-
- if (key) {
- if (strlen(key) != 2 * DIGEST_LENGTH) {
- ERROR("Invalid key length.");
- return EXIT_FAILURE;
- }
-
- if (!hexify_string(key, system_key, DIGEST_LENGTH)) {
- ERROR("Failed to convert hex string to byte array");
- return EXIT_FAILURE;
- }
- } else {
- /* Factory mode will never call finalize from the command
- * line, so force Production mode here.
- */
- if (!find_system_key(kModeProduction, system_key, &migrate)) {
- ERROR("Could not locate system key.");
- return EXIT_FAILURE;
- }
- }
-
- encryption_key = dm_get_key(dmcrypt_dev);
- if (!encryption_key) {
- ERROR("Could not locate encryption key for %s.", dmcrypt_dev);
- return EXIT_FAILURE;
- }
-
- finalize(system_key, encryption_key);
-
- return EXIT_SUCCESS;
-}
-
-static void spawn_resizer(const char *device, uint64_t blocks,
- uint64_t blocks_max)
-{
- pid_t pid;
-
- /* Skip resize before forking, if it's not going to happen. */
- if (blocks >= blocks_max) {
- INFO("Resizing skipped. blocks:%" PRIu64 " >= blocks_max:%" PRIu64,
- blocks, blocks_max);
- return;
- }
-
- fflush(NULL);
- pid = fork();
- if (pid < 0) {
- PERROR("fork");
- return;
- }
- if (pid != 0) {
- INFO("Started filesystem resizing process %d.", pid);
- return;
- }
-
- /* Child */
- tpm_close();
- INFO_INIT("Resizer spawned.");
-
- if (daemon(0, 1)) {
- PERROR("daemon");
- goto out;
- }
-
- filesystem_resize(device, blocks, blocks_max);
-
-out:
- INFO_DONE("Done.");
- exit(0);
-}
-
-/* Do all the work needed to actually set up the encrypted partition.
- * Takes "mode" argument to help determine where the system key should
- * come from.
- */
-static int setup_encrypted(int mode)
-{
- int has_system_key;
- uint8_t system_key[DIGEST_LENGTH];
- char *encryption_key = NULL;
- int migrate_allowed = 0, migrate_needed = 0, rebuild = 0;
- gchar *lodev = NULL;
- uint64_t sectors;
- struct bind_mount *bind;
- int sparsefd;
- struct statvfs stateful_statbuf;
- uint64_t blocks_min, blocks_max;
- int valid_keyfile = 0;
-
- /* Use the "system key" to decrypt the "encryption key" stored in
- * the stateful partition.
- */
- has_system_key = find_system_key(mode, system_key, &migrate_allowed);
- if (has_system_key) {
- encryption_key = keyfile_read(key_path, system_key);
- } else {
- INFO("No usable system key found.");
- }
-
- if (encryption_key) {
- /* If we found a stored encryption key, we've already
- * finished a complete login and Cryptohome Finalize
- * so migration is finished.
- */
- migrate_allowed = 0;
- valid_keyfile = 1;
- } else {
- uint8_t useless_key[DIGEST_LENGTH];
- sha256((char *)kStaticKeyFinalizationNeeded, useless_key);
- encryption_key = keyfile_read(needs_finalization_path,
- useless_key);
- if (!encryption_key) {
- /* This is a brand new system with no keys. */
- INFO("Generating new encryption key.");
- encryption_key = choose_encryption_key();
- if (!encryption_key)
- return 0;
- rebuild = 1;
- } else {
- ERROR("Finalization unfinished! " \
- "Encryption key still on disk!");
- }
- }
-
- if (rebuild) {
- uint64_t fs_bytes_max;
-
- /* Wipe out the old files, and ignore errors. */
- unlink(key_path);
- unlink(block_path);
-
- /* Calculate the desired size of the new partition. */
- if (statvfs(stateful_mount, &stateful_statbuf)) {
- PERROR(stateful_mount);
- return 0;
- }
- fs_bytes_max = stateful_statbuf.f_blocks;
- fs_bytes_max *= kSizePercent;
- fs_bytes_max *= stateful_statbuf.f_frsize;
-
- INFO("Creating sparse backing file with size %" PRIu64 ".",
- fs_bytes_max);
-
- /* Create the sparse file. */
- sparsefd = sparse_create(block_path, fs_bytes_max);
- if (sparsefd < 0) {
- PERROR(block_path);
- return 0;
- }
- } else {
- sparsefd = open(block_path, O_RDWR | O_NOFOLLOW);
- if (sparsefd < 0) {
- PERROR(block_path);
- return 0;
- }
- }
-
- /* Set up loopback device. */
- INFO("Loopback attaching %s (named %s).", block_path, dmcrypt_name);
- lodev = loop_attach(sparsefd, dmcrypt_name);
- if (!lodev || strlen(lodev) == 0) {
- ERROR("loop_attach failed");
- goto failed;
- }
-
- /* Get size as seen by block device. */
- sectors = blk_size(lodev) / kSectorSize;
- if (!sectors) {
- ERROR("Failed to read device size");
- goto lo_cleanup;
- }
-
- /* Mount loopback device with dm-crypt using the encryption key. */
- INFO("Setting up dm-crypt %s as %s.", lodev, dmcrypt_dev);
- if (!dm_setup(sectors, encryption_key, dmcrypt_name, lodev,
- dmcrypt_dev, kCryptAllowDiscard)) {
- /* If dm_setup() fails, it could be due to lacking
- * "allow_discard" support, so try again with discard
- * disabled. There doesn't seem to be a way to query
- * the kernel for this feature short of a fallible
- * version test or just trying to set up the dm table
- * again, so do the latter.
- */
- if (!dm_setup(sectors, encryption_key, dmcrypt_name, lodev,
- dmcrypt_dev, !kCryptAllowDiscard)) {
- ERROR("dm_setup failed");
- goto lo_cleanup;
- }
- INFO("%s: dm-crypt does not support discard; disabling.",
- dmcrypt_dev);
- }
-
- /* Decide now if any migration will happen. If so, we will not
- * grow the new filesystem in the background, since we need to
- * copy the contents over before /var is valid again.
- */
- if (!rebuild)
- migrate_allowed = 0;
- if (migrate_allowed) {
- for (bind = bind_mounts; bind->src; ++ bind) {
- if (migrate_contents(bind, MIGRATE_TEST_ONLY))
- migrate_needed = 1;
- }
- }
-
- /* Calculate filesystem min/max size. */
- blocks_max = sectors / (kExt4BlockSize / kSectorSize);
- blocks_min = kExt4MinBytes / kExt4BlockSize;
- if (migrate_needed && migrate_allowed) {
- uint64_t fs_bytes_min;
- uint64_t calc_blocks_min;
- /* When doing a migration, the new filesystem must be
- * large enough to hold what we're going to migrate.
- * Instead of walking the bind mount sources, which would
- * be IO and time expensive, just read the bytes-used
- * value from statvfs (plus 10% for overhead). It will
- * be too large, since it includes the eCryptFS data, so
- * we must cap at the max filesystem size just in case.
- */
-
- /* Bytes used in stateful partition plus 10%. */
- fs_bytes_min = stateful_statbuf.f_blocks -
- stateful_statbuf.f_bfree;
- fs_bytes_min *= stateful_statbuf.f_frsize;
- DEBUG("Stateful bytes used: %" PRIu64 "", fs_bytes_min);
- fs_bytes_min *= kMigrationSizeMultiplier;
-
- /* Minimum blocks needed for that many bytes. */
- calc_blocks_min = fs_bytes_min / kExt4BlockSize;
- /* Do not use more than blocks_max. */
- if (calc_blocks_min > blocks_max)
- calc_blocks_min = blocks_max;
- /* Do not use less than blocks_min. */
- else if (calc_blocks_min < blocks_min)
- calc_blocks_min = blocks_min;
-
- DEBUG("Maximum fs blocks: %" PRIu64 "", blocks_max);
- DEBUG("Minimum fs blocks: %" PRIu64 "", blocks_min);
- DEBUG("Migration blocks chosen: %" PRIu64 "", calc_blocks_min);
- blocks_min = calc_blocks_min;
- }
-
- if (rebuild) {
- INFO("Building filesystem on %s "
- "(blocksize:%" PRIu64 ", min:%" PRIu64 ", max:%" PRIu64 ").",
- dmcrypt_dev, kExt4BlockSize, blocks_min, blocks_max);
- if (!filesystem_build(dmcrypt_dev, kExt4BlockSize,
- blocks_min, blocks_max))
- goto dm_cleanup;
- }
-
- /* Mount the dm-crypt partition finally. */
- INFO("Mounting %s onto %s.", dmcrypt_dev, encrypted_mount);
- if (access(encrypted_mount, R_OK) &&
- mkdir(encrypted_mount, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
- PERROR(dmcrypt_dev);
- goto dm_cleanup;
- }
- if (mount(dmcrypt_dev, encrypted_mount, kEncryptedFSType,
- MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_RELATIME,
- "discard,commit=600")) {
- PERROR("mount(%s,%s)", dmcrypt_dev, encrypted_mount);
- goto dm_cleanup;
- }
-
- /* Always spawn filesystem resizer, in case growth was interrupted. */
- /* TODO(keescook): if already full size, don't resize. */
- spawn_resizer(dmcrypt_dev, blocks_min, blocks_max);
-
- /* If the legacy lockbox NVRAM area exists, we've rebuilt the
- * filesystem, and there are old bind sources on disk, attempt
- * migration.
- */
- if (migrate_needed && migrate_allowed) {
- /* Migration needs to happen before bind mounting because
- * some partitions were not already on the stateful partition,
- * and would be over-mounted by the new bind mount.
- */
- for (bind = bind_mounts; bind->src; ++ bind)
- migrate_contents(bind, MIGRATE_FOR_REAL);
- }
-
- /* Perform bind mounts. */
- for (bind = bind_mounts; bind->src; ++ bind) {
- INFO("Bind mounting %s onto %s.", bind->src, bind->dst);
- if (check_bind(bind, BIND_SOURCE) ||
- check_bind(bind, BIND_DEST))
- goto unbind;
- if (mount(bind->src, bind->dst, "none", MS_BIND, NULL)) {
- PERROR("mount(%s,%s)", bind->src, bind->dst);
- goto unbind;
- }
- }
-
- /* When we are creating the encrypted mount for the first time,
- * either finalize immediately, or write the encryption key to
- * disk (*sigh*) to handle the seemingly endless broken or
- * wedged TPM states.
- */
- if (rebuild) {
- /* Devices that already have the NVRAM area populated and
- * are being rebuilt don't need to wait for Cryptohome
- * because the NVRAM area isn't going to change.
- *
- * Devices that do not have the NVRAM area populated
- * may potentially never have the NVRAM area populated,
- * which means we have to write the encryption key to
- * disk until we finalize. Once secure deletion is
- * supported on ext4, this won't be as horrible.
- */
- if (has_system_key)
- finalize(system_key, encryption_key);
- else
- needs_finalization(encryption_key);
- } else {
- /* If we're not rebuilding and we have a sane system
- * key, then we must either need finalization (if we
- * failed to finalize in Cryptohome), or we have already
- * finalized, but maybe failed to clean up.
- */
- if (has_system_key) {
- if (!valid_keyfile)
- finalize(system_key, encryption_key);
- else
- finalized();
- }
- }
-
- free(lodev);
- return 1;
-
-unbind:
- for (bind = bind_mounts; bind->src; ++ bind) {
- INFO("Unmounting %s.", bind->dst);
- umount(bind->dst);
- }
-
- INFO("Unmounting %s.", encrypted_mount);
- umount(encrypted_mount);
-
-dm_cleanup:
- INFO("Removing %s.", dmcrypt_dev);
- /* TODO(keescook): something holds this open briefly on mkfs failure
- * and I haven't been able to catch it yet. Adding an "fuser" call
- * here is sufficient to lose the race. Instead, just sleep during
- * the error path.
- */
- sleep(1);
- dm_teardown(dmcrypt_dev);
-
-lo_cleanup:
- INFO("Unlooping %s.", lodev);
- loop_detach(lodev);
-
-failed:
- free(lodev);
-
- return 0;
-}
-
-/* Clean up all bind mounts, mounts, attaches, etc. Only the final
- * action informs the return value. This makes it so that failures
- * can be cleaned up from, and continue the shutdown process on a
- * second call. If the loopback cannot be found, claim success.
- */
-static int shutdown(void)
-{
- struct bind_mount *bind;
-
- for (bind = bind_mounts; bind->src; ++ bind) {
- INFO("Unmounting %s.", bind->dst);
- errno = 0;
- /* Allow either success or a "not mounted" failure. */
- if (umount(bind->dst)) {
- if (errno != EINVAL) {
- PERROR("umount(%s)", bind->dst);
- return EXIT_FAILURE;
- }
- }
- }
-
- INFO("Unmounting %s.", encrypted_mount);
- errno = 0;
- /* Allow either success or a "not mounted" failure. */
- if (umount(encrypted_mount)) {
- if (errno != EINVAL) {
- PERROR("umount(%s)", encrypted_mount);
- return EXIT_FAILURE;
- }
- }
-
- /*
- * Force syncs to make sure we don't tickle racey/buggy kernel
- * routines that might be causing crosbug.com/p/17610.
- */
- sync();
-
- /* Optionally run fsck on the device after umount. */
- if (getenv("MOUNT_ENCRYPTED_FSCK")) {
- char *cmd;
-
- if (asprintf(&cmd, "fsck -a %s", dmcrypt_dev) == -1)
- PERROR("asprintf");
- else {
- int rc;
-
- rc = system(cmd);
- if (rc != 0)
- ERROR("'%s' failed: %d", cmd, rc);
- }
- }
-
- INFO("Removing %s.", dmcrypt_dev);
- if (!dm_teardown(dmcrypt_dev))
- ERROR("dm_teardown(%s)", dmcrypt_dev);
- sync();
-
- INFO("Unlooping %s (named %s).", block_path, dmcrypt_name);
- if (!loop_detach_name(dmcrypt_name)) {
- ERROR("loop_detach_name(%s)", dmcrypt_name);
- return EXIT_FAILURE;
- }
- sync();
-
- return EXIT_SUCCESS;
-}
-
-static void check_mount_states(void)
-{
- struct bind_mount *bind;
-
- /* Verify stateful partition exists. */
- if (access(stateful_mount, R_OK)) {
- INFO("%s does not exist.", stateful_mount);
- exit(1);
- }
- /* Verify stateful is either a separate mount, or that the
- * root directory is writable (i.e. a factory install, dev mode
- * where root remounted rw, etc).
- */
- if (same_vfs(stateful_mount, rootdir) && access(rootdir, W_OK)) {
- INFO("%s is not mounted.", stateful_mount);
- exit(1);
- }
-
- /* Verify encrypted partition is missing or not already mounted. */
- if (access(encrypted_mount, R_OK) == 0 &&
- !same_vfs(encrypted_mount, stateful_mount)) {
- INFO("%s already appears to be mounted.", encrypted_mount);
- exit(0);
- }
-
- /* Verify that bind mount targets exist. */
- for (bind = bind_mounts; bind->src; ++ bind) {
- if (access(bind->dst, R_OK)) {
- PERROR("%s mount point is missing.", bind->dst);
- exit(1);
- }
- }
-
- /* Verify that old bind mounts on stateful haven't happened yet. */
- for (bind = bind_mounts; bind->src; ++ bind) {
- if (bind->submount)
- continue;
-
- if (same_vfs(bind->dst, stateful_mount)) {
- INFO("%s already bind mounted.", bind->dst);
- exit(1);
- }
- }
-
- INFO("VFS mount state sanity check ok.");
-}
-
-static int report_info(void)
-{
- uint8_t system_key[DIGEST_LENGTH];
- uint8_t owned = 0;
- struct bind_mount *mnt;
- int migrate = -1;
-
- printf("TPM: %s\n", has_tpm ? "yes" : "no");
- if (has_tpm) {
- printf("TPM Owned: %s\n", tpm_owned(&owned) != TPM_SUCCESS ?
- "fail" : (owned ? "yes" : "no"));
- }
- printf("ChromeOS: %s\n", has_chromefw() ? "yes" : "no");
- printf("CR48: %s\n", is_cr48() ? "yes" : "no");
- if (has_chromefw()) {
- int rc;
- rc = get_nvram_key(system_key, &migrate);
- if (!rc)
- printf("NVRAM: missing.\n");
- else {
- printf("NVRAM: %s, %s.\n",
- migrate ? "legacy" : "modern",
- rc ? "available" : "ignored");
- }
- }
- else {
- printf("NVRAM: not present\n");
- }
-
- printf("rootdir: %s\n", rootdir);
- printf("stateful_mount: %s\n", stateful_mount);
- printf("key_path: %s\n", key_path);
- printf("block_path: %s\n", block_path);
- printf("encrypted_mount: %s\n", encrypted_mount);
- printf("dmcrypt_name: %s\n", dmcrypt_name);
- printf("dmcrypt_dev: %s\n", dmcrypt_dev);
- printf("bind mounts:\n");
- for (mnt = bind_mounts; mnt->src; ++mnt) {
- printf("\tsrc:%s\n", mnt->src);
- printf("\tdst:%s\n", mnt->dst);
- printf("\tprevious:%s\n", mnt->previous);
- printf("\tpending:%s\n", mnt->pending);
- printf("\towner:%s\n", mnt->owner);
- printf("\tmode:%o\n", mnt->mode);
- printf("\tsubmount:%d\n", mnt->submount);
- printf("\n");
- }
-
- return EXIT_SUCCESS;
-}
-
-/* This expects "mnt" to be allocated and initialized to NULL bytes. */
-static int dup_bind_mount(struct bind_mount *mnt, struct bind_mount *old,
- char *dir)
-{
- if (old->src && asprintf(&mnt->src, "%s%s", dir, old->src) == -1)
- goto fail;
- if (old->dst && asprintf(&mnt->dst, "%s%s", dir, old->dst) == -1)
- goto fail;
- if (old->previous && asprintf(&mnt->previous, "%s%s", dir,
- old->previous) == -1)
- goto fail;
- if (old->pending && asprintf(&mnt->pending, "%s%s", dir,
- old->pending) == -1)
- goto fail;
- if (!(mnt->owner = strdup(old->owner)))
- goto fail;
- if (!(mnt->group = strdup(old->group)))
- goto fail;
- mnt->mode = old->mode;
- mnt->submount = old->submount;
-
- return 0;
-
-fail:
- perror(__FUNCTION__);
- return 1;
-}
-
-static void prepare_paths(void)
-{
- char *dir = NULL;
- struct bind_mount *old;
- struct bind_mount *mnt;
-
- mnt = bind_mounts = calloc(sizeof(bind_mounts_default) /
- sizeof(*bind_mounts_default),
- sizeof(*bind_mounts_default));
- if (!mnt) {
- perror("calloc");
- exit(1);
- }
-
- if ((dir = getenv("MOUNT_ENCRYPTED_ROOT")) != NULL) {
- unsigned char digest[DIGEST_LENGTH];
- gchar *hex;
-
- if (asprintf(&rootdir, "%s/", dir) == -1)
- goto fail;
-
- /* Generate a shortened hash for non-default cryptnames,
- * which will get re-used in the loopback name, which
- * must be less than 64 (LO_NAME_SIZE) bytes. */
- sha256(dir, digest);
- hex = stringify_hex(digest, sizeof(digest));
- hex[17] = '\0';
- if (asprintf(&dmcrypt_name, "%s_%s", kCryptDevName,
- hex) == -1)
- goto fail;
- g_free(hex);
- } else {
- rootdir = "/";
- if (!(dmcrypt_name = strdup(kCryptDevName)))
- goto fail;
- }
-
- if (asprintf(&stateful_mount, "%s%s", rootdir, STATEFUL_MNT) == -1)
- goto fail;
- if (asprintf(&key_path, "%s%s", rootdir,
- STATEFUL_MNT "/encrypted.key") == -1)
- goto fail;
- if (asprintf(&needs_finalization_path, "%s%s", rootdir,
- STATEFUL_MNT "/encrypted.needs-finalization") == -1)
- goto fail;
- if (asprintf(&block_path, "%s%s", rootdir,
- STATEFUL_MNT "/encrypted.block") == -1)
- goto fail;
- if (asprintf(&encrypted_mount, "%s%s", rootdir, ENCRYPTED_MNT) == -1)
- goto fail;
- if (asprintf(&dmcrypt_dev, "/dev/mapper/%s", dmcrypt_name) == -1)
- goto fail;
-
- for (old = bind_mounts_default; old->src; ++old) {
- if (dup_bind_mount(mnt++, old, rootdir))
- exit(1);
- }
-
- return;
-
-fail:
- perror("asprintf");
- exit(1);
-}
-
-/* Exports NVRAM contents to tmpfs for use by install attributes */
-void nvram_export(uint8_t *data, uint32_t size)
-{
- int fd;
- DEBUG("Export NVRAM contents");
- if (!size || !data)
- return;
- fd = open(kNvramExport, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
- if (fd < 0) {
- perror("open(nvram_export)");
- return;
- }
- if (write(fd, data, size) != size) {
- /* Don't leave broken files around */
- unlink(kNvramExport);
- }
- close(fd);
-}
-
-int main(int argc, char *argv[])
-{
- int okay;
- int mode = kModeProduction;
-
- INFO_INIT("Starting.");
- prepare_paths();
-
- if (argc > 1) {
- if (!strcmp(argv[1], "umount"))
- return shutdown();
- else if (!strcmp(argv[1], "info"))
- return report_info();
- else if (!strcmp(argv[1], "finalize"))
- return finalize_from_cmdline(argc > 2 ? argv[2] : NULL);
- else if (!strcmp(argv[1], "factory"))
- mode = kModeFactory;
- else {
- fprintf(stderr,
- "Usage: %s [info|finalize|umount|factory]\n",
- argv[0]);
- return 1;
- }
- }
-
- check_mount_states();
-
- okay = setup_encrypted(mode);
- /* If we fail, let chromeos_startup handle the stateful wipe. */
-
- if (okay)
- nvram_export(nvram_data, nvram_size);
-
- INFO_DONE("Done.");
-
- /* Continue boot. */
- return okay ? EXIT_SUCCESS : EXIT_FAILURE;
-}