升级ubifs以支持 fscrypt

The Linux kernel configuration item CONFIG_UBIFS_FS_ENCRYPTION:

ubifs fscrypt的优势:

Enable encryption of UBIFS files and directories. This feature is similar to ecryptfs, but it is more memory efficient since it avoids caching the encrypted and decrypted pages in the page cache.

本来内核版本是4.9.68,ubifs不支持fscrypt,现在将升级到kernel 4.10.10以支持ubifs fscrypt

需要修改的地方:Some changes to UBIFS and fscrypt were required

struct ubifs_info {   //新增加两个bit field,sb占用的字节大小不变,所以可以向后兼容
    struct super_block *vfs_sb;

    ...

    unsigned int double_hash:1;
    unsigned int encrypted:1;

    ...

}

在读取超级块的函数中增加这两个bit field的解析:

int ubifs_read_superblock(struct ubifs_info *c)

{

    ...

    c->double_hash = !!(sup_flags & UBIFS_FLG_DOUBLE_HASH);
    c->encrypted = !!(sup_flags & UBIFS_FLG_ENCRYPTION);

}
int ubifs_enable_encryption(struct ubifs_info *c)
{
	int err;
	struct ubifs_sb_node *sup;

	if (c->encrypted)
		return 0;

	if (c->ro_mount || c->ro_media)
		return -EROFS;

	if (c->fmt_version < 5) {
		ubifs_err(c, "on-flash format version 5 is needed for encryption");
		return -EINVAL;
	}

	sup = ubifs_read_sb_node(c);
	if (IS_ERR(sup))
		return PTR_ERR(sup);

	sup->flags |= cpu_to_le32(UBIFS_FLG_ENCRYPTION);

	err = ubifs_write_sb_node(c, sup);
	if (!err)
		c->encrypted = 1;
	kfree(sup);

	return err;
}
//ioctl中新增命令FS_IOC_SET_ENCRYPTION_POLICY和FS_IOC_GET_ENCRYPTION_POLICY,使能ubifs目录加密

long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    ...
    case FS_IOC_SET_ENCRYPTION_POLICY: {
#ifdef CONFIG_UBIFS_FS_ENCRYPTION
		struct ubifs_info *c = inode->i_sb->s_fs_info;

		err = ubifs_enable_encryption(c);
		if (err)
			return err;

		return fscrypt_ioctl_set_policy(file, (const void __user *)arg);
#else
		return -EOPNOTSUPP;
#endif
	}
	case FS_IOC_GET_ENCRYPTION_POLICY: {
#ifdef CONFIG_UBIFS_FS_ENCRYPTION
		return fscrypt_ioctl_get_policy(file, (void __user *)arg);
#else
		return -EOPNOTSUPP;
#endif
}
//fs/crypto/policy.c
int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg) //设置目录加密策略
{
      struct inode *inode = file_inode(filp);
    ...
      if (!inode_has_encryption_context(inode)) {  //目录的xattr中是否加密相关的属性,存在返回TRUE
          if (!S_ISDIR(inode->i_mode))  //不是目录,返回错误
              ret = -EINVAL;
          else if (!inode->i_sb->s_cop->empty_dir) //目录不为空,返回错误
              ret = -EOPNOTSUPP;
          else if (!inode->i_sb->s_cop->empty_dir(inode)) //设置加密的目录不为空,则返回错误
              ret = -ENOTEMPTY;
          else
              ret = create_encryption_context_from_policy(inode,
                                      &policy); //将加密策略保存在目录对应的xattr中
      } else if (!is_encryption_context_consistent_with_policy(inode,
                                   &policy)) {//如果目录已经设置了加密策略,则检查是否一致
          printk(KERN_WARNING
                 "%s: Policy inconsistent with encryption context\n",
                 __func__);
          ret = -EINVAL;
      }
    ...
}
  static int inode_has_encryption_context(struct inode *inode)                                                                                                           
  {
    if (!inode->i_sb->s_cop->get_context)
          return 0;
    return (inode->i_sb->s_cop->get_context(inode, NULL, 0L) > 0); //大于0说明属性存在
  }
