Jimmy Chen

A Programmer

(原创)高通MSM8909+Android 8.1.0 FDE全盘加密失败分析记录

  • Author: Jimmy Chen
  • Version: v1.0
  • Date:2018-04-13

  最近在MSM8909+Android 8.1.0上熟悉高通平台的相关开发。在尝试进行FDE全盘机密的时候,出现了失败,显示Encryption unsuccessful,如下图显示

《(原创)高通MSM8909+Android 8.1.0 FDE全盘加密失败分析记录》

  当看到这张图片是有点懵逼的,因为之前都在分析FBE,还没分析过FDE,不过想想大概分析流程应该是差不多的吧。首先当然是看看fstab的内容了

fstab.qcom

#                                                                         
#/dev/block/bootdevice/by-name/system         /system      ext4    ro,barrier=1                                        wait
/dev/block/bootdevice/by-name/userdata       /data        ext4    nosuid,noatime,nodev,barrier=1,noauto_da_alloc,discard      wait,check,encryptable=footer,quota
/devices/soc/7864900.sdhci/mmc_host*        /storage/sdcard1   vfat    nosuid,nodev         wait,voldmanaged=sdcard1:auto,noemulatedsd,encryptable=footer
/dev/block/bootdevice/by-name/config         /frp  emmc  defaults defaults
/dev/block/bootdevice/by-name/misc      /misc             emmc    defaults                                            defaults

从fstab中可以看到指定加密使用的是encryptable=footer可以看到默认是不加密的,可以自行到setting->security中执行加密操作,footer表明加密的信息存储在userdata的尾部。

了解到这个信息后,我们接着看加密失败的log,首先是kernel log,搜索encrypt有用的信息比较少

kernel log

[   28.077287] init: processing action (persist.sys.synaptics_dsx.qhd=false) from (/vendor/etc/init/hw/init.target.rc:231)
[   28.077936] init: processing action (encrypt) from (/system/etc/init/vdc.rc:8)
[   28.110739] init: starting service 'exec 4 (/system/bin/vdc --wait cryptfs enablecrypto inplace default noui)'...
[   28.115176] hyp_assign_table: Failed to assign memory protection, ret = -19
[   28.115222] memshare: hyp_assign_phys failed size=2097152 err=-19
[   28.144340] init: SVC_EXEC pid 412 (uid 0 gid 0+0 context default) started; waiting...
[   28.144630] init: Command 'exec - root -- /system/bin/vdc --wait cryptfs enablecrypto inplace default noui' action=encrypt (/system/etc/init/vdc.rc:10) returned 0 took 66ms.
[   28.293634] read descriptors

看kernel log,有用的信息比较少,不过有我们熟悉的流程信息。init最后也是通过vdc进程进行加密相关操作的。加密使用参数为exec 4 (/system/bin/vdc --wait cryptfs enablecrypto inplace default noui)。接下来再看看main log是否有可疑的信息

main log

