| /* |
| * FIVE dmverity functions |
| * |
| * Copyright (C) 2019 Samsung Electronics, Inc. |
| * |
| * Egor Uleyskiy, <e.uleyskiy@samsung.com> |
| * Viacheslav Vovchenko <v.vovchenko@samsung.com> |
| * Yevgen Kopylov <y.kopylov@samsung.com> |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include "five_dmverity.h" |
| #include "five.h" |
| |
| #include "drivers/md/dm.h" |
| #include "drivers/block/loop.h" |
| |
| #ifdef CONFIG_FIVE_DEBUG |
| #include <linux/debugfs.h> |
| #endif |
| |
| #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) || defined(CONFIG_FIVE_DEBUG) |
| static bool check_prebuilt_paths_dmverity; |
| #endif |
| |
| static inline int __init init_fs(void); |
| |
| int __init five_init_dmverity(void) |
| { |
| return init_fs(); |
| } |
| |
| static bool is_loop_device(const struct file *file) |
| { |
| const struct inode *inode; |
| |
| inode = file_inode(file); |
| if (unlikely(!inode || !inode->i_sb)) |
| return false; |
| |
| return MAJOR(inode->i_sb->s_dev) == LOOP_MAJOR ? true : false; |
| } |
| |
| /* Check whether the file belongs to the device mapper's enabled target |
| * Need to check: |
| * - Super block points to a block device. |
| * - The block device is created by the device mapper |
| * (dm_get_md/dm_put_md functions). |
| * - The block device is a disk with a name "dm-[1-9]". |
| * - The disk is in readonly mode. |
| * - There is only one enabled target in the device mapper table. |
| * - The target name is known target name. |
| */ |
| static enum five_dmverity_codes is_dmverity_partition( |
| const struct file *file) |
| { |
| const char dm_dev_prefix[] = "dm-"; |
| const char * const dm_targets_name[] = { |
| "verity", "verity-fec" |
| }; |
| enum five_dmverity_codes result = FIVE_DMV_NO_DM_TARGET_NAME; |
| int srcu_idx; |
| unsigned int num_targets; |
| size_t i; |
| const struct inode *inode; |
| struct super_block *i_sb; |
| struct mapped_device *md; |
| struct gendisk *disk; |
| struct dm_table *table; |
| struct dm_target *target; |
| |
| inode = file_inode(file); |
| if (unlikely(!inode)) { |
| result = FIVE_DMV_BAD_INPUT; |
| goto exit; |
| } |
| |
| i_sb = inode->i_sb; |
| if (unlikely(!i_sb)) { |
| result = FIVE_DMV_BAD_INPUT; |
| goto exit; |
| } |
| |
| md = dm_get_md(i_sb->s_dev); |
| if (!md) { |
| result = FIVE_DMV_NO_DM_DEVICE; |
| goto exit; |
| } |
| |
| disk = dm_disk(md); |
| if (!disk) { |
| result = FIVE_DMV_NO_DM_DISK; |
| goto exit_free_dm; |
| } |
| |
| if (!get_disk_ro(disk)) { |
| result = FIVE_DMV_NOT_READONLY_DM_DISK; |
| goto exit_free_dm; |
| } |
| |
| if (strncmp(disk->disk_name, dm_dev_prefix, |
| sizeof(dm_dev_prefix) - 1)) { |
| result = FIVE_DMV_BAD_DM_PREFIX_NAME; |
| goto exit_free_dm; |
| } |
| |
| table = dm_get_live_table(md, &srcu_idx); |
| if (!table) { |
| result = FIVE_DMV_NO_DM_TABLE; |
| goto exit_free_dm; |
| } |
| |
| if (dm_table_get_mode(table) & ~FMODE_READ) { |
| result = FIVE_DMV_NOT_READONLY_DM_TABLE; |
| goto exit_free_table; |
| } |
| |
| num_targets = dm_table_get_num_targets(table); |
| target = dm_table_get_target(table, 0); |
| if (!target) { |
| result = FIVE_DMV_NO_DM_TARGET; |
| goto exit_free_table; |
| } |
| |
| /* Only support devices that have a single target */ |
| if (num_targets != 1) { |
| result = FIVE_DMV_NOT_SINGLE_TARGET; |
| goto exit_free_table; |
| } |
| |
| /* Checks the specific target name from available targets |
| * in device mapper. |
| */ |
| for (i = 0; i < ARRAY_SIZE(dm_targets_name); ++i) { |
| if (!strncmp(target->type->name, dm_targets_name[i], |
| strlen(dm_targets_name[i]) + 1)) { |
| result = FIVE_DMV_PARTITION; |
| break; |
| } |
| } |
| |
| exit_free_table: |
| dm_put_live_table(md, srcu_idx); |
| exit_free_dm: |
| dm_put(md); |
| exit: |
| return result; |
| } |
| |
| static enum five_dmverity_codes is_dmverity_loop(const struct file *file) |
| { |
| const fmode_t mode_bdev = FMODE_READ; |
| enum five_dmverity_codes result = FIVE_DMV_NO_SB_LOOP_DEVICE; |
| struct super_block *i_sb; |
| const struct inode *inode; |
| struct block_device *bdev; |
| struct gendisk *bd_disk; |
| struct file *file_mount_to_lo_dev; |
| struct loop_device *lo_dev; |
| |
| inode = file_inode(file); |
| i_sb = inode->i_sb; |
| |
| if (MAJOR(i_sb->s_dev) != LOOP_MAJOR) |
| goto exit; |
| |
| bdev = blkdev_get_by_dev(i_sb->s_dev, mode_bdev, NULL); |
| if (IS_ERR_OR_NULL(bdev) || MAJOR(bdev->bd_dev) != LOOP_MAJOR) { |
| result = FIVE_DMV_NO_BD_LOOP_DEVICE; |
| goto exit; |
| } |
| |
| bd_disk = bdev->bd_disk; |
| if (unlikely(!bd_disk)) { |
| result = FIVE_DMV_NO_BD_DISK; |
| goto exit_free_blkdev; |
| } |
| |
| lo_dev = bd_disk->private_data; |
| if (unlikely(!lo_dev)) { |
| result = FIVE_DMV_NO_LOOP_DEV; |
| goto exit_free_blkdev; |
| } |
| |
| file_mount_to_lo_dev = lo_dev->lo_backing_file; |
| if (unlikely(!file_mount_to_lo_dev)) { |
| result = FIVE_DMV_NO_LOOP_BACK_FILE; |
| goto exit_free_blkdev; |
| } |
| |
| result = is_dmverity_partition(file_mount_to_lo_dev); |
| |
| exit_free_blkdev: |
| blkdev_put(bdev, mode_bdev); |
| exit: |
| return result; |
| } |
| |
| #if defined(CONFIG_SAMSUNG_PRODUCT_SHIP) && !defined(CONFIG_FIVE_DEBUG) |
| bool five_is_dmverity_protected(const struct file *file) |
| { |
| enum five_dmverity_codes dmv_file_status; |
| bool result = false; |
| |
| if (unlikely(!file)) |
| return result; |
| |
| /* Checks is loop device /dev/block/loopX or no */ |
| if (is_loop_device(file)) |
| /* Checks /dev/block/loopX mounted to /apex/name@version and |
| * it should specify to /system/apex/[name].apex under dmverity |
| * protection. |
| */ |
| dmv_file_status = is_dmverity_loop(file); |
| else |
| /* Checks file on dmverity partition, in case of dmverity is |
| * disabled need to check prebuilt paths of dmverity. |
| */ |
| dmv_file_status = is_dmverity_partition(file); |
| |
| if (dmv_file_status == FIVE_DMV_PARTITION) |
| result = true; |
| |
| return result; |
| } |
| #else |
| static bool is_dmverity_prebuit_path(const struct file *file) |
| { |
| const char * const paths[] = { |
| "/system/", "/vendor/", "/apex/", |
| "/product/", "/odm/", "/prism/", "/optics/" |
| }; |
| const char *pathname = NULL; |
| char *pathbuf = NULL; |
| char filename[NAME_MAX]; |
| bool result = false; |
| size_t i; |
| |
| pathname = five_d_path(&file->f_path, &pathbuf, filename); |
| |
| for (i = 0; i < ARRAY_SIZE(paths); ++i) { |
| if (!strncmp(pathname, paths[i], strlen(paths[i]))) { |
| result = true; |
| break; |
| } |
| } |
| |
| if (pathbuf) |
| __putname(pathbuf); |
| |
| return result; |
| } |
| |
| bool five_is_dmverity_protected(const struct file *file) |
| { |
| enum five_dmverity_codes dmv_file_status; |
| bool result = false; |
| |
| if (unlikely(!file)) |
| return result; |
| |
| if (check_prebuilt_paths_dmverity) |
| return is_dmverity_prebuit_path(file); |
| |
| /* Checks is loop device /dev/block/loopX or no */ |
| if (is_loop_device(file)) |
| /* Checks /dev/block/loopX mounted to /apex/name@version/ and |
| * it should specify to /system/apex/[name].apex under dmverity |
| * protection. |
| */ |
| dmv_file_status = is_dmverity_loop(file); |
| else |
| /* Checks file on dmverity partition, in case of dmverity is |
| * disabled need to check prebuilt paths dmverity. |
| */ |
| dmv_file_status = is_dmverity_partition(file); |
| |
| if (dmv_file_status == FIVE_DMV_PARTITION) |
| result = true; |
| else |
| result = is_dmverity_prebuit_path(file); |
| |
| return result; |
| } |
| #endif |
| |
| #ifdef CONFIG_FIVE_DEBUG |
| static ssize_t five_check_prebuilt_paths_dmverity_read(struct file *file, |
| char __user *user_buf, size_t count, loff_t *pos) |
| { |
| char buf[2]; |
| |
| buf[0] = check_prebuilt_paths_dmverity ? '1' : '0'; |
| buf[1] = '\n'; |
| |
| return simple_read_from_buffer(user_buf, count, pos, buf, sizeof(buf)); |
| } |
| |
| static ssize_t five_check_prebuilt_paths_dmverity_write(struct file *file, |
| const char __user *buf, size_t count, loff_t *pos) |
| { |
| char command; |
| |
| if (get_user(command, buf)) |
| return -EFAULT; |
| |
| switch (command) { |
| case '0': |
| check_prebuilt_paths_dmverity = false; |
| break; |
| case '1': |
| check_prebuilt_paths_dmverity = true; |
| break; |
| default: |
| pr_err("FIVE: %s: unknown cmd: %hhx\n", __func__, command); |
| return -EINVAL; |
| } |
| |
| pr_info("FIVE debug: Check dm-verity file paths %s\n", |
| check_prebuilt_paths_dmverity ? "enabled" : "disabled"); |
| return count; |
| } |
| |
| static const struct file_operations five_dmverity_fops = { |
| .owner = THIS_MODULE, |
| .read = five_check_prebuilt_paths_dmverity_read, |
| .write = five_check_prebuilt_paths_dmverity_write |
| }; |
| |
| static inline int __init init_fs(void) |
| { |
| struct dentry *debug_file = NULL; |
| const umode_t umode = 0664; |
| |
| debug_file = debugfs_create_file("five_prebuilt_paths_dmverity", |
| umode, NULL, NULL, &five_dmverity_fops); |
| if (IS_ERR_OR_NULL(debug_file)) |
| goto error; |
| |
| return 0; |
| error: |
| if (debug_file) |
| return -PTR_ERR(debug_file); |
| |
| return -EEXIST; |
| } |
| #else |
| static inline int __init init_fs(void) |
| { |
| return 0; |
| } |
| #endif |