//cop是crypt operations的缩写,sb中s_cop是本来就有的字段,对于ubifs新增如下代码
static int ubifs_fill_super(struct super_block *sb, void *data, int silent)
{
    ...
    sb->s_cop = &ubifs_crypt_operations;
    ...
}
//fs/ubifs/crypto.c,这个文件为新增
  struct fscrypt_operations ubifs_crypt_operations = { 
      .flags          = FS_CFLG_OWN_PAGES,
      .get_context        = ubifs_crypt_get_context,
      .set_context        = ubifs_crypt_set_context,
      .is_encrypted       = __ubifs_crypt_is_encrypted,
      .empty_dir      = ubifs_crypt_empty_dir,
      .max_namelen        = ubifs_crypt_max_namelen,
      .key_prefix     = ubifs_key_prefix,
}; 

static inline bool __ubifs_crypt_is_encrypted(struct inode *inode)                                                                                                     
{
    struct ubifs_inode *ui = ubifs_inode(inode);
  
    return ui->flags & UBIFS_CRYPT_FL;  //inode中flags指定是否加密
}
static int ubifs_crypt_get_context(struct inode *inode, void *ctx, size_t len)
{
	return ubifs_xattr_get(inode, UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT,
			       ctx, len);
}
#define UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT "c"
//从目录的xattr中取名字为"c"的属性值
static bool ubifs_crypt_empty_dir(struct inode *inode)
{
	return ubifs_check_dir_empty(inode) == 0; //如果目录为空,则返回0
}
  static int create_encryption_context_from_policy(struct inode *inode,
                  const struct fscrypt_policy *policy)
  {
      struct fscrypt_context ctx;
      int res;
      ...
      ctx.format = FS_ENCRYPTION_CONTEXT_FORMAT_V1;
      memcpy(ctx.master_key_descriptor, policy->master_key_descriptor,
                      FS_KEY_DESCRIPTOR_SIZE);
  
      if (!fscrypt_valid_contents_enc_mode( //检查文件内容加密模式
                  policy->contents_encryption_mode)) {
          printk(KERN_WARNING
                 "%s: Invalid contents encryption mode %d\n", __func__,                                                                                                  
              policy->contents_encryption_mode);
          return -EINVAL;
      }
      if (!fscrypt_valid_filenames_enc_mode( //检查文件名字加密模式
                  policy->filenames_encryption_mode)) {
          printk(KERN_WARNING
              "%s: Invalid filenames encryption mode %d\n", __func__,
              policy->filenames_encryption_mode);
          return -EINVAL;
      }
  
      if (policy->flags & ~FS_POLICY_FLAGS_VALID) //检查flags的合法性
          return -EINVAL;
  
      ctx.contents_encryption_mode = policy->contents_encryption_mode;
      ctx.filenames_encryption_mode = policy->filenames_encryption_mode;
      ctx.flags = policy->flags;
      BUILD_BUG_ON(sizeof(ctx.nonce) != FS_KEY_DERIVATION_NONCE_SIZE);
      get_random_bytes(ctx.nonce, FS_KEY_DERIVATION_NONCE_SIZE); //nonce填充随机值,FS_KEY_DERIVATION_NONCE_SIZE = 16 byte
  
      return inode->i_sb->s_cop->set_context(inode, &ctx, sizeof(ctx), NULL);
  }
//目前文件内容加密模式仅仅支持FS_ENCRYPTION_MODE_AES_256_XTS
static inline bool fscrypt_valid_contents_enc_mode(u32 mode)                                                                                                           
  {
    return (mode == FS_ENCRYPTION_MODE_AES_256_XTS);
  }
//目前文件名字加密模式仅仅支持FS_ENCRYPTION_MODE_AES_256_CTS
static inline bool fscrypt_valid_filenames_enc_mode(u32 mode)
  {
    return (mode == FS_ENCRYPTION_MODE_AES_256_CTS);
  }

  static int ubifs_crypt_set_context(struct inode *inode, const void *ctx,
                   size_t len, void *fs_data)
  {
      return ubifs_xattr_set(inode, UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT,
                     ctx, len, 0); 
  }