01-03 01:55:28.954   279   429 E Cryptfs : Cannot load dm-crypt mapping table.
01-03 01:55:28.957  1208  1208 W PackageManager: Failed to parse /system/app/EasterEgg: Not a coreApp: /system/app/EasterEgg
01-03 01:55:28.964   279   429 I EncryptInplace: Encrypting ext4 filesystem in place...
01-03 01:55:28.964   279   429 I EncryptInplace: Encrypting group 0
01-03 01:55:28.992  1208  1208 W PackageManager: Failed to parse /system/app/ExactCalculator: Not a coreApp: /system/app/ExactCalculator
01-03 01:55:29.038  1208  1208 W PackageManager: Failed to parse /system/app/Exchange2: Not a coreApp: /system/app/Exchange2
01-03 01:55:29.042   279   429 E EncryptInplace: Error writing crypto_blkdev /dev/block/dm-0 for inplace encrypt
01-03 01:55:29.042   279   429 I EncryptInplace: Encrypted to sector 0
01-03 01:55:29.047   279   429 E EncryptInplace: Error encrypting groups
01-03 01:55:29.053  1208  1208 I PackageManager: /system/app/ExtShared changed; collecting certs
01-03 01:55:29.054   279   429 D EncryptInplace: cryptfs_enable_inplace_ext4()=-1
01-03 01:55:29.058   279   429 E f2fs_sparseblock: Not a valid F2FS super block. Magic:0x04e700 != 0xf2f52010
01-03 01:55:29.058   279   429 E f2fs_sparseblock: Failed to read superblock
01-03 01:55:29.058   279   429 E EncryptInplace: Failed to encrypt f2fs filesystem on /dev/block/bootdevice/by-name/userdata
01-03 01:55:29.058   279   429 I EncryptInplace: Encrypted to block -1
01-03 01:55:29.058   279   429 D EncryptInplace: cryptfs_enable_inplace_f2fs()=-1
01-03 01:55:29.058   279   429 E EncryptInplace: Encrypting filesystem in place...
01-03 01:55:29.058   279   429 E EncryptInplace: Cannot seek to previously encrypted point on /dev/block/dm-0
01-03 01:55:29.058   279   429 D EncryptInplace: cryptfs_enable_inplace_full()=-1

main log的有用信息就稍微多一些了,如上面列出的

  • load dm-crypt mapping table失败
  • 我们使用的是ext4格式,调用cryptfs_enable_inplace_ext4函数时的返回值是-1,代表失败了
  • 接下来的两种系统格式也都失败

通过这点信息,可以猜测下是不是因为dm-crypt mapping table加载失败,导致在调用cryptfs_enable_inplace_ext4函数时传入的参数有误导致。既然这样,我们先来看看为什么加载dm映射表的过程会出问题。FDE过程中,是在cryptfs_enable_internal中调用create_crypto_blk_dev来完成的。

cryptfs_enable_internal

int cryptfs_enable_internal(char *howarg, int crypt_type, const char *passwd,
                            int no_ui)
{
    ........... // 参数准备,没什么好看的

    // 获取加密的master key
    decrypt_master_key(passwd, decrypted_master_key, &crypt_ftr, 0, 0);
    // 好的,加载dm映射表在这里进行,下面重点关注这个
    create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev, crypto_blkdev,
                          CRYPTO_BLOCK_DEVICE);

    ........... // 反正重点关注create_crypto_blk_dev函数就是了
}

cryptfs_enable_internal –>create_crypto_blk_dev

static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr,
        const unsigned char *master_key, const char *real_blk_name,
        char *crypto_blk_name, const char *name) {

  ................ // 参数准备略过

  // 打开device-mapper驱动
  if ((fd = open("/dev/device-mapper", O_RDWR|O_CLOEXEC)) < 0 ) {
    SLOGE("Cannot open device-mapper\n");
    goto errout;
  }

  io = (struct dm_ioctl *) buffer;

  // 下面通过IOCTL初始化IOBUF,获取device-mapper的基本信息
  ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
  err = ioctl(fd, DM_DEV_CREATE, io);
  if (err) {
    SLOGE("Cannot create dm-crypt device %s: %s\n", name, strerror(errno));
    goto errout;
  }

  ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
  if (ioctl(fd, DM_DEV_STATUS, io)) {
    SLOGE("Cannot retrieve dm-crypt device status\n");
    goto errout;
  }
  minor = (io->dev & 0xff) | ((io->dev >> 12) & 0xfff00);
  snprintf(crypto_blk_name, MAXPATHLEN, "/dev/block/dm-%u", minor);

  extra_params = "";
  if (! get_dm_crypt_version(fd, name, version)) {
      /* Support for allow_discards was added in version 1.11.0 */
      if ((version[0] >= 2) ||
          ((version[0] == 1) && (version[1] >= 11))) {
          extra_params = "1 allow_discards";
          SLOGI("Enabling support for allow_discards in dmcrypt.\n");
      }
  }

  // 加载mapping table
  load_count = load_crypto_mapping_table(crypt_ftr, master_key, real_blk_name, name,
                                         fd, extra_params);
  if (load_count < 0) {
      // mapping table数量小于0出错
      SLOGE("Cannot load dm-crypt mapping table.\n");
      goto errout;
  } else if (load_count > 1) {
      SLOGI("Took %d tries to load dmcrypt table.\n", load_count);
  }

  /* Resume this device to activate it */
  ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);

  if (ioctl(fd, DM_DEV_SUSPEND, io)) {
    SLOGE("Cannot resume the dm-crypt device\n");
    goto errout;
  }

  /* We made it here with no errors.  Woot! */
  retval = 0;

