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.c650
1 files changed, 650 insertions, 0 deletions
diff --git a/utility/mount-helpers.c b/utility/mount-helpers.c
new file mode 100644
index 00000000..b6f31c30
--- /dev/null
+++ b/utility/mount-helpers.c
@@ -0,0 +1,650 @@
+/* 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 <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 <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 size_t kResizeBlocks = 32768 * 10;
+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);
+}
+
+size_t get_sectors(const char *device)
+{
+ size_t sectors;
+ int fd;
+ if ((fd = open(device, O_RDONLY | O_NOFOLLOW)) < 0) {
+ PERROR("open(%s)", device);
+ return 0;
+ }
+ if (ioctl(fd, BLKGETSIZE, &sectors)) {
+ PERROR("ioctl(%s, BLKGETSIZE)", device);
+ return 0;
+ }
+ close(fd);
+ return sectors;
+}
+
+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 statvfs stat_a, stat_b;
+
+ if (statvfs(mnt_a, &stat_a)) {
+ PERROR("statvfs(%s)", mnt_a);
+ exit(1);
+ }
+ if (statvfs(mnt_b, &stat_b)) {
+ PERROR("statvfs(%s)", mnt_b);
+ exit(1);
+ }
+ return (stat_a.f_fsid == stat_b.f_fsid);
+}
+
+/* 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;
+}
+
+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_info info;
+
+ errno = 0;
+ if (ioctl(fd, LOOP_GET_STATUS, &info) && errno == ENXIO)
+ return 0;
+
+ return 1;
+}
+
+static int loop_allocate(gchar **loopback)
+{
+ int i, fd;
+
+ *loopback = NULL;
+ for (i = 0; i < kLoopMax; ++i) {
+ 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) && !loop_is_attached(fd)) {
+ close(fd);
+ fd = open(*loopback, O_RDWR | O_NOFOLLOW);
+ if (is_loop_device(fd) && !loop_is_attached(fd))
+ return fd;
+ }
+ close(fd);
+ }
+ ERROR("Ran out of loopback devices");
+
+failed:
+ g_free(*loopback);
+ *loopback = NULL;
+ return -1;
+}
+
+int loop_detach(const gchar *loopback)
+{
+ int fd;
+
+ 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))
+ goto failed;
+ if (ioctl(fd, LOOP_CLR_FD, 0)) {
+ PERROR("LOOP_CLR_FD");
+ goto failed;
+ }
+
+ close (fd);
+ return 1;
+
+failed:
+ close(fd);
+ return 0;
+}
+
+gchar *loop_attach(int fd, const char *name)
+{
+ gchar *loopback = NULL;
+ int loopfd;
+ struct loop_info64 info;
+
+ loopfd = loop_allocate(&loopback);
+ 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(size_t sectors, const gchar *encryption_key, const char *name,
+ const gchar *device, const char *path)
+{
+ /* Mount loopback device with dm-crypt using the encryption key. */
+ gchar *table = g_strdup_printf("0 %zu crypt " \
+ "aes-cbc-essiv:sha256 %s " \
+ "0 %s 0 " \
+ "1 allow_discards",
+ sectors,
+ encryption_key,
+ device);
+ 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;
+}
+
+void dm_teardown(const gchar *device)
+{
+ const char *argv[] = {
+ "/sbin/dmsetup",
+ "remove", device,
+ "--noudevrules", "--noudevsync",
+ NULL
+ };
+ /* TODO(keescook): replace with call to libdevmapper. */
+ runcmd(argv, NULL);
+}
+
+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, size_t size)
+{
+ 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, size)) {
+ int saved_errno = errno;
+
+ close(sparsefd);
+ unlink(path);
+ errno = saved_errno;
+
+ sparsefd = -1;
+ }
+
+out:
+ return sparsefd;
+}
+
+int filesystem_build(const char *device, size_t block_bytes, size_t blocks_min,
+ size_t blocks_max)
+{
+ int rc = 0;
+
+ gchar *blocksize = g_strdup_printf("%zu", block_bytes);
+ if (!blocksize) {
+ PERROR("g_strdup_printf");
+ goto out;
+ }
+
+ gchar *blocks_str;
+ blocks_str = g_strdup_printf("%zu", 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=%zu",
+ kExt4ExtendedOptions, blocks_max);
+ } else {
+ extended = g_strdup_printf("%s", kExt4ExtendedOptions);
+ }
+ if (!extended) {
+ PERROR("g_strdup_printf");
+ goto free_blocks_str;
+ }
+
+ const gchar *mkfs[] = {
+ "/sbin/mkfs.ext4",
+ "-T", "default",
+ "-b", blocksize,
+ "-m", "0",
+ "-O", "^huge_file,^flex_bg",
+ "-E", extended,
+ device,
+ blocks_str,
+ NULL
+ };
+
+ rc = (runcmd(mkfs, NULL) == 0);
+ if (!rc)
+ goto free_extended;
+
+ const gchar *tune2fs[] = {
+ "/sbin/tune2fs",
+ "-c", "0",
+ "-i", "0",
+ device,
+ NULL
+ };
+ rc = (runcmd(tune2fs, NULL) == 0);
+
+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. */
+void filesystem_resizer(const char *device, size_t blocks, size_t blocks_max)
+{
+ pid_t pid;
+
+ /* Ignore resizing if we know the filesystem was built to max size. */
+ if (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;
+ }
+
+ if (setsid() < 0) {
+ PERROR("setsid");
+ goto out;
+ }
+
+ /* 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_INIT("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("%zu", blocks);
+ if (!blocks_str) {
+ PERROR("g_strdup_printf");
+ goto out;
+ }
+
+ const gchar *resize[] = {
+ "/sbin/resize2fs",
+ "-f",
+ device,
+ blocks_str,
+ NULL
+ };
+
+ INFO("Resizing filesystem on %s to %zu.", device, blocks);
+ if (runcmd(resize, NULL)) {
+ ERROR("resize2fs failed");
+ goto out;
+ }
+ g_free(blocks_str);
+ } while (blocks < blocks_max);
+
+ INFO("Resizing finished.");
+out:
+ exit(0);
+}
+
+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);
+ 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;
+ }
+ /* TODO(keescook): this is a heap overflow -- file size not checked. */
+ 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();
+
+ DEBUG("Staring to process 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) == 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 keyfile %s", keyfile);
+ 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:
+ DEBUG("rc:%d", rc);
+ return rc;
+}