static int ubifs_file_open(struct inode *inode, struct file *filp)
{
	int ret;
	struct dentry *dir;
	struct ubifs_info *c = inode->i_sb->s_fs_info;

	if (ubifs_crypt_is_encrypted(inode)) {//inode中flags指定是否加密
		ret = fscrypt_get_encryption_info(inode); //得到加密信息
		if (ret)
			return -EACCES;
		if (!fscrypt_has_encryption_key(inode))
			return -ENOKEY;
	}

	dir = dget_parent(file_dentry(filp));
	if (ubifs_crypt_is_encrypted(d_inode(dir)) &&
			!fscrypt_has_permitted_context(d_inode(dir), inode)) {
		ubifs_err(c, "Inconsistent encryption contexts: %lu/%lu",
			  (unsigned long) d_inode(dir)->i_ino,
			  (unsigned long) inode->i_ino);
		dput(dir);
		ubifs_ro_mode(c, -EPERM);
		return -EPERM;
	}
	dput(dir);

	return 0;
}

int fscrypt_get_encryption_info(struct inode *inode)
{
	struct fscrypt_info *crypt_info;
	struct fscrypt_context ctx;
	struct crypto_skcipher *ctfm;
	const char *cipher_str;
	int keysize;
	u8 *raw_key = NULL;
	int res;

	if (inode->i_crypt_info)
		return 0;

	res = fscrypt_initialize(inode->i_sb->s_cop->flags);
	if (res)
		return res;

	res = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));//从xattr得到加密策略
    ...
	crypt_info = kmem_cache_alloc(fscrypt_info_cachep, GFP_NOFS);
	if (!crypt_info)
		return -ENOMEM;

	crypt_info->ci_flags = ctx.flags;
	crypt_info->ci_data_mode = ctx.contents_encryption_mode;
	crypt_info->ci_filename_mode = ctx.filenames_encryption_mode;
	crypt_info->ci_ctfm = NULL;
	memcpy(crypt_info->ci_master_key, ctx.master_key_descriptor,
				sizeof(crypt_info->ci_master_key)); //注意这里的master_key_descriptor
    //根据加密mode,得到对应的加密器和key的大小
	res = determine_cipher_type(crypt_info, inode, &cipher_str, &keysize);
    ...
	raw_key = kmalloc(FS_MAX_KEY_SIZE, GFP_NOFS);
    ...
	res = validate_user_key(crypt_info, &ctx, raw_key, //根据前缀检查key的合法性,并将key放入raw_key中
			FS_KEY_DESC_PREFIX, FS_KEY_DESC_PREFIX_SIZE);
//#define FS_KEY_DESC_PREFIX             "fscrypt:"
	if (res && inode->i_sb->s_cop->key_prefix) {
		u8 *prefix = NULL;
		int prefix_size, res2;

		prefix_size = inode->i_sb->s_cop->key_prefix(inode, &prefix);
		res2 = validate_user_key(crypt_info, &ctx, raw_key,
							prefix, prefix_size);
        ...
	} else if (res) {
		goto out;
	}
got_key:
	ctfm = crypto_alloc_skcipher(cipher_str, 0, 0); //根据名字分配加密器
	crypt_info->ci_ctfm = ctfm;
	crypto_skcipher_clear_flags(ctfm, ~0);
	crypto_skcipher_set_flags(ctfm, CRYPTO_TFM_REQ_WEAK_KEY);
	res = crypto_skcipher_setkey(ctfm, raw_key, keysize); //设置key
	if (cmpxchg(&inode->i_crypt_info, NULL, crypt_info) == NULL) //保存到i_crypt_info中
		crypt_info = NULL;