errout:
  close(fd);   /* If fd is <0 from a failed open call, it's safe to just ignore the close error */

  return retval;
}

接着看load_crypto_mapping_table函数

create_crypto_blk_dev-->load_crypto_mapping_table

static int load_crypto_mapping_table(struct crypt_mnt_ftr *crypt_ftr,
        const unsigned char *master_key, const char *real_blk_name,
        const char *name, int fd, const char *extra_params) {

  ..................... // 参数初始化忽略

// 查看log,#ifdef为true
#ifdef CONFIG_HW_DISK_ENCRYPTION
  if(is_hw_disk_encryption((char*)crypt_ftr->crypto_type_name)) {
    strlcpy(tgt->target_type, "req-crypt",DM_MAX_TYPE_NAME);
    if (is_ice_enabled())
      convert_key_to_hex_ascii(master_key, sizeof(int), master_key_ascii);
    else
      convert_key_to_hex_ascii(master_key, crypt_ftr->keysize, master_key_ascii);
  }
  else {
    convert_key_to_hex_ascii(master_key, crypt_ftr->keysize, master_key_ascii);
    strlcpy(tgt->target_type, "crypt", DM_MAX_TYPE_NAME);
  }
  snprintf(crypt_params, sizeof(buffer) - buff_offset, "%s %s 0 %s 0 %s 0",
           crypt_ftr->crypto_type_name, master_key_ascii,
           real_blk_name, extra_params);

  SLOGI("target_type = %s", tgt->target_type);
  SLOGI("real_blk_name = %s, extra_params = %s", real_blk_name, extra_params);
#else
  SLOGE("CONFIG_HW_DISK_ENCRYPTION has not defined!");
  convert_key_to_hex_ascii(master_key, crypt_ftr->keysize, master_key_ascii);
  strlcpy(tgt->target_type, "crypt", DM_MAX_TYPE_NAME);
  snprintf(crypt_params, sizeof(buffer) - buff_offset, "%s %s 0 %s 0 %s",
           crypt_ftr->crypto_type_name, master_key_ascii, real_blk_name,
           extra_params);
#endif

  crypt_params += strlen(crypt_params) + 1;
  crypt_params = (char *) (((unsigned long)crypt_params + 7) & ~8); /* Align to an 8 byte boundary */
  tgt->next = crypt_params - buffer;

  // 尝试10次
  for (i = 0; i < TABLE_LOAD_RETRIES; i++) {
    if (! ioctl(fd, DM_TABLE_LOAD, io)) {
      break;
    }
    usleep(500000);
  }

  // 加载10次还是失败,return -1
  if (i == TABLE_LOAD_RETRIES) {
    /* We failed to load the table, return an error */
    return -1;
  } else {
    return i + 1;
  }
}

从main log中可以看到,走的是#ifdef CONFIG_HW_DISK_ENCRYPTION为true这一段

01-03 01:55:53.384   279   429 I Cryptfs : target_type = req-crypt
01-03 01:55:53.384   279   429 I Cryptfs : real_blk_name = /dev/block/bootdevice/by-name/userdata, extra_params = fde_enabled

下面重点来了:可以看到,这里device-mapper使用的加密插件是req-crypt,那么req-crypt是什么东西呢?其次,为什么CONFIG_HW_DISK_ENCRYPTION宏会被定义为true,这个宏看着像是使用硬件加密啊,8909能用?带着疑问继续分析

