diff options
Diffstat (limited to 'utility/mount-helpers.c')
-rw-r--r-- | utility/mount-helpers.c | 803 |
1 files changed, 0 insertions, 803 deletions
diff --git a/utility/mount-helpers.c b/utility/mount-helpers.c deleted file mode 100644 index aed5ef06..00000000 --- a/utility/mount-helpers.c +++ /dev/null @@ -1,803 +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 is a collection of helper utilities for use with the "mount-encrypted" - * utility. - * - */ -#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 <math.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/mount.h> -#include <linux/fs.h> -#include <linux/loop.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include <openssl/evp.h> - -#include "mount-encrypted.h" -#include "mount-helpers.h" - -static const gchar * const kRootDir = "/"; -static const gchar * const kLoopTemplate = "/dev/loop%d"; -static const int kLoopMajor = 7; -static const int kLoopMax = 8; -static const unsigned int kResizeStepSeconds = 2; -static const uint64_t kResizeBlocks = 32768 * 10; -static const uint64_t kBlocksPerGroup = 32768; -static const uint64_t kInodeRatioDefault = 16384; -static const uint64_t kInodeRatioMinimum = 2048; -static const gchar * const kExt4ExtendedOptions = "discard,lazy_itable_init"; - -int remove_tree(const char *tree) -{ - const gchar *rm[] = { - "/bin/rm", "-rf", tree, - NULL - }; - - return runcmd(rm, NULL); -} - -uint64_t blk_size(const char *device) -{ - uint64_t bytes; - int fd; - if ((fd = open(device, O_RDONLY | O_NOFOLLOW)) < 0) { - PERROR("open(%s)", device); - return 0; - } - if (ioctl(fd, BLKGETSIZE64, &bytes)) { - PERROR("ioctl(%s, BLKGETSIZE64)", device); - return 0; - } - close(fd); - return bytes; -} - -int runcmd(const gchar *argv[], gchar **output) -{ - gint rc; - gchar *out = NULL, *errout = NULL; - GError *err = NULL; - - g_spawn_sync(kRootDir, (gchar **)argv, NULL, 0, NULL, NULL, - &out, &errout, &rc, &err); - if (err) { - ERROR("%s: %s", argv[0], err->message); - g_error_free(err); - return -1; - } - - if (rc) - ERROR("%s failed (%d)\n%s\n%s", argv[0], rc, out, errout); - - if (output) - *output = out; - else - g_free(out); - g_free(errout); - - return rc; -} - -int same_vfs(const char *mnt_a, const char *mnt_b) -{ - struct stat stat_a, stat_b; - - if (lstat(mnt_a, &stat_a)) { - PERROR("lstat(%s)", mnt_a); - exit(1); - } - if (lstat(mnt_b, &stat_b)) { - PERROR("lstat(%s)", mnt_b); - exit(1); - } - return (stat_a.st_dev == stat_b.st_dev); -} - -/* Returns allocated string that holds [length]*2 + 1 characters. */ -char *stringify_hex(uint8_t *binary, size_t length) -{ - char *string; - size_t i; - - string = malloc(length * 2 + 1); - if (!string) { - PERROR("malloc"); - return NULL; - } - for (i = 0; i < length; ++i) - sprintf(string + (i * 2), "%02x", binary[i]); - string[length * 2] = '\0'; - - return string; -} - -/* Returns allocated byte array that holds strlen([string])/2 bytes. */ -uint8_t *hexify_string(char *string, uint8_t *binary, size_t length) -{ - size_t bytes, i; - - bytes = strlen(string) / 2; - if (bytes > length) { - ERROR("Hex string too long (%zu) for byte array (%zu)", - bytes, length); - return NULL; - } - - for (i = 0; i < bytes; ++i) { - if (sscanf(&string[i * 2], "%2hhx", &binary[i]) != 1) { - ERROR("Invalid hex code at byte %zu.", i); - return NULL; - } - } - - return binary; -} - -/* Overwrite file contents. Useless on SSD. :( */ -void shred(const char *pathname) -{ - uint8_t patterns[] = { 0xA5, 0x5A, 0xFF, 0x00 }; - FILE *target; - struct stat info; - uint8_t *pattern; - int fd, i; - - /* Give up if we can't safely open or stat the target. */ - if ((fd = open(pathname, O_WRONLY | O_NOFOLLOW)) < 0) { - PERROR(pathname); - return; - } - if (fstat(fd, &info)) { - close(fd); - PERROR(pathname); - return; - } - if (!(target = fdopen(fd, "w"))) { - close(fd); - PERROR(pathname); - return; - } - /* Ignore errors here, since there's nothing we can really do. */ - pattern = malloc(info.st_size); - for (i = 0; i < sizeof(patterns); ++i) { - memset(pattern, patterns[i], info.st_size); - if (fseek(target, 0, SEEK_SET)) - PERROR(pathname); - if (fwrite(pattern, info.st_size, 1, target) != 1) - PERROR(pathname); - if (fflush(target)) - PERROR(pathname); - if (fdatasync(fd)) - PERROR(pathname); - } - free(pattern); - /* fclose() closes the fd too. */ - fclose(target); -} - -static int is_loop_device(int fd) -{ - struct stat info; - - return (fstat(fd, &info) == 0 && S_ISBLK(info.st_mode) && - major(info.st_rdev) == kLoopMajor); -} - -static int loop_is_attached(int fd, struct loop_info64 *info) -{ - struct loop_info64 local_info; - - return ioctl(fd, LOOP_GET_STATUS64, info ? info : &local_info) == 0; -} - -/* Returns either the matching loopback name, or next available, if NULL. */ -static int loop_locate(gchar **loopback, const char *name) -{ - int i, fd, namelen = 0; - - if (name) { - namelen = strlen(name); - if (namelen >= LO_NAME_SIZE) { - ERROR("'%s' too long (>= %d)", name, LO_NAME_SIZE); - return -1; - } - } - - *loopback = NULL; - for (i = 0; i < kLoopMax; ++i) { - struct loop_info64 info; - int attached; - - g_free(*loopback); - *loopback = g_strdup_printf(kLoopTemplate, i); - if (!*loopback) { - PERROR("g_strdup_printf"); - return -1; - } - - fd = open(*loopback, O_RDONLY | O_NOFOLLOW); - if (fd < 0) { - PERROR("open(%s)", *loopback); - goto failed; - } - if (!is_loop_device(fd)) { - close(fd); - continue; - } - - memset(&info, 0, sizeof(info)); - attached = loop_is_attached(fd, &info); - close(fd); - - if (attached) - DEBUG("Saw %s on %s", info.lo_file_name, *loopback); - - if ((attached && name && - strncmp((char *)info.lo_file_name, name, namelen) == 0) || - (!attached && !name)) { - DEBUG("Using %s", *loopback); - /* Reopen for working on it. */ - fd = open(*loopback, O_RDWR | O_NOFOLLOW); - if (is_loop_device(fd) && - loop_is_attached(fd, NULL) == attached) - return fd; - } - } - ERROR("Ran out of loopback devices"); - -failed: - g_free(*loopback); - *loopback = NULL; - return -1; -} - -static int loop_detach_fd(int fd) -{ - if (ioctl(fd, LOOP_CLR_FD, 0)) { - PERROR("LOOP_CLR_FD"); - return 0; - } - return 1; -} - -int loop_detach(const gchar *loopback) -{ - int fd, rc = 1; - - fd = open(loopback, O_RDONLY | O_NOFOLLOW); - if (fd < 0) { - PERROR("open(%s)", loopback); - return 0; - } - if (!is_loop_device(fd) || !loop_is_attached(fd, NULL) || - !loop_detach_fd(fd)) - rc = 0; - - close (fd); - return rc; -} - -int loop_detach_name(const char *name) -{ - gchar *loopback = NULL; - int loopfd, rc; - - loopfd = loop_locate(&loopback, name); - if (loopfd < 0) - return 0; - rc = loop_detach_fd(loopfd); - - close(loopfd); - g_free(loopback); - return rc; -} - -/* Closes fd, returns name of loopback device pathname. */ -gchar *loop_attach(int fd, const char *name) -{ - gchar *loopback = NULL; - int loopfd; - struct loop_info64 info; - - loopfd = loop_locate(&loopback, NULL); - if (loopfd < 0) - return NULL; - if (ioctl(loopfd, LOOP_SET_FD, fd) < 0) { - PERROR("LOOP_SET_FD"); - goto failed; - } - - memset(&info, 0, sizeof(info)); - strncpy((char*)info.lo_file_name, name, LO_NAME_SIZE); - if (ioctl(loopfd, LOOP_SET_STATUS64, &info)) { - PERROR("LOOP_SET_STATUS64"); - goto failed; - } - - close(loopfd); - close(fd); - return loopback; -failed: - close(loopfd); - close(fd); - g_free(loopback); - return 0; -} - -int dm_setup(uint64_t sectors, const gchar *encryption_key, const char *name, - const gchar *device, const char *path, int discard) -{ - /* Mount loopback device with dm-crypt using the encryption key. */ - gchar *table = g_strdup_printf("0 %" PRIu64 " crypt " \ - "aes-cbc-essiv:sha256 %s " \ - "0 %s 0%s", - sectors, - encryption_key, - device, - discard ? " 1 allow_discards" : ""); - if (!table) { - PERROR("g_strdup_printf"); - return 0; - } - - const gchar *argv[] = { - "/sbin/dmsetup", - "create", name, - "--noudevrules", "--noudevsync", - "--table", table, - NULL - }; - - /* TODO(keescook): replace with call to libdevmapper. */ - if (runcmd(argv, NULL) != 0) { - g_free(table); - return 0; - } - g_free(table); - - /* Make sure the dm-crypt device showed up. */ - if (access(path, R_OK)) { - ERROR("%s does not exist", path); - return 0; - } - - return 1; -} - -int dm_teardown(const gchar *device) -{ - const char *argv[] = { - "/sbin/dmsetup", - "remove", device, - "--noudevrules", "--noudevsync", - NULL - }; - /* TODO(keescook): replace with call to libdevmapper. */ - if (runcmd(argv, NULL) != 0) - return 0; - return 1; -} - -char *dm_get_key(const gchar *device) -{ - gchar *output = NULL; - char *key; - int i; - const char *argv[] = { - "/sbin/dmsetup", - "table", "--showkeys", - device, - NULL - }; - /* TODO(keescook): replace with call to libdevmapper. */ - if (runcmd(argv, &output) != 0) - return NULL; - - /* Key is 4th field in the output. */ - for (i = 0, key = strtok(output, " "); - i < 4 && key; - ++i, key = strtok(NULL, " ")) { } - - /* Create a copy of the key and free the output buffer. */ - if (key) { - key = strdup(key); - g_free(output); - } - - return key; -} - -int sparse_create(const char *path, uint64_t bytes) -{ - int sparsefd; - - sparsefd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, - S_IRUSR | S_IWUSR); - if (sparsefd < 0) - goto out; - - if (ftruncate(sparsefd, bytes)) { - int saved_errno = errno; - - close(sparsefd); - unlink(path); - errno = saved_errno; - - sparsefd = -1; - } - -out: - return sparsefd; -} - -/* When creating a filesystem that will grow, the inode ratio is calculated - * using the starting size not the hinted "resize" size, which means the - * number of inodes can be highly constrained on tiny starting filesystems. - * Instead, calculate what the correct inode ratio should be for a given - * filesystem based on its expected starting and ending sizes. - * - * inode-ratio_mkfs = - * - * ceil(blocks_max / group-ratio) * size_mkfs - * ------------------------------------------------------------------ - * ceil(size_max / inode-ratio_max) * ceil(blocks_mkfs / group-ratio) - */ -static uint64_t get_inode_ratio(uint64_t block_bytes_in, - uint64_t blocks_mkfs_in, - uint64_t blocks_max_in) -{ - double block_bytes = (double)block_bytes_in; - double blocks_mkfs = (double)blocks_mkfs_in; - double blocks_max = (double)blocks_max_in; - - double size_max, size_mkfs, groups_max, groups_mkfs, inodes_max; - double denom, inode_ratio_mkfs; - - size_max = block_bytes * blocks_max; - size_mkfs = block_bytes * blocks_mkfs; - - groups_max = ceil(blocks_max / kBlocksPerGroup); - groups_mkfs = ceil(blocks_mkfs / kBlocksPerGroup); - - inodes_max = ceil(size_max / kInodeRatioDefault); - - denom = inodes_max * groups_mkfs; - /* Make sure we never trigger divide-by-zero. */ - if (denom == 0.0) - goto failure; - inode_ratio_mkfs = (groups_max * size_mkfs) / denom; - - /* Make sure we never calculate anything totally huge. */ - if (inode_ratio_mkfs > blocks_mkfs) - goto failure; - /* Make sure we never calculate anything totally tiny. */ - if (inode_ratio_mkfs < kInodeRatioMinimum) - goto failure; - - return (uint64_t)inode_ratio_mkfs; - -failure: - return kInodeRatioDefault; -} - -/* Creates an ext4 filesystem. - * device: path to block device to create filesystem on. - * block_bytes: bytes per block to use for filesystem. - * blocks_min: starting number of blocks on filesystem. - * blocks_max: largest expected size in blocks of filesystem, for growth hints. - * - * Returns 1 on success, 0 on failure. - */ -int filesystem_build(const char *device, uint64_t block_bytes, - uint64_t blocks_min, uint64_t blocks_max) -{ - int rc = 0; - uint64_t inode_ratio; - - gchar *blocksize = g_strdup_printf("%" PRIu64, block_bytes); - if (!blocksize) { - PERROR("g_strdup_printf"); - goto out; - } - - gchar *blocks_str; - blocks_str = g_strdup_printf("%" PRIu64, blocks_min); - if (!blocks_str) { - PERROR("g_strdup_printf"); - goto free_blocksize; - } - - gchar *extended; - if (blocks_min < blocks_max) { - extended = g_strdup_printf("%s,resize=%" PRIu64, - kExt4ExtendedOptions, blocks_max); - } else { - extended = g_strdup_printf("%s", kExt4ExtendedOptions); - } - if (!extended) { - PERROR("g_strdup_printf"); - goto free_blocks_str; - } - - inode_ratio = get_inode_ratio(block_bytes, blocks_min, blocks_max); - gchar *inode_ratio_str = g_strdup_printf("%" PRIu64, inode_ratio); - if (!inode_ratio_str) { - PERROR("g_strdup_printf"); - goto free_extended; - } - - const gchar *mkfs[] = { - "/sbin/mkfs.ext4", - "-T", "default", - "-b", blocksize, - "-m", "0", - "-O", "^huge_file,^flex_bg", - "-i", inode_ratio_str, - "-E", extended, - device, - blocks_str, - NULL - }; - - rc = (runcmd(mkfs, NULL) == 0); - if (!rc) - goto free_inode_ratio_str; - - const gchar *tune2fs[] = { - "/sbin/tune2fs", - "-c", "0", - "-i", "0", - device, - NULL - }; - rc = (runcmd(tune2fs, NULL) == 0); - -free_inode_ratio_str: - g_free(inode_ratio_str); -free_extended: - g_free(extended); -free_blocks_str: - g_free(blocks_str); -free_blocksize: - g_free(blocksize); -out: - return rc; -} - -/* Spawns a filesystem resizing process. */ -int filesystem_resize(const char *device, uint64_t blocks, uint64_t blocks_max) -{ - /* Ignore resizing if we know the filesystem was built to max size. */ - if (blocks >= blocks_max) { - INFO("Resizing aborted. blocks:%" PRIu64 " >= blocks_max:%" PRIu64, - blocks, blocks_max); - return 1; - } - - /* TODO(keescook): Read superblock to find out the current size of - * the filesystem (since statvfs does not report the correct value). - * For now, instead of doing multi-step resizing, just resize to the - * full size of the block device in one step. - */ - blocks = blocks_max; - - INFO("Resizing started in %d second steps.", kResizeStepSeconds); - - do { - gchar *blocks_str; - - sleep(kResizeStepSeconds); - - blocks += kResizeBlocks; - if (blocks > blocks_max) - blocks = blocks_max; - - blocks_str = g_strdup_printf("%" PRIu64, blocks); - if (!blocks_str) { - PERROR("g_strdup_printf"); - return 0; - } - - const gchar *resize[] = { - "/sbin/resize2fs", - "-f", - device, - blocks_str, - NULL - }; - - INFO("Resizing filesystem on %s to %" PRIu64 ".", device, blocks); - if (runcmd(resize, NULL)) { - ERROR("resize2fs failed"); - return 0; - } - g_free(blocks_str); - } while (blocks < blocks_max); - - INFO("Resizing finished."); - return 1; -} - -char *keyfile_read(const char *keyfile, uint8_t *system_key) -{ - char *key = NULL; - unsigned char *cipher = NULL; - gsize length; - uint8_t *plain = NULL; - int plain_length, final_len; - GError *error = NULL; - EVP_CIPHER_CTX ctx; - const EVP_CIPHER *algo = EVP_aes_256_cbc(); - - DEBUG("Reading keyfile %s", keyfile); - if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) { - ERROR("cipher key size mismatch (got %d, want %d)", - EVP_CIPHER_key_length(algo), DIGEST_LENGTH); - goto out; - } - - if (access(keyfile, R_OK)) { - /* This file being missing is handled in caller, so - * do not emit error message. - */ - INFO("%s does not exist.", keyfile); - goto out; - } - - if (!g_file_get_contents(keyfile, (gchar **)&cipher, &length, - &error)) { - ERROR("Unable to read %s: %s", keyfile, error->message); - g_error_free(error); - goto out; - } - plain = malloc(length + EVP_CIPHER_block_size(algo)); - if (!plain) { - PERROR("malloc"); - goto free_cipher; - } - - DEBUG("Decrypting keyfile %s", keyfile); - /* Use the default IV. */ - if (!EVP_DecryptInit(&ctx, algo, system_key, NULL)) { - SSL_ERROR("EVP_DecryptInit"); - goto free_plain; - } - if (!EVP_DecryptUpdate(&ctx, plain, &plain_length, cipher, length)) { - SSL_ERROR("EVP_DecryptUpdate"); - goto free_ctx; - } - if (!EVP_DecryptFinal(&ctx, plain+plain_length, &final_len)) { - SSL_ERROR("EVP_DecryptFinal"); - goto free_ctx; - } - plain_length += final_len; - - if (plain_length != DIGEST_LENGTH) { - ERROR("Decrypted encryption key length (%d) is not %d.", - plain_length, DIGEST_LENGTH); - goto free_ctx; - } - - debug_dump_hex("encryption key", plain, DIGEST_LENGTH); - - key = stringify_hex(plain, DIGEST_LENGTH); - -free_ctx: - EVP_CIPHER_CTX_cleanup(&ctx); -free_plain: - free(plain); -free_cipher: - g_free(cipher); -out: - DEBUG("key:%p", key); - return key; -} - -int keyfile_write(const char *keyfile, uint8_t *system_key, char *string) -{ - int rc = 0; - size_t length; - uint8_t plain[DIGEST_LENGTH]; - uint8_t *cipher = NULL; - int cipher_length, final_len; - GError *error = NULL; - EVP_CIPHER_CTX ctx; - const EVP_CIPHER *algo = EVP_aes_256_cbc(); - mode_t mask; - - DEBUG("Staring to process keyfile %s", keyfile); - /* Have key file be read/write only by root user. */ - mask = umask(0077); - - if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) { - ERROR("cipher key size mismatch (got %d, want %d)", - EVP_CIPHER_key_length(algo), DIGEST_LENGTH); - goto out; - } - - if (access(keyfile, R_OK) == 0) { - ERROR("%s already exists.", keyfile); - goto out; - } - - length = strlen(string); - if (length != sizeof(plain) * 2) { - ERROR("Encryption key string length (%zu) is not %zu.", - length, sizeof(plain) * 2); - goto out; - } - - length = sizeof(plain); - if (!hexify_string(string, plain, length)) { - ERROR("Failed to convert encryption key to byte array"); - goto out; - } - - debug_dump_hex("encryption key", plain, DIGEST_LENGTH); - - cipher = malloc(length + EVP_CIPHER_block_size(algo)); - if (!cipher) { - PERROR("malloc"); - goto out; - } - - DEBUG("Encrypting keyfile %s", keyfile); - /* Use the default IV. */ - if (!EVP_EncryptInit(&ctx, algo, system_key, NULL)) { - SSL_ERROR("EVP_EncryptInit"); - goto free_cipher; - } - if (!EVP_EncryptUpdate(&ctx, cipher, &cipher_length, - (unsigned char *)plain, length)) { - SSL_ERROR("EVP_EncryptUpdate"); - goto free_ctx; - } - if (!EVP_EncryptFinal(&ctx, cipher+cipher_length, &final_len)) { - SSL_ERROR("EVP_EncryptFinal"); - goto free_ctx; - } - length = cipher_length + final_len; - - DEBUG("Writing %zu bytes to %s", length, keyfile); - /* TODO(keescook): use fd here, and set secure delete. Unsupported - * by ext4 currently. :( - * int f; - * ioctl(fd, EXT2_IOC_GETFLAGS, &f); - * f |= EXT2_SECRM_FL; - * ioctl(fd, EXT2_IOC_SETFLAGS, &f); - */ - if (!g_file_set_contents(keyfile, (gchar *)cipher, length, &error)) { - ERROR("Unable to write %s: %s", keyfile, error->message); - g_error_free(error); - goto free_ctx; - } - - rc = 1; - -free_ctx: - EVP_CIPHER_CTX_cleanup(&ctx); -free_cipher: - free(cipher); -out: - umask(mask); - DEBUG("keyfile write rc:%d", rc); - return rc; -} |