out:
	if (res == -ENOKEY)
		res = 0;
	put_crypt_info(crypt_info);
	kzfree(raw_key);
	return res;
}
static int validate_user_key(struct fscrypt_info *crypt_info,
			struct fscrypt_context *ctx, u8 *raw_key,
			u8 *prefix, int prefix_size)
{
	u8 *full_key_descriptor;
	struct key *keyring_key;
	struct fscrypt_key *master_key;
	const struct user_key_payload *ukp;
	int full_key_len = prefix_size + (FS_KEY_DESCRIPTOR_SIZE * 2) + 1;
	int res;

	full_key_descriptor = kmalloc(full_key_len, GFP_NOFS);
	if (!full_key_descriptor)
		return -ENOMEM;

	memcpy(full_key_descriptor, prefix, prefix_size);
	sprintf(full_key_descriptor + prefix_size,
			"%*phN", FS_KEY_DESCRIPTOR_SIZE,
			ctx->master_key_descriptor); //构建full_key_descriptor 
	full_key_descriptor[full_key_len - 1] = '\0';
	keyring_key = request_key(&key_type_logon, full_key_descriptor, NULL);//根据full_key_descriptor 得到key
	kfree(full_key_descriptor);
	if (IS_ERR(keyring_key))
		return PTR_ERR(keyring_key);
	down_read(&keyring_key->sem);

	if (keyring_key->type != &key_type_logon) {
		printk_once(KERN_WARNING
				"%s: key type must be logon\n", __func__);
		res = -ENOKEY;
		goto out;
	}
	ukp = user_key_payload(keyring_key);
	if (ukp->datalen != sizeof(struct fscrypt_key)) {
		res = -EINVAL;
		goto out;
	}
	master_key = (struct fscrypt_key *)ukp->data;
	BUILD_BUG_ON(FS_AES_128_ECB_KEY_SIZE != FS_KEY_DERIVATION_NONCE_SIZE);

	if (master_key->size != FS_AES_256_XTS_KEY_SIZE) {
		printk_once(KERN_WARNING
				"%s: key size incorrect: %d\n",
				__func__, master_key->size);
		res = -ENOKEY;
		goto out;
	}
	res = derive_key_aes(ctx->nonce, master_key->raw, raw_key);//nonce作为数据,master_key->raw作为key,进行AES-128-ECB运算加密,加密后的数据存入raw_key
out:
	up_read(&keyring_key->sem);
	key_put(keyring_key);
	return res;
}

/*
 * user defined keys take an arbitrary string as the description and an
 * arbitrary blob of data as the payload
 */
struct key_type key_type_user = {
	.name			= "user",
	.preparse		= user_preparse,
	.free_preparse		= user_free_preparse,
	.instantiate		= generic_key_instantiate,
	.update			= user_update,
	.revoke			= user_revoke,
	.destroy		= user_destroy,
	.describe		= user_describe,
	.read			= user_read,
};

EXPORT_SYMBOL_GPL(key_type_user);

/*
 * This key type is essentially the same as key_type_user, but it does
 * not define a .read op. This is suitable for storing username and
 * password pairs in the keyring that you do not want to be readable
 * from userspace.
 */
struct key_type key_type_logon = {
	.name			= "logon",
	.preparse		= user_preparse,
	.free_preparse		= user_free_preparse,
	.instantiate		= generic_key_instantiate,
	.update			= user_update,
	.revoke			= user_revoke,
	.destroy		= user_destroy,
	.describe		= user_describe,
	.vet_description	= logon_vet_description,
};
EXPORT_SYMBOL_GPL(key_type_logon);

SYSCALL_DEFINE5(add_key, const char __user *, _type,
          const char __user *, _description,
          const void __user *, _payload,
          size_t, plen,
          key_serial_t, ringid)
//通过系统调用add_key,增加一个key,每个key包括type,_description和_payload,key_type_logon 和key_type_user 相似,只是从用户空间读取不出来key,request_key()根据_description得到对应的_payload。

static int read_block(struct inode *inode, void *addr, unsigned int block,
		      struct ubifs_data_node *dn)
{
    ...
	if (ubifs_crypt_is_encrypted(inode)) {
		err = ubifs_decrypt(inode, dn, &dlen, block);//dn中存放着读到的数据,进行解密
		if (err)
			goto dump;
	}
    ...
}