首先看看req-crypt这个加密插件,req-crypt是在kernel下的drivers/md目录下的dm-req-crypt.c文件

static struct target_type req_crypt_target = {
    .name   = "req-crypt",
    .version = {1, 0, 0},
    .module = THIS_MODULE,
    .ctr    = req_crypt_ctr,
    .dtr    = req_crypt_dtr,
    .map_rq = req_crypt_map,
    .rq_end_io = req_crypt_endio,
    .iterate_devices = req_crypt_iterate_devices,
};

然后在drivers/md下的Makefile文件中找到编译条件

obj-$(CONFIG_DM_ERA)        += dm-era.o
obj-$(CONFIG_DM_REQ_CRYPT)      += dm-req-crypt.o
obj-$(CONFIG_DM_ANDROID_VERITY) += dm-android-verity.o

但是这个CONFIG_DM_REQ_CRYPT宏在项目对应的msm8909_defconfig文件中没有找到定义,所以可以判断这个文件是不会编译进内核的。那加载映射表的时候,当然就找不到这个加密插件了。

其次就是CONFIG_HW_DISK_ENCRYPTION这个宏为什么会为true?因为在下面有一个判断的函数is_ice_enabled,所以估计这个硬件加密就是ICE(Inline Crypto Engine)。具体ICE是什么大家可以在kernel/documents下面找到msm_ice_driver.txt,里面会有详细的介绍。

is_ice_enabled

int is_ice_enabled(void)
{
  char prop_storage[PATH_MAX];
  int storage_type = 0;
  int fd;

  if (property_get("ro.boot.bootdevice", prop_storage, "")) {
    if (strstr(prop_storage, "ufs")) {
      // 所有的UFS设备都支持
      storage_type = QCOM_ICE_STORAGE_UFS;
    } else if (strstr(prop_storage, "sdhc")) {
      // sdhc设备需要判断/dev/icesdcc是否存在,不存在就不支持
      if (access("/dev/icesdcc", F_OK) != -1)
        storage_type = QCOM_ICE_STORAGE_SDCC;
    }
  }
  return storage_type;
}

博主这边使用的是EMMC,而且/dev/icesdcc也不存在。那么就暂且判断为不支持ICE了(这里存在另一个可能就是设备是支持ICE的,但是设备数没有配置好,这个哪个兄dei知道怎么判断不?请告知一声)。

如果不支持ICE,那就只能使用软件加密了,这个就比较影响性能了。要使用软件机密,需要解决的问题取消CONFIG_HW_DISK_ENCRYPTION宏的定义了。查看vold的Android.mk文件,有下面这一段玩意

ifeq ($(TARGET_HW_DISK_ENCRYPTION),true)
  common_c_includes += $(TARGET_CRYPTFS_HW_PATH)
  common_shared_libraries += libcryptfs_hw
  vold_cflags += -DCONFIG_HW_DISK_ENCRYPTION
endif

emmmmmm....,也就是说如果TARGET_HW_DISK_ENCRYPTION有定义为true,那在编译VOLD的时候就会加上CONFIG_HW_DISK_ENCRYPTION这个定义(这里吐槽下,坑爹的使用OpenGrok都没有办法找到这个Android.mk文件中的CONFIG_HW_DISK_ENCRYPTION字符串,只因为全面多了个D就不匹配了)。然后再到设备的BoardConfig.mk文件中看到定义:

# 使用的是QCOM不是AOSP
ifeq ($(TARGET_USES_AOSP), true)
.................
else
# SDClang configuration
SDCLANG := true
#SDCLANG_PATH :=prebuilts/clang/host/linux-x86/sdclang-4.0/bin
SDCLANG_LTO_DEFS := device/qcom/common/sdllvm-lto-defs.mk
# 这里默认设置为true的,我们改为false即可
TARGET_HW_DISK_ENCRYPTION := true
endif

然后编译试试看,能不能行了?Interesting................

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: