blob: 9a472bb919e52985d6043d07b89e2a2334d5ae6d [file] [log] [blame]
Kees Cook498977a2012-02-27 12:43:15 -08001/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 *
5 * This is a collection of helper utilities for use with the "mount-encrypted"
6 * utility.
7 *
8 */
9#define _GNU_SOURCE
10#define _FILE_OFFSET_BITS 64
11#include <stdint.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <unistd.h>
15#include <string.h>
16#include <errno.h>
17#include <fcntl.h>
18#include <sys/ioctl.h>
19#include <sys/stat.h>
20#include <sys/statvfs.h>
21#include <sys/time.h>
22#include <sys/types.h>
23#include <sys/mount.h>
24#include <linux/fs.h>
25#include <linux/loop.h>
26
27#include <glib.h>
28#include <glib/gstdio.h>
29
30#include <openssl/evp.h>
31
32#include "mount-encrypted.h"
33#include "mount-helpers.h"
34
35static const gchar * const kRootDir = "/";
36static const gchar * const kLoopTemplate = "/dev/loop%d";
37static const int kLoopMajor = 7;
38static const int kLoopMax = 8;
39static const unsigned int kResizeStepSeconds = 2;
40static const size_t kResizeBlocks = 32768 * 10;
41static const gchar * const kExt4ExtendedOptions = "discard,lazy_itable_init";
42
43int remove_tree(const char *tree)
44{
45 const gchar *rm[] = {
46 "/bin/rm", "-rf", tree,
47 NULL
48 };
49
50 return runcmd(rm, NULL);
51}
52
53size_t get_sectors(const char *device)
54{
55 size_t sectors;
56 int fd;
57 if ((fd = open(device, O_RDONLY | O_NOFOLLOW)) < 0) {
58 PERROR("open(%s)", device);
59 return 0;
60 }
61 if (ioctl(fd, BLKGETSIZE, &sectors)) {
62 PERROR("ioctl(%s, BLKGETSIZE)", device);
63 return 0;
64 }
65 close(fd);
66 return sectors;
67}
68
69int runcmd(const gchar *argv[], gchar **output)
70{
71 gint rc;
72 gchar *out = NULL, *errout = NULL;
73 GError *err = NULL;
74
75 g_spawn_sync(kRootDir, (gchar **)argv, NULL, 0, NULL, NULL,
76 &out, &errout, &rc, &err);
77 if (err) {
78 ERROR("%s: %s", argv[0], err->message);
79 g_error_free(err);
80 return -1;
81 }
82
83 if (rc)
84 ERROR("%s failed (%d)\n%s\n%s", argv[0], rc, out, errout);
85
86 if (output)
87 *output = out;
88 else
89 g_free(out);
90 g_free(errout);
91
92 return rc;
93}
94
95int same_vfs(const char *mnt_a, const char *mnt_b)
96{
97 struct statvfs stat_a, stat_b;
98
99 if (statvfs(mnt_a, &stat_a)) {
100 PERROR("statvfs(%s)", mnt_a);
101 exit(1);
102 }
103 if (statvfs(mnt_b, &stat_b)) {
104 PERROR("statvfs(%s)", mnt_b);
105 exit(1);
106 }
107 return (stat_a.f_fsid == stat_b.f_fsid);
108}
109
110/* Returns allocated string that holds [length]*2 + 1 characters. */
111char *stringify_hex(uint8_t *binary, size_t length)
112{
113 char *string;
114 size_t i;
115
116 string = malloc(length * 2 + 1);
117 if (!string) {
118 PERROR("malloc");
119 return NULL;
120 }
121 for (i = 0; i < length; ++i)
122 sprintf(string + (i * 2), "%02x", binary[i]);
123 string[length * 2] = '\0';
124
125 return string;
126}
127
128/* Returns allocated byte array that holds strlen([string])/2 bytes. */
129uint8_t *hexify_string(char *string, uint8_t *binary, size_t length)
130{
131 size_t bytes, i;
132
133 bytes = strlen(string) / 2;
134 if (bytes > length) {
135 ERROR("Hex string too long (%zu) for byte array (%zu)",
136 bytes, length);
137 return NULL;
138 }
139
140 for (i = 0; i < bytes; ++i) {
141 if (sscanf(&string[i * 2], "%2hhx", &binary[i]) != 1) {
142 ERROR("Invalid hex code at byte %zu.", i);
143 return NULL;
144 }
145 }
146
147 return binary;
148}
149
150static int is_loop_device(int fd)
151{
152 struct stat info;
153
154 return (fstat(fd, &info) == 0 && S_ISBLK(info.st_mode) &&
155 major(info.st_rdev) == kLoopMajor);
156}
157
Kees Cooke97760c2012-05-04 16:52:02 -0700158static int loop_is_attached(int fd, struct loop_info64 *info)
Kees Cook498977a2012-02-27 12:43:15 -0800159{
Kees Cooke97760c2012-05-04 16:52:02 -0700160 struct loop_info64 local_info;
Kees Cook498977a2012-02-27 12:43:15 -0800161
Kees Cooke97760c2012-05-04 16:52:02 -0700162 return ioctl(fd, LOOP_GET_STATUS64, info ? info : &local_info) == 0;
Kees Cook498977a2012-02-27 12:43:15 -0800163}
164
Kees Cooke97760c2012-05-04 16:52:02 -0700165/* Returns either the matching loopback name, or next available, if NULL. */
166static int loop_locate(gchar **loopback, const char *name)
Kees Cook498977a2012-02-27 12:43:15 -0800167{
Kees Cooke97760c2012-05-04 16:52:02 -0700168 int i, fd, namelen = 0;
169
170 if (name) {
171 namelen = strlen(name);
Kees Cook655cc112012-05-23 16:35:56 -0700172 if (namelen >= LO_NAME_SIZE) {
173 ERROR("'%s' too long (>= %d)", name, LO_NAME_SIZE);
Kees Cooke97760c2012-05-04 16:52:02 -0700174 return -1;
Kees Cook655cc112012-05-23 16:35:56 -0700175 }
Kees Cooke97760c2012-05-04 16:52:02 -0700176 }
Kees Cook498977a2012-02-27 12:43:15 -0800177
178 *loopback = NULL;
179 for (i = 0; i < kLoopMax; ++i) {
Kees Cooke97760c2012-05-04 16:52:02 -0700180 struct loop_info64 info;
181 int attached;
182
Kees Cook498977a2012-02-27 12:43:15 -0800183 g_free(*loopback);
184 *loopback = g_strdup_printf(kLoopTemplate, i);
185 if (!*loopback) {
186 PERROR("g_strdup_printf");
187 return -1;
188 }
189
190 fd = open(*loopback, O_RDONLY | O_NOFOLLOW);
191 if (fd < 0) {
192 PERROR("open(%s)", *loopback);
193 goto failed;
194 }
Kees Cooke97760c2012-05-04 16:52:02 -0700195 if (!is_loop_device(fd)) {
Kees Cook498977a2012-02-27 12:43:15 -0800196 close(fd);
Kees Cooke97760c2012-05-04 16:52:02 -0700197 continue;
198 }
199
200 memset(&info, 0, sizeof(info));
201 attached = loop_is_attached(fd, &info);
202 close(fd);
203
Kees Cook655cc112012-05-23 16:35:56 -0700204 if (attached)
205 DEBUG("Saw %s on %s", info.lo_file_name, *loopback);
206
Kees Cooke97760c2012-05-04 16:52:02 -0700207 if ((attached && name &&
208 strncmp((char *)info.lo_file_name, name, namelen) == 0) ||
209 (!attached && !name)) {
Kees Cook655cc112012-05-23 16:35:56 -0700210 DEBUG("Using %s", *loopback);
Kees Cooke97760c2012-05-04 16:52:02 -0700211 /* Reopen for working on it. */
Kees Cook498977a2012-02-27 12:43:15 -0800212 fd = open(*loopback, O_RDWR | O_NOFOLLOW);
Kees Cooke97760c2012-05-04 16:52:02 -0700213 if (is_loop_device(fd) &&
214 loop_is_attached(fd, NULL) == attached)
Kees Cook498977a2012-02-27 12:43:15 -0800215 return fd;
216 }
Kees Cook498977a2012-02-27 12:43:15 -0800217 }
218 ERROR("Ran out of loopback devices");
219
220failed:
221 g_free(*loopback);
222 *loopback = NULL;
223 return -1;
224}
225
Kees Cooke97760c2012-05-04 16:52:02 -0700226static int loop_detach_fd(int fd)
227{
228 if (ioctl(fd, LOOP_CLR_FD, 0)) {
229 PERROR("LOOP_CLR_FD");
230 return 0;
231 }
232 return 1;
233}
234
Kees Cook498977a2012-02-27 12:43:15 -0800235int loop_detach(const gchar *loopback)
236{
Kees Cooke97760c2012-05-04 16:52:02 -0700237 int fd, rc = 1;
Kees Cook498977a2012-02-27 12:43:15 -0800238
239 fd = open(loopback, O_RDONLY | O_NOFOLLOW);
240 if (fd < 0) {
241 PERROR("open(%s)", loopback);
242 return 0;
243 }
Kees Cooke97760c2012-05-04 16:52:02 -0700244 if (!is_loop_device(fd) || !loop_is_attached(fd, NULL) ||
245 !loop_detach_fd(fd))
246 rc = 0;
Kees Cook498977a2012-02-27 12:43:15 -0800247
248 close (fd);
Kees Cooke97760c2012-05-04 16:52:02 -0700249 return rc;
Kees Cook498977a2012-02-27 12:43:15 -0800250}
251
Kees Cooke97760c2012-05-04 16:52:02 -0700252int loop_detach_name(const char *name)
253{
254 gchar *loopback = NULL;
255 int loopfd, rc;
256
257 loopfd = loop_locate(&loopback, name);
258 if (loopfd < 0)
259 return 0;
260 rc = loop_detach_fd(loopfd);
261
262 close(loopfd);
263 g_free(loopback);
264 return rc;
265}
266
267/* Closes fd, returns name of loopback device pathname. */
Kees Cook498977a2012-02-27 12:43:15 -0800268gchar *loop_attach(int fd, const char *name)
269{
270 gchar *loopback = NULL;
271 int loopfd;
272 struct loop_info64 info;
273
Kees Cooke97760c2012-05-04 16:52:02 -0700274 loopfd = loop_locate(&loopback, NULL);
Kees Cook498977a2012-02-27 12:43:15 -0800275 if (loopfd < 0)
276 return NULL;
277 if (ioctl(loopfd, LOOP_SET_FD, fd) < 0) {
278 PERROR("LOOP_SET_FD");
279 goto failed;
280 }
281
282 memset(&info, 0, sizeof(info));
283 strncpy((char*)info.lo_file_name, name, LO_NAME_SIZE);
284 if (ioctl(loopfd, LOOP_SET_STATUS64, &info)) {
285 PERROR("LOOP_SET_STATUS64");
286 goto failed;
287 }
288
289 close(loopfd);
290 close(fd);
291 return loopback;
292failed:
293 close(loopfd);
294 close(fd);
295 g_free(loopback);
296 return 0;
297}
298
299int dm_setup(size_t sectors, const gchar *encryption_key, const char *name,
300 const gchar *device, const char *path)
301{
302 /* Mount loopback device with dm-crypt using the encryption key. */
303 gchar *table = g_strdup_printf("0 %zu crypt " \
304 "aes-cbc-essiv:sha256 %s " \
305 "0 %s 0 " \
306 "1 allow_discards",
307 sectors,
308 encryption_key,
309 device);
310 if (!table) {
311 PERROR("g_strdup_printf");
312 return 0;
313 }
314
315 const gchar *argv[] = {
316 "/sbin/dmsetup",
317 "create", name,
318 "--noudevrules", "--noudevsync",
319 "--table", table,
320 NULL
321 };
322
323 /* TODO(keescook): replace with call to libdevmapper. */
324 if (runcmd(argv, NULL) != 0) {
325 g_free(table);
326 return 0;
327 }
328 g_free(table);
329
330 /* Make sure the dm-crypt device showed up. */
331 if (access(path, R_OK)) {
332 ERROR("%s does not exist", path);
333 return 0;
334 }
335
336 return 1;
337}
338
Kees Cooke97760c2012-05-04 16:52:02 -0700339int dm_teardown(const gchar *device)
Kees Cook498977a2012-02-27 12:43:15 -0800340{
341 const char *argv[] = {
342 "/sbin/dmsetup",
343 "remove", device,
344 "--noudevrules", "--noudevsync",
345 NULL
346 };
347 /* TODO(keescook): replace with call to libdevmapper. */
Kees Cooke97760c2012-05-04 16:52:02 -0700348 if (runcmd(argv, NULL) != 0)
349 return 0;
350 return 1;
Kees Cook498977a2012-02-27 12:43:15 -0800351}
352
353char *dm_get_key(const gchar *device)
354{
355 gchar *output = NULL;
356 char *key;
357 int i;
358 const char *argv[] = {
359 "/sbin/dmsetup",
360 "table", "--showkeys",
361 device,
362 NULL
363 };
364 /* TODO(keescook): replace with call to libdevmapper. */
365 if (runcmd(argv, &output) != 0)
366 return NULL;
367
368 /* Key is 4th field in the output. */
369 for (i = 0, key = strtok(output, " ");
370 i < 4 && key;
371 ++i, key = strtok(NULL, " ")) { }
372
373 /* Create a copy of the key and free the output buffer. */
374 if (key) {
375 key = strdup(key);
376 g_free(output);
377 }
378
379 return key;
380}
381
382int sparse_create(const char *path, size_t size)
383{
384 int sparsefd;
385
386 sparsefd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
387 S_IRUSR | S_IWUSR);
388 if (sparsefd < 0)
389 goto out;
390
391 if (ftruncate(sparsefd, size)) {
392 int saved_errno = errno;
393
394 close(sparsefd);
395 unlink(path);
396 errno = saved_errno;
397
398 sparsefd = -1;
399 }
400
401out:
402 return sparsefd;
403}
404
405int filesystem_build(const char *device, size_t block_bytes, size_t blocks_min,
406 size_t blocks_max)
407{
408 int rc = 0;
409
410 gchar *blocksize = g_strdup_printf("%zu", block_bytes);
411 if (!blocksize) {
412 PERROR("g_strdup_printf");
413 goto out;
414 }
415
416 gchar *blocks_str;
417 blocks_str = g_strdup_printf("%zu", blocks_min);
418 if (!blocks_str) {
419 PERROR("g_strdup_printf");
420 goto free_blocksize;
421 }
422
423 gchar *extended;
424 if (blocks_min < blocks_max) {
425 extended = g_strdup_printf("%s,resize=%zu",
426 kExt4ExtendedOptions, blocks_max);
427 } else {
428 extended = g_strdup_printf("%s", kExt4ExtendedOptions);
429 }
430 if (!extended) {
431 PERROR("g_strdup_printf");
432 goto free_blocks_str;
433 }
434
435 const gchar *mkfs[] = {
436 "/sbin/mkfs.ext4",
437 "-T", "default",
438 "-b", blocksize,
439 "-m", "0",
440 "-O", "^huge_file,^flex_bg",
441 "-E", extended,
442 device,
443 blocks_str,
444 NULL
445 };
446
447 rc = (runcmd(mkfs, NULL) == 0);
448 if (!rc)
449 goto free_extended;
450
451 const gchar *tune2fs[] = {
452 "/sbin/tune2fs",
453 "-c", "0",
454 "-i", "0",
455 device,
456 NULL
457 };
458 rc = (runcmd(tune2fs, NULL) == 0);
459
460free_extended:
461 g_free(extended);
462free_blocks_str:
463 g_free(blocks_str);
464free_blocksize:
465 g_free(blocksize);
466out:
467 return rc;
468}
469
470/* Spawns a filesystem resizing process. */
Kees Cookf9e82e92012-04-03 11:57:04 -0700471int filesystem_resize(const char *device, size_t blocks, size_t blocks_max)
Kees Cook498977a2012-02-27 12:43:15 -0800472{
Kees Cook498977a2012-02-27 12:43:15 -0800473 /* Ignore resizing if we know the filesystem was built to max size. */
Kees Cook655cc112012-05-23 16:35:56 -0700474 if (blocks >= blocks_max) {
475 INFO("Resizing aborted. blocks:%zu >= blocks_max:%zu",
476 blocks, blocks_max);
Kees Cookf9e82e92012-04-03 11:57:04 -0700477 return 1;
Kees Cook655cc112012-05-23 16:35:56 -0700478 }
Kees Cook498977a2012-02-27 12:43:15 -0800479
480 /* TODO(keescook): Read superblock to find out the current size of
481 * the filesystem (since statvfs does not report the correct value).
482 * For now, instead of doing multi-step resizing, just resize to the
483 * full size of the block device in one step.
484 */
485 blocks = blocks_max;
486
Kees Cook655cc112012-05-23 16:35:56 -0700487 INFO("Resizing started in %d second steps.", kResizeStepSeconds);
Kees Cook498977a2012-02-27 12:43:15 -0800488
489 do {
490 gchar *blocks_str;
491
492 sleep(kResizeStepSeconds);
493
494 blocks += kResizeBlocks;
495 if (blocks > blocks_max)
496 blocks = blocks_max;
497
498 blocks_str = g_strdup_printf("%zu", blocks);
499 if (!blocks_str) {
500 PERROR("g_strdup_printf");
Kees Cookf9e82e92012-04-03 11:57:04 -0700501 return 0;
Kees Cook498977a2012-02-27 12:43:15 -0800502 }
503
504 const gchar *resize[] = {
505 "/sbin/resize2fs",
506 "-f",
507 device,
508 blocks_str,
509 NULL
510 };
511
512 INFO("Resizing filesystem on %s to %zu.", device, blocks);
513 if (runcmd(resize, NULL)) {
514 ERROR("resize2fs failed");
Kees Cookf9e82e92012-04-03 11:57:04 -0700515 return 0;
Kees Cook498977a2012-02-27 12:43:15 -0800516 }
517 g_free(blocks_str);
518 } while (blocks < blocks_max);
519
520 INFO("Resizing finished.");
Kees Cookf9e82e92012-04-03 11:57:04 -0700521 return 1;
Kees Cook498977a2012-02-27 12:43:15 -0800522}
523
524char *keyfile_read(const char *keyfile, uint8_t *system_key)
525{
526 char *key = NULL;
527 unsigned char *cipher = NULL;
528 gsize length;
529 uint8_t *plain = NULL;
530 int plain_length, final_len;
531 GError *error = NULL;
532 EVP_CIPHER_CTX ctx;
533 const EVP_CIPHER *algo = EVP_aes_256_cbc();
534
535 DEBUG("Reading keyfile %s", keyfile);
536 if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
537 ERROR("cipher key size mismatch (got %d, want %d)",
538 EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
539 goto out;
540 }
541
542 if (access(keyfile, R_OK)) {
543 /* This file being missing is handled in caller, so
544 * do not emit error message.
545 */
546 INFO("%s does not exist.", keyfile);
547 goto out;
548 }
549
550 if (!g_file_get_contents(keyfile, (gchar **)&cipher, &length,
551 &error)) {
552 ERROR("Unable to read %s: %s", keyfile, error->message);
553 g_error_free(error);
554 goto out;
555 }
556 plain = malloc(length);
557 if (!plain) {
558 PERROR("malloc");
559 goto free_cipher;
560 }
561
562 DEBUG("Decrypting keyfile %s", keyfile);
563 /* Use the default IV. */
564 if (!EVP_DecryptInit(&ctx, algo, system_key, NULL)) {
565 SSL_ERROR("EVP_DecryptInit");
566 goto free_plain;
567 }
568 /* TODO(keescook): this is a heap overflow -- file size not checked. */
569 if (!EVP_DecryptUpdate(&ctx, plain, &plain_length, cipher, length)) {
570 SSL_ERROR("EVP_DecryptUpdate");
571 goto free_ctx;
572 }
573 if (!EVP_DecryptFinal(&ctx, plain+plain_length, &final_len)) {
574 SSL_ERROR("EVP_DecryptFinal");
575 goto free_ctx;
576 }
577 plain_length += final_len;
578
579 if (plain_length != DIGEST_LENGTH) {
580 ERROR("Decrypted encryption key length (%d) is not %d.",
581 plain_length, DIGEST_LENGTH);
582 goto free_ctx;
583 }
584
585 debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
586
587 key = stringify_hex(plain, DIGEST_LENGTH);
588
589free_ctx:
590 EVP_CIPHER_CTX_cleanup(&ctx);
591free_plain:
592 free(plain);
593free_cipher:
594 g_free(cipher);
595out:
596 DEBUG("key:%p", key);
597 return key;
598}
599
600int keyfile_write(const char *keyfile, uint8_t *system_key, char *string)
601{
602 int rc = 0;
603 size_t length;
604 uint8_t plain[DIGEST_LENGTH];
605 uint8_t *cipher = NULL;
606 int cipher_length, final_len;
607 GError *error = NULL;
608 EVP_CIPHER_CTX ctx;
609 const EVP_CIPHER *algo = EVP_aes_256_cbc();
610
611 DEBUG("Staring to process keyfile %s", keyfile);
612 if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
613 ERROR("cipher key size mismatch (got %d, want %d)",
614 EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
615 goto out;
616 }
617
618 if (access(keyfile, R_OK) == 0) {
619 ERROR("%s already exists.", keyfile);
620 goto out;
621 }
622
623 length = strlen(string);
624 if (length != sizeof(plain) * 2) {
625 ERROR("Encryption key string length (%zu) is not %zu.",
626 length, sizeof(plain) * 2);
627 goto out;
628 }
629
630 length = sizeof(plain);
631 if (!hexify_string(string, plain, length)) {
632 ERROR("Failed to convert encryption key to byte array");
633 goto out;
634 }
635
636 debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
637
638 cipher = malloc(length + EVP_CIPHER_block_size(algo));
639 if (!cipher) {
640 PERROR("malloc");
641 goto out;
642 }
643
644 DEBUG("Encrypting keyfile %s", keyfile);
645 /* Use the default IV. */
646 if (!EVP_EncryptInit(&ctx, algo, system_key, NULL)) {
647 SSL_ERROR("EVP_EncryptInit");
648 goto free_cipher;
649 }
650 if (!EVP_EncryptUpdate(&ctx, cipher, &cipher_length,
651 (unsigned char *)plain, length)) {
652 SSL_ERROR("EVP_EncryptUpdate");
653 goto free_ctx;
654 }
655 if (!EVP_EncryptFinal(&ctx, cipher+cipher_length, &final_len)) {
656 SSL_ERROR("EVP_EncryptFinal");
657 goto free_ctx;
658 }
659 length = cipher_length + final_len;
660
Kees Cook6a312262012-06-15 13:55:24 -0700661 DEBUG("Writing %zu bytes to %s", length, keyfile);
Kees Cookbeb3be12012-04-05 10:15:47 -0700662 /* TODO(keescook): replace this with a mode-400 writer. */
Kees Cook498977a2012-02-27 12:43:15 -0800663 if (!g_file_set_contents(keyfile, (gchar *)cipher, length, &error)) {
664 ERROR("Unable to write %s: %s", keyfile, error->message);
665 g_error_free(error);
666 goto free_ctx;
667 }
668
669 rc = 1;
670
671free_ctx:
672 EVP_CIPHER_CTX_cleanup(&ctx);
673free_cipher:
674 free(cipher);
675out:
Kees Cookbeb3be12012-04-05 10:15:47 -0700676 DEBUG("keyfile write rc:%d", rc);
Kees Cook498977a2012-02-27 12:43:15 -0800677 return rc;
678}