int ubifs_decrypt(const struct inode *inode, struct ubifs_data_node *dn,
		  unsigned int *out_len, int block)
{
    ...
	err = fscrypt_decrypt_page(inode, virt_to_page(&dn->data), dlen,
			offset_in_page(&dn->data), block);
    ...
	*out_len = clen;

	return 0;
}
  int fscrypt_decrypt_page(const struct inode *inode, struct page *page,                                                                                                 
              unsigned int len, unsigned int offs, u64 lblk_num)
  {
      return do_page_crypto(inode, FS_DECRYPT, lblk_num, page, page, len,
              offs, GFP_NOFS); //解密后的数据保存在同一个page中
  }
static int do_page_crypto(const struct inode *inode,
			fscrypt_direction_t rw, u64 lblk_num,
			struct page *src_page, struct page *dest_page,
			unsigned int len, unsigned int offs,
			gfp_t gfp_flags)
{
	struct {
		__le64 index;
		u8 padding[FS_XTS_TWEAK_SIZE - sizeof(__le64)];
	} xts_tweak;
	struct skcipher_request *req = NULL;
	DECLARE_FS_COMPLETION_RESULT(ecr);
	struct scatterlist dst, src;
	struct fscrypt_info *ci = inode->i_crypt_info;  //从inode从得到fscrypt_info
	struct crypto_skcipher *tfm = ci->ci_ctfm;
	int res = 0;

	BUG_ON(len == 0);

	req = skcipher_request_alloc(tfm, gfp_flags);
	if (!req) {
		printk_ratelimited(KERN_ERR
				"%s: crypto_request_alloc() failed\n",
				__func__);
		return -ENOMEM;
	}

	skcipher_request_set_callback(
		req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
		page_crypt_complete, &ecr);

	BUILD_BUG_ON(sizeof(xts_tweak) != FS_XTS_TWEAK_SIZE);
	xts_tweak.index = cpu_to_le64(lblk_num);
	memset(xts_tweak.padding, 0, sizeof(xts_tweak.padding));

	sg_init_table(&dst, 1);
	sg_set_page(&dst, dest_page, len, offs);
	sg_init_table(&src, 1);
	sg_set_page(&src, src_page, len, offs);
	skcipher_request_set_crypt(req, &src, &dst, len, &xts_tweak);
	if (rw == FS_DECRYPT)
		res = crypto_skcipher_decrypt(req);
	else
		res = crypto_skcipher_encrypt(req);
	if (res == -EINPROGRESS || res == -EBUSY) {
		BUG_ON(req->base.data != &ecr);
		wait_for_completion(&ecr.completion);
		res = ecr.res;
	}
	skcipher_request_free(req);
	if (res) {
		printk_ratelimited(KERN_ERR
			"%s: crypto_skcipher_encrypt() returned %d\n",
			__func__, res);
		return res;
	}
	return 0;
}

在已经设备了加密策略的空目录下创建文件会发生什么呢

/**
 * ubifs_new_inode - allocate new UBIFS inode object.
 * @c: UBIFS file-system description object
 * @dir: parent directory inode
 * @mode: inode mode flags
 *
 * This function finds an unused inode number, allocates new inode and
 * initializes it. Returns new inode in case of success and an error code in
 * case of failure.
 */
struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
			      umode_t mode)
{
	bool encrypted = false;

	if (ubifs_crypt_is_encrypted(dir)) { //如果父目录是加密的
		err = fscrypt_get_encryption_info(dir); //并且设备了加密策略
		if (err) {
			ubifs_err(c, "fscrypt_get_encryption_info failed: %i", err);
			return ERR_PTR(err);
		}

		if (!fscrypt_has_encryption_key(dir))
			return ERR_PTR(-EPERM);

		encrypted = true;
	}
	inode = new_inode(c->vfs_sb);
	ui = ubifs_inode(inode);
    ...
	if (encrypted) {
		err = fscrypt_inherit_context(dir, inode, &encrypted, true);//继承父目录的加密策略,存放在新创建文件的xattr中
		if (err) {
			ubifs_err(c, "fscrypt_inherit_context failed: %i", err);
			make_bad_inode(inode);
			iput(inode);
			return ERR_PTR(err);
		}
	}

	return inode;
}
static inline int fscrypt_has_encryption_key(const struct inode *inode)                                                                                                
{
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
    return (inode->i_crypt_info != NULL);
#else
    return 0;
#endif
}
/**
 * fscrypt_inherit_context() - Sets a child context from its parent
 * @parent: Parent inode from which the context is inherited.
 * @child:  Child inode that inherits the context from @parent.
 * @fs_data:  private data given by FS.
 * @preload:  preload child i_crypt_info
 *
 * Return: Zero on success, non-zero otherwise
 */
int fscrypt_inherit_context(struct inode *parent, struct inode *child,
						void *fs_data, bool preload)
{
	struct fscrypt_context ctx;
	struct fscrypt_info *ci;
	int res;

	if (!parent->i_sb->s_cop->set_context)
		return -EOPNOTSUPP;

	res = fscrypt_get_encryption_info(parent);
	if (res < 0)
		return res;

	ci = parent->i_crypt_info; //得到父目录的加密策略
	if (ci == NULL)
		return -ENOKEY;

	ctx.format = FS_ENCRYPTION_CONTEXT_FORMAT_V1;
	if (fscrypt_dummy_context_enabled(parent)) {
		ctx.contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS;
		ctx.filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
		ctx.flags = 0;
		memset(ctx.master_key_descriptor, 0x42, FS_KEY_DESCRIPTOR_SIZE);
		res = 0;
	} else {
		ctx.contents_encryption_mode = ci->ci_data_mode;
		ctx.filenames_encryption_mode = ci->ci_filename_mode;
		ctx.flags = ci->ci_flags;
		memcpy(ctx.master_key_descriptor, ci->ci_master_key,
				FS_KEY_DESCRIPTOR_SIZE);
	}
	get_random_bytes(ctx.nonce, FS_KEY_DERIVATION_NONCE_SIZE);//不同的nonce
	res = parent->i_sb->s_cop->set_context(child, &ctx,
						sizeof(ctx), fs_data); //保存到新创建文件的xattr中
	if (res)
		return res;
	return preload ? fscrypt_get_encryption_info(child): 0;
}

整个过程大概如下图所示:

为了安全起见,Master keys blob是加密的,由硬件解密后通过系统调用add_key()加载到内核User key中,同时将Master keys的描述符存放在ubifs对应目录的xattr中,在读文件的时候,如果是加密的,则通过xattr中的Master keys描述符取得Master keys,然后通过Master keys加密存放在xattr中的Nonce生成File key,这个File key就可以用来解码加密的文件名字和文件内容了。

注意Master keys必须要小心保管,一般都是加密存储,要不然ubifs fscrypt没有任何意义,Master keys加载到内核中后就没法从用户空间读取出来,只能在内核内部获取使用,所以还是安全的。

通过以上的代码分析,应该很清楚了,下面看看怎么使用:

./fscryptctl insert_key --ext4 < key.data这个动作实际上是通过系统调用add_key()增加key_type_logon的key,它的描述符是cd8c77009a9a3e6d

keyctl show 用于display增加到kernel中的key

./fscryptctl set_policy cd8c77009a9a3e6d /mnt/disks/encrypted/test 通过ubifs_ioctl()的FS_IOC_SET_ENCRYPTION_POLICY,cd8c77009a9a3e6d就是key的描述符,存放在中policy->master_key_descriptor,最后通过set_context存放在目录对应的xattr中。

参考资料:

https://elinux.org/images/f/f6/Slides_24-ubifs.pdf

https://elinux.org/images/9/97/Protecting_your_system.pdf

https://github.com/google/fscryptctl/

发布了85 篇原创文章 · 获赞 26 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/whuzm08/article/details/86526546