diff options
Diffstat (limited to 'utility/mount-encrypted.c')
-rw-r--r-- | utility/mount-encrypted.c | 1349 |
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; -} |