diff options
Diffstat (limited to 'fs_mgr')
-rw-r--r-- | fs_mgr/Android.mk | 7 | ||||
-rw-r--r-- | fs_mgr/fs_mgr.c | 221 | ||||
-rw-r--r-- | fs_mgr/fs_mgr_fstab.c | 26 | ||||
-rw-r--r-- | fs_mgr/fs_mgr_priv.h | 5 | ||||
-rw-r--r-- | fs_mgr/fs_mgr_verity.c | 657 | ||||
-rw-r--r-- | fs_mgr/include/fs_mgr.h | 30 |
6 files changed, 857 insertions, 89 deletions
diff --git a/fs_mgr/Android.mk b/fs_mgr/Android.mk index 0ec6c4b..8ed5cc9 100644 --- a/fs_mgr/Android.mk +++ b/fs_mgr/Android.mk @@ -12,8 +12,8 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/include \ external/openssl/include LOCAL_MODULE:= libfs_mgr -LOCAL_STATIC_LIBRARIES := liblogwrap libmincrypt libext4_utils_static -LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_STATIC_LIBRARIES := liblogwrap libmincrypt libext4_utils_static libsquashfs_utils +LOCAL_C_INCLUDES += system/extras/ext4_utils system/extras/squashfs_utils LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include LOCAL_CFLAGS := -Werror @@ -38,8 +38,9 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/sbin LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) -LOCAL_STATIC_LIBRARIES := libfs_mgr liblogwrap libcutils liblog libc libmincrypt libext4_utils_static +LOCAL_STATIC_LIBRARIES := libfs_mgr liblogwrap libcutils liblog libc libmincrypt libext4_utils_static libsquashfs_utils LOCAL_STATIC_LIBRARIES += libsparse_static libz libselinux +LOCAL_CXX_STL := libc++_static LOCAL_CFLAGS := -Werror diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c index 8533ff6..5f639b7 100644 --- a/fs_mgr/fs_mgr.c +++ b/fs_mgr/fs_mgr.c @@ -28,6 +28,10 @@ #include <libgen.h> #include <time.h> #include <sys/swap.h> +#include <dirent.h> +#include <ext4.h> +#include <ext4_sb.h> +#include <ext4_crypt.h> #include <linux/loop.h> #include <private/android_filesystem_config.h> @@ -116,10 +120,23 @@ static void check_fs(char *blk_device, char *fs_type, char *target) * filesytsem due to an error, e2fsck is still run to do a full check * fix the filesystem. */ + errno = 0; ret = mount(blk_device, target, fs_type, tmpmnt_flags, tmpmnt_opts); - INFO("%s(): mount(%s,%s,%s)=%d\n", __func__, blk_device, target, fs_type, ret); + INFO("%s(): mount(%s,%s,%s)=%d: %s\n", + __func__, blk_device, target, fs_type, ret, strerror(errno)); if (!ret) { - umount(target); + int i; + for (i = 0; i < 5; i++) { + // Try to umount 5 times before continuing on. + // Should we try rebooting if all attempts fail? + int result = umount(target); + if (result == 0) { + INFO("%s(): unmount(%s) succeeded\n", __func__, target); + break; + } + ERROR("%s(): umount(%s)=%d: %s\n", __func__, target, result, strerror(errno)); + sleep(1); + } } /* @@ -176,19 +193,22 @@ static void remove_trailing_slashes(char *n) * Mark the given block device as read-only, using the BLKROSET ioctl. * Return 0 on success, and -1 on error. */ -static void fs_set_blk_ro(const char *blockdev) +int fs_mgr_set_blk_ro(const char *blockdev) { int fd; + int rc = -1; int ON = 1; - fd = open(blockdev, O_RDONLY); + fd = TEMP_FAILURE_RETRY(open(blockdev, O_RDONLY | O_CLOEXEC)); if (fd < 0) { // should never happen - return; + return rc; } - ioctl(fd, BLKROSET, &ON); - close(fd); + rc = ioctl(fd, BLKROSET, &ON); + TEMP_FAILURE_RETRY(close(fd)); + + return rc; } /* @@ -214,7 +234,7 @@ static int __mount(const char *source, const char *target, const struct fstab_re save_errno = errno; INFO("%s(source=%s,target=%s,type=%s)=%d\n", __func__, source, target, rec->fs_type, ret); if ((ret == 0) && (mountflags & MS_RDONLY) != 0) { - fs_set_blk_ro(source); + fs_mgr_set_blk_ro(source); } errno = save_errno; return ret; @@ -338,6 +358,150 @@ static int mount_with_alternatives(struct fstab *fstab, int start_idx, int *end_ return 0; } +static int translate_ext_labels(struct fstab_rec *rec) +{ + DIR *blockdir = NULL; + struct dirent *ent; + char *label; + size_t label_len; + int ret = -1; + + if (strncmp(rec->blk_device, "LABEL=", 6)) + return 0; + + label = rec->blk_device + 6; + label_len = strlen(label); + + if (label_len > 16) { + ERROR("FS label is longer than allowed by filesystem\n"); + goto out; + } + + + blockdir = opendir("/dev/block"); + if (!blockdir) { + ERROR("couldn't open /dev/block\n"); + goto out; + } + + while ((ent = readdir(blockdir))) { + int fd; + char super_buf[1024]; + struct ext4_super_block *sb; + + if (ent->d_type != DT_BLK) + continue; + + fd = openat(dirfd(blockdir), ent->d_name, O_RDONLY); + if (fd < 0) { + ERROR("Cannot open block device /dev/block/%s\n", ent->d_name); + goto out; + } + + if (TEMP_FAILURE_RETRY(lseek(fd, 1024, SEEK_SET)) < 0 || + TEMP_FAILURE_RETRY(read(fd, super_buf, 1024)) != 1024) { + /* Probably a loopback device or something else without a readable + * superblock. + */ + close(fd); + continue; + } + + sb = (struct ext4_super_block *)super_buf; + if (sb->s_magic != EXT4_SUPER_MAGIC) { + INFO("/dev/block/%s not ext{234}\n", ent->d_name); + continue; + } + + if (!strncmp(label, sb->s_volume_name, label_len)) { + char *new_blk_device; + + if (asprintf(&new_blk_device, "/dev/block/%s", ent->d_name) < 0) { + ERROR("Could not allocate block device string\n"); + goto out; + } + + INFO("resolved label %s to %s\n", rec->blk_device, new_blk_device); + + free(rec->blk_device); + rec->blk_device = new_blk_device; + ret = 0; + break; + } + } + +out: + closedir(blockdir); + return ret; +} + +// Check to see if a mountable volume has encryption requirements +static int handle_encryptable(struct fstab *fstab, const struct fstab_rec* rec) +{ + /* If this is block encryptable, need to trigger encryption */ + if ( (rec->fs_mgr_flags & MF_FORCECRYPT) + || (device_is_force_encrypted() && fs_mgr_is_encryptable(rec))) { + if (umount(rec->mount_point) == 0) { + return FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION; + } else { + WARNING("Could not umount %s (%s) - allow continue unencrypted\n", + rec->mount_point, strerror(errno)); + return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED; + } + } + + // Deal with file level encryption + if (rec->fs_mgr_flags & MF_FILEENCRYPTION) { + // Default or not yet initialized encryption requires no more work here + if (!e4crypt_non_default_key(rec->mount_point)) { + INFO("%s is default file encrypted\n", rec->mount_point); + return FS_MGR_MNTALL_DEV_DEFAULT_FILE_ENCRYPTED; + } + + INFO("%s is non-default file encrypted\n", rec->mount_point); + + // Uses non-default key, so must unmount and set up temp file system + if (umount(rec->mount_point)) { + ERROR("Failed to umount %s - rebooting\n", rec->mount_point); + return FS_MGR_MNTALL_FAIL; + } + + if (fs_mgr_do_tmpfs_mount(rec->mount_point) != 0) { + ERROR("Failed to mount a tmpfs at %s\n", rec->mount_point); + return FS_MGR_MNTALL_FAIL; + } + + // Mount data temporarily so we can access unencrypted dir + char tmp_mnt[PATH_MAX]; + strlcpy(tmp_mnt, rec->mount_point, sizeof(tmp_mnt)); + strlcat(tmp_mnt, "/tmp_mnt", sizeof(tmp_mnt)); + if (mkdir(tmp_mnt, 0700)) { + ERROR("Failed to create temp mount point\n"); + return FS_MGR_MNTALL_FAIL; + } + + if (fs_mgr_do_mount(fstab, rec->mount_point, + rec->blk_device, tmp_mnt)) { + ERROR("Error temp mounting encrypted file system\n"); + return FS_MGR_MNTALL_FAIL; + } + + // Link it to the normal place so ext4_crypt functions work normally + strlcat(tmp_mnt, "/unencrypted", sizeof(tmp_mnt)); + char link_path[PATH_MAX]; + strlcpy(link_path, rec->mount_point, sizeof(link_path)); + strlcat(link_path, "/unencrypted", sizeof(link_path)); + if (symlink(tmp_mnt, link_path)) { + ERROR("Error creating symlink to unencrypted directory\n"); + return FS_MGR_MNTALL_FAIL; + } + + return FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED; + } + + return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED; +} + /* When multiple fstab records share the same mount_point, it will * try to mount each one in turn, and ignore any duplicates after a * first successful mount. @@ -369,6 +533,17 @@ int fs_mgr_mount_all(struct fstab *fstab) continue; } + /* Translate LABEL= file system labels into block devices */ + if (!strcmp(fstab->recs[i].fs_type, "ext2") || + !strcmp(fstab->recs[i].fs_type, "ext3") || + !strcmp(fstab->recs[i].fs_type, "ext4")) { + int tret = translate_ext_labels(&fstab->recs[i]); + if (tret < 0) { + ERROR("Could not translate label to block device\n"); + continue; + } + } + if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { wait_for_file(fstab->recs[i].blk_device, WAIT_TIMEOUT); } @@ -391,25 +566,21 @@ int fs_mgr_mount_all(struct fstab *fstab) /* Deal with encryptability. */ if (!mret) { - /* If this is encryptable, need to trigger encryption */ - if ( (fstab->recs[attempted_idx].fs_mgr_flags & MF_FORCECRYPT) - || (device_is_force_encrypted() - && fs_mgr_is_encryptable(&fstab->recs[attempted_idx]))) { - if (umount(fstab->recs[attempted_idx].mount_point) == 0) { - if (encryptable == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) { - ERROR("Will try to encrypt %s %s\n", fstab->recs[attempted_idx].mount_point, - fstab->recs[attempted_idx].fs_type); - encryptable = FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION; - } else { - ERROR("Only one encryptable/encrypted partition supported\n"); - encryptable = FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED; - } - } else { - INFO("Could not umount %s - allow continue unencrypted\n", - fstab->recs[attempted_idx].mount_point); - continue; + int status = handle_encryptable(fstab, &fstab->recs[attempted_idx]); + + if (status == FS_MGR_MNTALL_FAIL) { + /* Fatal error - no point continuing */ + return status; + } + + if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) { + if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) { + // Log and continue + ERROR("Only one encryptable/encrypted partition supported\n"); } + encryptable = status; } + /* Success! Go get the next one */ continue; } diff --git a/fs_mgr/fs_mgr_fstab.c b/fs_mgr/fs_mgr_fstab.c index c2da28a..51f398e 100644 --- a/fs_mgr/fs_mgr_fstab.c +++ b/fs_mgr/fs_mgr_fstab.c @@ -26,6 +26,7 @@ struct fs_mgr_flag_values { char *key_loc; + char *verity_loc; long long part_length; char *label; int partnum; @@ -62,6 +63,7 @@ static struct flag_list fs_mgr_flags[] = { { "check", MF_CHECK }, { "encryptable=",MF_CRYPT }, { "forceencrypt=",MF_FORCECRYPT }, + { "fileencryption",MF_FILEENCRYPTION }, { "nonremovable",MF_NONREMOVABLE }, { "voldmanaged=",MF_VOLDMANAGED}, { "length=", MF_LENGTH }, @@ -123,6 +125,14 @@ static int parse_flags(char *flags, struct flag_list *fl, * location of the keys. Get it and return it. */ flag_vals->key_loc = strdup(strchr(p, '=') + 1); + } else if ((fl[i].flag == MF_VERIFY) && flag_vals) { + /* If the verify flag is followed by an = and the + * location for the verity state, get it and return it. + */ + char *start = strchr(p, '='); + if (start) { + flag_vals->verity_loc = strdup(start + 1); + } } else if ((fl[i].flag == MF_FORCECRYPT) && flag_vals) { /* The forceencrypt flag is followed by an = and the * location of the keys. Get it and return it. @@ -312,6 +322,7 @@ struct fstab *fs_mgr_read_fstab(const char *fstab_path) fstab->recs[cnt].fs_mgr_flags = parse_flags(p, fs_mgr_flags, &flag_vals, NULL, 0); fstab->recs[cnt].key_loc = flag_vals.key_loc; + fstab->recs[cnt].verity_loc = flag_vals.verity_loc; fstab->recs[cnt].length = flag_vals.part_length; fstab->recs[cnt].label = flag_vals.label; fstab->recs[cnt].partnum = flag_vals.partnum; @@ -429,27 +440,32 @@ struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const ch return fs_mgr_get_entry_for_mount_point_after(NULL, fstab, path); } -int fs_mgr_is_voldmanaged(struct fstab_rec *fstab) +int fs_mgr_is_voldmanaged(const struct fstab_rec *fstab) { return fstab->fs_mgr_flags & MF_VOLDMANAGED; } -int fs_mgr_is_nonremovable(struct fstab_rec *fstab) +int fs_mgr_is_nonremovable(const struct fstab_rec *fstab) { return fstab->fs_mgr_flags & MF_NONREMOVABLE; } -int fs_mgr_is_verified(struct fstab_rec *fstab) +int fs_mgr_is_verified(const struct fstab_rec *fstab) { return fstab->fs_mgr_flags & MF_VERIFY; } -int fs_mgr_is_encryptable(struct fstab_rec *fstab) +int fs_mgr_is_encryptable(const struct fstab_rec *fstab) { return fstab->fs_mgr_flags & (MF_CRYPT | MF_FORCECRYPT); } -int fs_mgr_is_noemulatedsd(struct fstab_rec *fstab) +int fs_mgr_is_file_encrypted(const struct fstab_rec *fstab) +{ + return fstab->fs_mgr_flags & MF_FILEENCRYPTION; +} + +int fs_mgr_is_noemulatedsd(const struct fstab_rec *fstab) { return fstab->fs_mgr_flags & MF_NOEMULATEDSD; } diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h index fd58306..2d252e4 100644 --- a/fs_mgr/fs_mgr_priv.h +++ b/fs_mgr/fs_mgr_priv.h @@ -21,6 +21,7 @@ #include <fs_mgr.h> #define INFO(x...) KLOG_INFO("fs_mgr", x) +#define WARNING(x...) KLOG_WARNING("fs_mgr", x) #define ERROR(x...) KLOG_ERROR("fs_mgr", x) #define CRYPTO_TMPFS_OPTIONS "size=256m,mode=0771,uid=1000,gid=1000" @@ -76,8 +77,10 @@ #define MF_NOEMULATEDSD 0x800 /* no emulated sdcard daemon, sd card is the only external storage */ #define MF_FORMATTABLE 0x1000 +#define MF_FILEENCRYPTION 0x2000 #define DM_BUF_SIZE 4096 -#endif /* __CORE_FS_MGR_PRIV_H */ +int fs_mgr_set_blk_ro(const char *blockdev); +#endif /* __CORE_FS_MGR_PRIV_H */ diff --git a/fs_mgr/fs_mgr_verity.c b/fs_mgr/fs_mgr_verity.c index a82db4e..6ef46ba 100644 --- a/fs_mgr/fs_mgr_verity.c +++ b/fs_mgr/fs_mgr_verity.c @@ -38,13 +38,38 @@ #include "mincrypt/sha256.h" #include "ext4_sb.h" +#include "squashfs_utils.h" #include "fs_mgr_priv.h" #include "fs_mgr_priv_verity.h" +#define FSTAB_PREFIX "/fstab." + #define VERITY_METADATA_SIZE 32768 #define VERITY_TABLE_RSA_KEY "/verity_key" +#define METADATA_MAGIC 0x01564c54 +#define METADATA_TAG_MAX_LENGTH 63 +#define METADATA_EOD "eod" + +#define VERITY_LASTSIG_TAG "verity_lastsig" + +#define VERITY_STATE_TAG "verity_state" +#define VERITY_STATE_HEADER 0x83c0ae9d +#define VERITY_STATE_VERSION 1 + +#define VERITY_KMSG_RESTART "dm-verity device corrupted" +#define VERITY_KMSG_BUFSIZE 1024 + +#define __STRINGIFY(x) #x +#define STRINGIFY(x) __STRINGIFY(x) + +struct verity_state { + uint32_t header; + uint32_t version; + int32_t mode; +}; + extern struct fs_info info; static RSAPublicKey *load_key(char *path) @@ -116,11 +141,25 @@ out: return retval; } -static int get_target_device_size(char *blk_device, uint64_t *device_size) +static int squashfs_get_target_device_size(char *blk_device, uint64_t *device_size) +{ + struct squashfs_info sq_info; + + if (squashfs_parse_sb(blk_device, &sq_info) >= 0) { + *device_size = sq_info.bytes_used_4K_padded; + return 0; + } else { + return -1; + } +} + +static int ext4_get_target_device_size(char *blk_device, uint64_t *device_size) { int data_device; struct ext4_super_block sb; - struct fs_info info = {0}; + struct fs_info info; + + info.len = 0; /* Only len is set to 0 to ask the device for real size. */ data_device = TEMP_FAILURE_RETRY(open(blk_device, O_RDONLY | O_CLOEXEC)); if (data_device == -1) { @@ -147,16 +186,38 @@ static int get_target_device_size(char *blk_device, uint64_t *device_size) return 0; } -static int read_verity_metadata(char *block_device, char **signature, char **table) +static int get_fs_size(char *fs_type, char *blk_device, uint64_t *device_size) { + if (!strcmp(fs_type, "ext4")) { + if (ext4_get_target_device_size(blk_device, device_size) < 0) { + ERROR("Failed to get ext4 fs size on %s.", blk_device); + return -1; + } + } else if (!strcmp(fs_type, "squashfs")) { + if (squashfs_get_target_device_size(blk_device, device_size) < 0) { + ERROR("Failed to get squashfs fs size on %s.", blk_device); + return -1; + } + } else { + ERROR("%s: Unsupported filesystem for verity.", fs_type); + return -1; + } + return 0; +} + +static int read_verity_metadata(uint64_t device_size, char *block_device, char **signature, + char **table) { unsigned magic_number; unsigned table_length; - uint64_t device_length; int protocol_version; int device; int retval = FS_MGR_SETUP_VERITY_FAIL; - *signature = 0; - *table = 0; + + *signature = NULL; + + if (table) { + *table = NULL; + } device = TEMP_FAILURE_RETRY(open(block_device, O_RDONLY | O_CLOEXEC)); if (device == -1) { @@ -164,12 +225,7 @@ static int read_verity_metadata(char *block_device, char **signature, char **tab goto out; } - // find the start of the verity metadata - if (get_target_device_size(block_device, &device_length) < 0) { - ERROR("Could not get target device size.\n"); - goto out; - } - if (TEMP_FAILURE_RETRY(lseek64(device, device_length, SEEK_SET)) < 0) { + if (TEMP_FAILURE_RETRY(lseek64(device, device_size, SEEK_SET)) < 0) { ERROR("Could not seek to start of verity metadata block.\n"); goto out; } @@ -190,8 +246,7 @@ static int read_verity_metadata(char *block_device, char **signature, char **tab #endif if (magic_number != VERITY_METADATA_MAGIC_NUMBER) { - ERROR("Couldn't find verity metadata at offset %"PRIu64"!\n", - device_length); + ERROR("Couldn't find verity metadata at offset %"PRIu64"!\n", device_size); goto out; } @@ -217,6 +272,11 @@ static int read_verity_metadata(char *block_device, char **signature, char **tab goto out; } + if (!table) { + retval = FS_MGR_SETUP_VERITY_SUCCESS; + goto out; + } + // get the size of the table if (TEMP_FAILURE_RETRY(read(device, &table_length, sizeof(table_length))) != sizeof(table_length)) { @@ -244,10 +304,13 @@ out: TEMP_FAILURE_RETRY(close(device)); if (retval != FS_MGR_SETUP_VERITY_SUCCESS) { - free(*table); free(*signature); - *table = 0; - *signature = 0; + *signature = NULL; + + if (table) { + free(*table); + *table = NULL; + } } return retval; @@ -292,15 +355,12 @@ static int get_verity_device_name(struct dm_ioctl *io, char *name, int fd, char return 0; } -static int load_verity_table(struct dm_ioctl *io, char *name, char *blockdev, int fd, char *table) +static int load_verity_table(struct dm_ioctl *io, char *name, uint64_t device_size, int fd, char *table, + int mode) { char *verity_params; char *buffer = (char*) io; - uint64_t device_size = 0; - - if (get_target_device_size(blockdev, &device_size) < 0) { - return -1; - } + size_t bufsize; verity_ioctl_init(io, name, DM_STATUS_TABLE_FLAG); @@ -315,7 +375,15 @@ static int load_verity_table(struct dm_ioctl *io, char *name, char *blockdev, in // build the verity params here verity_params = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec); - if (sprintf(verity_params, "%s", table) < 0) { + bufsize = DM_BUF_SIZE - (verity_params - buffer); + + if (mode == VERITY_MODE_EIO) { + // allow operation with older dm-verity drivers that are unaware + // of the mode parameter by omitting it; this also means that we + // cannot use logging mode with these drivers, they always cause + // an I/O error for corrupted blocks + strcpy(verity_params, table); + } else if (snprintf(verity_params, bufsize, "%s %d", table, mode) < 0) { return -1; } @@ -354,36 +422,515 @@ static int test_access(char *device) { return -1; } -static int set_verified_property(char *name) { - int ret; - char *key; - ret = asprintf(&key, "partition.%s.verified", name); - if (ret < 0) { - ERROR("Error formatting verified property"); - return ret; - } - ret = PROP_NAME_MAX - strlen(key); - if (ret < 0) { - ERROR("Verified property name is too long"); +static int check_verity_restart(const char *fname) +{ + char buffer[VERITY_KMSG_BUFSIZE + 1]; + int fd; + int rc = 0; + ssize_t size; + struct stat s; + + fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_CLOEXEC)); + + if (fd == -1) { + if (errno != ENOENT) { + ERROR("Failed to open %s (%s)\n", fname, strerror(errno)); + } + goto out; + } + + if (fstat(fd, &s) == -1) { + ERROR("Failed to fstat %s (%s)\n", fname, strerror(errno)); + goto out; + } + + size = VERITY_KMSG_BUFSIZE; + + if (size > s.st_size) { + size = s.st_size; + } + + if (lseek(fd, s.st_size - size, SEEK_SET) == -1) { + ERROR("Failed to lseek %jd %s (%s)\n", (intmax_t)(s.st_size - size), fname, + strerror(errno)); + goto out; + } + + if (TEMP_FAILURE_RETRY(read(fd, buffer, size)) != size) { + ERROR("Failed to read %zd bytes from %s (%s)\n", size, fname, + strerror(errno)); + goto out; + } + + buffer[size] = '\0'; + + if (strstr(buffer, VERITY_KMSG_RESTART) != NULL) { + rc = 1; + } + +out: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + return rc; +} + +static int was_verity_restart() +{ + static const char *files[] = { + "/sys/fs/pstore/console-ramoops", + "/proc/last_kmsg", + NULL + }; + int i; + + for (i = 0; files[i]; ++i) { + if (check_verity_restart(files[i])) { + return 1; + } + } + + return 0; +} + +static int metadata_add(FILE *fp, long start, const char *tag, + unsigned int length, off64_t *offset) +{ + if (fseek(fp, start, SEEK_SET) < 0 || + fprintf(fp, "%s %u\n", tag, length) < 0) { return -1; } - ret = property_set(key, "1"); - if (ret < 0) - ERROR("Error setting verified property %s: %d", key, ret); - free(key); - return ret; + + *offset = ftell(fp); + + if (fseek(fp, length, SEEK_CUR) < 0 || + fprintf(fp, METADATA_EOD " 0\n") < 0) { + return -1; + } + + return 0; +} + +static int metadata_find(const char *fname, const char *stag, + unsigned int slength, off64_t *offset) +{ + FILE *fp = NULL; + char tag[METADATA_TAG_MAX_LENGTH + 1]; + int rc = -1; + int n; + long start = 0x4000; /* skip cryptfs metadata area */ + uint32_t magic; + unsigned int length = 0; + + if (!fname) { + return -1; + } + + fp = fopen(fname, "r+"); + + if (!fp) { + ERROR("Failed to open %s (%s)\n", fname, strerror(errno)); + goto out; + } + + /* check magic */ + if (fseek(fp, start, SEEK_SET) < 0 || + fread(&magic, sizeof(magic), 1, fp) != 1) { + ERROR("Failed to read magic from %s (%s)\n", fname, strerror(errno)); + goto out; + } + + if (magic != METADATA_MAGIC) { + magic = METADATA_MAGIC; + + if (fseek(fp, start, SEEK_SET) < 0 || + fwrite(&magic, sizeof(magic), 1, fp) != 1) { + ERROR("Failed to write magic to %s (%s)\n", fname, strerror(errno)); + goto out; + } + + rc = metadata_add(fp, start + sizeof(magic), stag, slength, offset); + if (rc < 0) { + ERROR("Failed to add metadata to %s: %s\n", fname, strerror(errno)); + } + + goto out; + } + + start += sizeof(magic); + + while (1) { + n = fscanf(fp, "%" STRINGIFY(METADATA_TAG_MAX_LENGTH) "s %u\n", + tag, &length); + + if (n == 2 && strcmp(tag, METADATA_EOD)) { + /* found a tag */ + start = ftell(fp); + + if (!strcmp(tag, stag) && length == slength) { + *offset = start; + rc = 0; + goto out; + } + + start += length; + + if (fseek(fp, length, SEEK_CUR) < 0) { + ERROR("Failed to seek %s (%s)\n", fname, strerror(errno)); + goto out; + } + } else { + rc = metadata_add(fp, start, stag, slength, offset); + if (rc < 0) { + ERROR("Failed to write metadata to %s: %s\n", fname, + strerror(errno)); + } + goto out; + } + } + +out: + if (fp) { + fflush(fp); + fclose(fp); + } + + return rc; +} + +static int write_verity_state(const char *fname, off64_t offset, int32_t mode) +{ + int fd; + int rc = -1; + struct verity_state s = { VERITY_STATE_HEADER, VERITY_STATE_VERSION, mode }; + + fd = TEMP_FAILURE_RETRY(open(fname, O_WRONLY | O_SYNC | O_CLOEXEC)); + + if (fd == -1) { + ERROR("Failed to open %s (%s)\n", fname, strerror(errno)); + goto out; + } + + if (TEMP_FAILURE_RETRY(pwrite64(fd, &s, sizeof(s), offset)) != sizeof(s)) { + ERROR("Failed to write %zu bytes to %s to offset %" PRIu64 " (%s)\n", + sizeof(s), fname, offset, strerror(errno)); + goto out; + } + + rc = 0; + +out: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + return rc; +} + +static int read_verity_state(const char *fname, off64_t offset, int *mode) +{ + int fd = -1; + int rc = -1; + struct verity_state s; + + fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_CLOEXEC)); + + if (fd == -1) { + ERROR("Failed to open %s (%s)\n", fname, strerror(errno)); + goto out; + } + + if (TEMP_FAILURE_RETRY(pread64(fd, &s, sizeof(s), offset)) != sizeof(s)) { + ERROR("Failed to read %zu bytes from %s offset %" PRIu64 " (%s)\n", + sizeof(s), fname, offset, strerror(errno)); + goto out; + } + + if (s.header != VERITY_STATE_HEADER) { + /* space allocated, but no state written. write default state */ + *mode = VERITY_MODE_DEFAULT; + rc = write_verity_state(fname, offset, *mode); + goto out; + } + + if (s.version != VERITY_STATE_VERSION) { + ERROR("Unsupported verity state version (%u)\n", s.version); + goto out; + } + + if (s.mode < VERITY_MODE_EIO || + s.mode > VERITY_MODE_LAST) { + ERROR("Unsupported verity mode (%u)\n", s.mode); + goto out; + } + + *mode = s.mode; + rc = 0; + +out: + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + return rc; +} + +static int compare_last_signature(struct fstab_rec *fstab, int *match) +{ + char tag[METADATA_TAG_MAX_LENGTH + 1]; + char *signature = NULL; + int fd = -1; + int rc = -1; + uint8_t curr[SHA256_DIGEST_SIZE]; + uint8_t prev[SHA256_DIGEST_SIZE]; + off64_t offset = 0; + uint64_t device_size; + + *match = 1; + + // get verity filesystem size + if (get_fs_size(fstab->fs_type, fstab->blk_device, &device_size) < 0) { + ERROR("Failed to get filesystem size\n"); + goto out; + } + + if (read_verity_metadata(device_size, fstab->blk_device, &signature, NULL) < 0) { + ERROR("Failed to read verity signature from %s\n", fstab->mount_point); + goto out; + } + + SHA256_hash(signature, RSANUMBYTES, curr); + + if (snprintf(tag, sizeof(tag), VERITY_LASTSIG_TAG "_%s", + basename(fstab->mount_point)) >= (int)sizeof(tag)) { + ERROR("Metadata tag name too long for %s\n", fstab->mount_point); + goto out; + } + + if (metadata_find(fstab->verity_loc, tag, SHA256_DIGEST_SIZE, + &offset) < 0) { + goto out; + } + + fd = TEMP_FAILURE_RETRY(open(fstab->verity_loc, O_RDWR | O_SYNC | O_CLOEXEC)); + + if (fd == -1) { + ERROR("Failed to open %s: %s\n", fstab->verity_loc, strerror(errno)); + goto out; + } + + if (TEMP_FAILURE_RETRY(pread64(fd, prev, sizeof(prev), + offset)) != sizeof(prev)) { + ERROR("Failed to read %zu bytes from %s offset %" PRIu64 " (%s)\n", + sizeof(prev), fstab->verity_loc, offset, strerror(errno)); + goto out; + } + + *match = !memcmp(curr, prev, SHA256_DIGEST_SIZE); + + if (!*match) { + /* update current signature hash */ + if (TEMP_FAILURE_RETRY(pwrite64(fd, curr, sizeof(curr), + offset)) != sizeof(curr)) { + ERROR("Failed to write %zu bytes to %s offset %" PRIu64 " (%s)\n", + sizeof(curr), fstab->verity_loc, offset, strerror(errno)); + goto out; + } + } + + rc = 0; + +out: + free(signature); + + if (fd != -1) { + TEMP_FAILURE_RETRY(close(fd)); + } + + return rc; +} + +static int get_verity_state_offset(struct fstab_rec *fstab, off64_t *offset) +{ + char tag[METADATA_TAG_MAX_LENGTH + 1]; + + if (snprintf(tag, sizeof(tag), VERITY_STATE_TAG "_%s", + basename(fstab->mount_point)) >= (int)sizeof(tag)) { + ERROR("Metadata tag name too long for %s\n", fstab->mount_point); + return -1; + } + + return metadata_find(fstab->verity_loc, tag, sizeof(struct verity_state), + offset); +} + +static int load_verity_state(struct fstab_rec *fstab, int *mode) +{ + off64_t offset = 0; + int match = 0; + + if (get_verity_state_offset(fstab, &offset) < 0) { + /* fall back to stateless behavior */ + *mode = VERITY_MODE_EIO; + return 0; + } + + if (was_verity_restart()) { + /* device was restarted after dm-verity detected a corrupted + * block, so switch to logging mode */ + *mode = VERITY_MODE_LOGGING; + return write_verity_state(fstab->verity_loc, offset, *mode); + } + + if (!compare_last_signature(fstab, &match) && !match) { + /* partition has been reflashed, reset dm-verity state */ + *mode = VERITY_MODE_DEFAULT; + return write_verity_state(fstab->verity_loc, offset, *mode); + } + + return read_verity_state(fstab->verity_loc, offset, mode); +} + +int fs_mgr_load_verity_state(int *mode) +{ + char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)]; + char propbuf[PROPERTY_VALUE_MAX]; + int rc = -1; + int i; + int current; + struct fstab *fstab = NULL; + + /* return the default mode, unless any of the verified partitions are in + * logging mode, in which case return that */ + *mode = VERITY_MODE_DEFAULT; + + property_get("ro.hardware", propbuf, ""); + snprintf(fstab_filename, sizeof(fstab_filename), FSTAB_PREFIX"%s", propbuf); + + fstab = fs_mgr_read_fstab(fstab_filename); + + if (!fstab) { + ERROR("Failed to read %s\n", fstab_filename); + goto out; + } + + for (i = 0; i < fstab->num_entries; i++) { + if (!fs_mgr_is_verified(&fstab->recs[i])) { + continue; + } + + rc = load_verity_state(&fstab->recs[i], ¤t); + if (rc < 0) { + continue; + } + + if (current == VERITY_MODE_LOGGING) { + *mode = current; + } + } + + rc = 0; + +out: + if (fstab) { + fs_mgr_free_fstab(fstab); + } + + return rc; +} + +int fs_mgr_update_verity_state(fs_mgr_verity_state_callback callback) +{ + _Alignas(struct dm_ioctl) char buffer[DM_BUF_SIZE]; + char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)]; + char *mount_point; + char propbuf[PROPERTY_VALUE_MAX]; + char *status; + int fd = -1; + int i; + int mode; + int rc = -1; + off64_t offset = 0; + struct dm_ioctl *io = (struct dm_ioctl *) buffer; + struct fstab *fstab = NULL; + + fd = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC)); + + if (fd == -1) { + ERROR("Error opening device mapper (%s)\n", strerror(errno)); + goto out; + } + + property_get("ro.hardware", propbuf, ""); + snprintf(fstab_filename, sizeof(fstab_filename), FSTAB_PREFIX"%s", propbuf); + + fstab = fs_mgr_read_fstab(fstab_filename); + + if (!fstab) { + ERROR("Failed to read %s\n", fstab_filename); + goto out; + } + + for (i = 0; i < fstab->num_entries; i++) { + if (!fs_mgr_is_verified(&fstab->recs[i])) { + continue; + } + + if (get_verity_state_offset(&fstab->recs[i], &offset) < 0 || + read_verity_state(fstab->recs[i].verity_loc, offset, &mode) < 0) { + continue; + } + + mount_point = basename(fstab->recs[i].mount_point); + verity_ioctl_init(io, mount_point, 0); + + if (ioctl(fd, DM_TABLE_STATUS, io)) { + ERROR("Failed to query DM_TABLE_STATUS for %s (%s)\n", mount_point, + strerror(errno)); + continue; + } + + status = &buffer[io->data_start + sizeof(struct dm_target_spec)]; + + if (*status == 'C') { + if (write_verity_state(fstab->recs[i].verity_loc, offset, + VERITY_MODE_LOGGING) < 0) { + continue; + } + } + + if (callback) { + callback(&fstab->recs[i], mount_point, mode, *status); + } + } + + rc = 0; + +out: + if (fstab) { + fs_mgr_free_fstab(fstab); + } + + if (fd) { + TEMP_FAILURE_RETRY(close(fd)); + } + + return rc; } int fs_mgr_setup_verity(struct fstab_rec *fstab) { int retval = FS_MGR_SETUP_VERITY_FAIL; int fd = -1; + int mode; char *verity_blk_name = 0; char *verity_table = 0; char *verity_table_signature = 0; + uint64_t device_size = 0; - char buffer[DM_BUF_SIZE]; + _Alignas(struct dm_ioctl) char buffer[DM_BUF_SIZE]; struct dm_ioctl *io = (struct dm_ioctl *) buffer; char *mount_point = basename(fstab->mount_point); @@ -391,16 +938,15 @@ int fs_mgr_setup_verity(struct fstab_rec *fstab) { io->flags |= 1; io->target_count = 1; - // check to ensure that the verity device is ext4 - // TODO: support non-ext4 filesystems - if (strcmp(fstab->fs_type, "ext4")) { - ERROR("Cannot verify non-ext4 device (%s)", fstab->fs_type); + // get verity filesystem size + if (get_fs_size(fstab->fs_type, fstab->blk_device, &device_size) < 0) { return retval; } // read the verity block at the end of the block device // send error code up the chain so we can detect attempts to disable verity - retval = read_verity_metadata(fstab->blk_device, + retval = read_verity_metadata(device_size, + fstab->blk_device, &verity_table_signature, &verity_table); if (retval < 0) { @@ -434,8 +980,19 @@ int fs_mgr_setup_verity(struct fstab_rec *fstab) { goto out; } + if (load_verity_state(fstab, &mode) < 0) { + /* if accessing or updating the state failed, switch to the default + * safe mode. This makes sure the device won't end up in an endless + * restart loop, and no corrupted data will be exposed to userspace + * without a warning. */ + mode = VERITY_MODE_EIO; + } + + INFO("Enabling dm-verity for %s (mode %d)\n", mount_point, mode); + // load the verity mapping table - if (load_verity_table(io, mount_point, fstab->blk_device, fd, verity_table) < 0) { + if (load_verity_table(io, mount_point, device_size, fd, verity_table, + mode) < 0) { goto out; } @@ -444,6 +1001,9 @@ int fs_mgr_setup_verity(struct fstab_rec *fstab) { goto out; } + // mark the underlying block device as read-only + fs_mgr_set_blk_ro(fstab->blk_device); + // assign the new verity block device as the block device free(fstab->blk_device); fstab->blk_device = verity_blk_name; @@ -454,8 +1014,7 @@ int fs_mgr_setup_verity(struct fstab_rec *fstab) { goto out; } - // set the property indicating that the partition is verified - retval = set_verified_property(mount_point); + retval = FS_MGR_SETUP_VERITY_SUCCESS; out: if (fd != -1) { diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h index 5a6ad2d..68af452 100644 --- a/fs_mgr/include/fs_mgr.h +++ b/fs_mgr/include/fs_mgr.h @@ -31,6 +31,15 @@ extern "C" { #endif +// Verity modes +enum verity_mode { + VERITY_MODE_EIO = 0, + VERITY_MODE_LOGGING = 1, + VERITY_MODE_RESTART = 2, + VERITY_MODE_LAST = VERITY_MODE_RESTART, + VERITY_MODE_DEFAULT = VERITY_MODE_RESTART +}; + /* * The entries must be kept in the same order as they were seen in the fstab. * Unless explicitly requested, a lookup on mount point should always @@ -58,13 +67,20 @@ struct fstab_rec { unsigned int zram_size; }; +// Callback function for verity status +typedef void (*fs_mgr_verity_state_callback)(struct fstab_rec *fstab, + const char *mount_point, int mode, int status); + struct fstab *fs_mgr_read_fstab(const char *fstab_path); void fs_mgr_free_fstab(struct fstab *fstab); +#define FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED 5 +#define FS_MGR_MNTALL_DEV_DEFAULT_FILE_ENCRYPTED 4 #define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 3 #define FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION 2 #define FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED 1 #define FS_MGR_MNTALL_DEV_NOT_ENCRYPTED 0 +#define FS_MGR_MNTALL_FAIL -1 int fs_mgr_mount_all(struct fstab *fstab); #define FS_MGR_DOMNT_FAILED -1 @@ -75,15 +91,18 @@ int fs_mgr_do_tmpfs_mount(char *n_name); int fs_mgr_unmount_all(struct fstab *fstab); int fs_mgr_get_crypt_info(struct fstab *fstab, char *key_loc, char *real_blk_device, int size); +int fs_mgr_load_verity_state(int *mode); +int fs_mgr_update_verity_state(fs_mgr_verity_state_callback callback); int fs_mgr_add_entry(struct fstab *fstab, const char *mount_point, const char *fs_type, const char *blk_device); struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path); -int fs_mgr_is_voldmanaged(struct fstab_rec *fstab); -int fs_mgr_is_nonremovable(struct fstab_rec *fstab); -int fs_mgr_is_verified(struct fstab_rec *fstab); -int fs_mgr_is_encryptable(struct fstab_rec *fstab); -int fs_mgr_is_noemulatedsd(struct fstab_rec *fstab); +int fs_mgr_is_voldmanaged(const struct fstab_rec *fstab); +int fs_mgr_is_nonremovable(const struct fstab_rec *fstab); +int fs_mgr_is_verified(const struct fstab_rec *fstab); +int fs_mgr_is_encryptable(const struct fstab_rec *fstab); +int fs_mgr_is_file_encrypted(const struct fstab_rec *fstab); +int fs_mgr_is_noemulatedsd(const struct fstab_rec *fstab); int fs_mgr_is_formattable(struct fstab_rec *fstab); int fs_mgr_swapon_all(struct fstab *fstab); @@ -94,4 +113,3 @@ int fs_mgr_do_format(struct fstab_rec *fstab); #endif #endif /* __CORE_FS_MGR_H */ - |