summaryrefslogtreecommitdiff
path: root/utility/mount-helpers.c
diff options
context:
space:
mode:
Diffstat (limited to 'utility/mount-helpers.c')
-rw-r--r--utility/mount-helpers.c803
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;
-}