diff mbox series

[RFC,v11,14/19] ipe: add support for dm-verity as a trust provider

Message ID 1696457386-3010-15-git-send-email-wufan@linux.microsoft.com (mailing list archive)
State Handled Elsewhere
Delegated to: Paul Moore
Headers show
Series Integrity Policy Enforcement LSM (IPE) | expand

Commit Message

Fan Wu Oct. 4, 2023, 10:09 p.m. UTC
From: Deven Bowers <deven.desai@linux.microsoft.com>

Allows author of IPE policy to indicate trust for a singular dm-verity
volume, identified by roothash, through "dmverity_roothash" and all
signed dm-verity volumes, through "dmverity_signature".

Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
Signed-off-by: Fan Wu <wufan@linux.microsoft.com>
---
v2:
  + No Changes

v3:
  + No changes

v4:
  + No changes

v5:
  + No changes

v6:
  + Fix an improper cleanup that can result in
    a leak

v7:
  + Squash patch 08/12, 10/12 to [11/16]

v8:
  + Undo squash of 08/12, 10/12 - separating drivers/md/ from security/
    & block/
  + Use common-audit function for dmverity_signature.
  + Change implementation for storing the dm-verity digest to use the
    newly introduced dm_verity_digest structure introduced in patch
    14/20.

v9:
  + Adapt to the new parser

v10:
  + Select the Kconfig when all dependencies are enabled

v11:
  + No changes
---
 security/ipe/Kconfig         |  18 +++++
 security/ipe/Makefile        |   1 +
 security/ipe/audit.c         |  31 +++++++-
 security/ipe/digest.c        | 142 +++++++++++++++++++++++++++++++++++
 security/ipe/digest.h        |  26 +++++++
 security/ipe/eval.c          | 101 ++++++++++++++++++++++++-
 security/ipe/eval.h          |  13 ++++
 security/ipe/hooks.c         |  51 +++++++++++++
 security/ipe/hooks.h         |   8 ++
 security/ipe/ipe.c           |  15 ++++
 security/ipe/ipe.h           |   4 +
 security/ipe/policy.h        |   3 +
 security/ipe/policy_parser.c |  24 +++++-
 13 files changed, 433 insertions(+), 4 deletions(-)
 create mode 100644 security/ipe/digest.c
 create mode 100644 security/ipe/digest.h

Comments

Paul Moore Oct. 24, 2023, 3:52 a.m. UTC | #1
On Oct  4, 2023 Fan Wu <wufan@linux.microsoft.com> wrote:
> 
> Allows author of IPE policy to indicate trust for a singular dm-verity
> volume, identified by roothash, through "dmverity_roothash" and all
> signed dm-verity volumes, through "dmverity_signature".
> 
> Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
> Signed-off-by: Fan Wu <wufan@linux.microsoft.com>
> ---
> v2:
>   + No Changes
> 
> v3:
>   + No changes
> 
> v4:
>   + No changes
> 
> v5:
>   + No changes
> 
> v6:
>   + Fix an improper cleanup that can result in
>     a leak
> 
> v7:
>   + Squash patch 08/12, 10/12 to [11/16]
> 
> v8:
>   + Undo squash of 08/12, 10/12 - separating drivers/md/ from security/
>     & block/
>   + Use common-audit function for dmverity_signature.
>   + Change implementation for storing the dm-verity digest to use the
>     newly introduced dm_verity_digest structure introduced in patch
>     14/20.
> 
> v9:
>   + Adapt to the new parser
> 
> v10:
>   + Select the Kconfig when all dependencies are enabled
> 
> v11:
>   + No changes
> ---
>  security/ipe/Kconfig         |  18 +++++
>  security/ipe/Makefile        |   1 +
>  security/ipe/audit.c         |  31 +++++++-
>  security/ipe/digest.c        | 142 +++++++++++++++++++++++++++++++++++
>  security/ipe/digest.h        |  26 +++++++
>  security/ipe/eval.c          | 101 ++++++++++++++++++++++++-
>  security/ipe/eval.h          |  13 ++++
>  security/ipe/hooks.c         |  51 +++++++++++++
>  security/ipe/hooks.h         |   8 ++
>  security/ipe/ipe.c           |  15 ++++
>  security/ipe/ipe.h           |   4 +
>  security/ipe/policy.h        |   3 +
>  security/ipe/policy_parser.c |  24 +++++-
>  13 files changed, 433 insertions(+), 4 deletions(-)
>  create mode 100644 security/ipe/digest.c
>  create mode 100644 security/ipe/digest.h

...

> diff --git a/security/ipe/audit.c b/security/ipe/audit.c
> index 0dd5f10c318f..b5c58655ac74 100644
> --- a/security/ipe/audit.c
> +++ b/security/ipe/audit.c
> @@ -13,6 +13,7 @@
>  #include "hooks.h"
>  #include "policy.h"
>  #include "audit.h"
> +#include "digest.h"
>  
>  #define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY")
>  
> @@ -40,8 +41,29 @@ static const char *const audit_op_names[__IPE_OP_MAX] = {
>  static const char *const audit_prop_names[__IPE_PROP_MAX] = {
>  	"boot_verified=FALSE",
>  	"boot_verified=TRUE",
> +#ifdef CONFIG_IPE_PROP_DM_VERITY
> +	"dmverity_roothash=",
> +	"dmverity_signature=FALSE",
> +	"dmverity_signature=TRUE",
> +#endif /* CONFIG_IPE_PROP_DM_VERITY */
>  };
>  
> +#ifdef CONFIG_IPE_PROP_DM_VERITY
> +/**
> + * audit_dmv_roothash - audit a roothash of a dmverity volume.
> + * @ab: Supplies a pointer to the audit_buffer to append to.
> + * @r: Supplies a pointer to the digest structure.
> + */
> +static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
> +{
> +	ipe_digest_audit(ab, rh);
> +}
> +#else
> +static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
> +{
> +}
> +#endif /* CONFIG_IPE_PROP_DM_VERITY */

Since the "dmverity_roothash=" field name is going to be written to
the audit record regardless of CONFIG_IPE_PROP_DM_VERITY we should
ensure that it has a value of "?" instead of nothing.  To fix that
I would suggest something like this:

  #else
  static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
  {
    audit_log_format(ab, "?");
  }
  #endif /* CONFIG_IPE_PROP_DM_VERITY */

>  /**
>   * audit_rule - audit an IPE policy rule approximation.
>   * @ab: Supplies a pointer to the audit_buffer to append to.
> @@ -53,8 +75,13 @@ static void audit_rule(struct audit_buffer *ab, const struct ipe_rule *r)
>  
>  	audit_log_format(ab, "rule=\"op=%s ", audit_op_names[r->op]);
>  
> -	list_for_each_entry(ptr, &r->props, next)
> -		audit_log_format(ab, "%s ", audit_prop_names[ptr->type]);
> +	list_for_each_entry(ptr, &r->props, next) {
> +		audit_log_format(ab, "%s", audit_prop_names[ptr->type]);
> +		if (ptr->type == IPE_PROP_DMV_ROOTHASH)
> +			audit_dmv_roothash(ab, ptr->value);

If you wanted to avoid the roothash change above, you could change the
code here so it didn't always write "dmverity_roothash=" into the audit
record.

> +		audit_log_format(ab, " ");
> +	}
>  
>  	audit_log_format(ab, "action=%s\"", ACTSTR(r->action));
>  }
> diff --git a/security/ipe/digest.c b/security/ipe/digest.c
> new file mode 100644
> index 000000000000..7a42ca71880c
> --- /dev/null
> +++ b/security/ipe/digest.c
> @@ -0,0 +1,142 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) Microsoft Corporation. All rights reserved.
> + */
> +
> +#include "digest.h"
> +
> +/**
> + * ipe_digest_parse - parse a digest in IPE's policy.
> + * @valstr: Supplies the string parsed from the policy.
> + * @value: Supplies a pointer to be populated with the result.
> + *
> + * Digests in IPE are defined in a standard way:
> + *	<alg_name>:<hex>
> + *
> + * Use this function to create a property to parse the digest
> + * consistently. The parsed digest will be saved in @value in IPE's
> + * policy.
> + *
> + * Return:
> + * * 0	- OK
> + * * !0	- Error
> + */
> +int ipe_digest_parse(const char *valstr, void **value)

Why is @value void?  You should make it a digest_info type or simply
skip the second parameter and return a digest_info pointer.

> +{
> +	char *sep, *raw_digest;
> +	size_t raw_digest_len;
> +	int rc = 0;
> +	u8 *digest = NULL;
> +	struct digest_info *info = NULL;
> +
> +	info = kzalloc(sizeof(*info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	sep = strchr(valstr, ':');
> +	if (!sep) {
> +		rc = -EBADMSG;
> +		goto err;
> +	}
> +
> +	info->alg = kstrndup(valstr, sep - valstr, GFP_KERNEL);
> +	if (!info->alg) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	raw_digest = sep + 1;
> +	raw_digest_len = strlen(raw_digest);
> +	info->raw_digest = kstrndup(raw_digest, raw_digest_len, GFP_KERNEL);

Since you're running a strlen() over @raw_digest, you might as well
just use kstrdup() instead of kstrndup().

> +	if (!info->raw_digest) {
> +		rc = -ENOMEM;
> +		goto err_free_alg;
> +	}
> +
> +	info->digest_len = (raw_digest_len + 1) / 2;
> +	digest = kzalloc(info->digest_len, GFP_KERNEL);
> +	if (!digest) {
> +		rc = -ENOMEM;
> +		goto err_free_raw;
> +	}
> +
> +	rc = hex2bin(digest, raw_digest, info->digest_len);
> +	if (rc < 0) {
> +		rc = -EINVAL;
> +		goto err_free_raw;
> +	}
> +
> +	info->digest = digest;
> +	*value = info;
> +	return 0;
> +
> +err_free_raw:
> +	kfree(info->raw_digest);
> +err_free_alg:
> +	kfree(info->alg);
> +err:
> +	kfree(digest);
> +	kfree(info);
> +	return rc;
> +}
> +
> +/**
> + * ipe_digest_eval - evaluate an IPE digest against another digest.
> + * @expect: Supplies the policy-provided digest value.
> + * @digest: Supplies the digest to compare against the policy digest value.
> + * @digest_len: The length of @digest.
> + * @alg: Supplies the name of the algorithm used to calculated @digest.
> + *
> + * Return:
> + * * true	- digests match
> + * * false	- digests do not match
> + */
> +bool ipe_digest_eval(const void *expect, const u8 *digest, size_t digest_len,
> +		     const char *alg)

Similar to the above, why not make the @expect parameter a digest_info
type?  Also, why not pass a second digest_info parameter instead of
a separate @digest, @digest_len, and @alg?

> +{
> +	const struct digest_info *info = (struct digest_info *)expect;
> +
> +	return (digest_len == info->digest_len) && !strcmp(alg, info->alg) &&
> +	       (!memcmp(info->digest, digest, info->digest_len));
> +}
> +
> +/**
> + * ipe_digest_free - free an IPE digest.
> + * @value: Supplies a pointer the policy-provided digest value to free.
> + */
> +void ipe_digest_free(void **value)

Another digest_info parameter/type issue.

> +{
> +	struct digest_info *info = (struct digest_info *)(*value);
> +
> +	if (IS_ERR_OR_NULL(info))
> +		return;
> +
> +	kfree(info->alg);
> +	kfree(info->raw_digest);
> +	kfree(info->digest);
> +	kfree(info);
> +}
> +
> +/**
> + * ipe_digest_audit - audit a digest that was sourced from IPE's policy.
> + * @ab: Supplies the audit_buffer to append the formatted result.
> + * @val: Supplies a pointer to source the audit record from.
> + *
> + * Digests in IPE are defined in a standard way:
> + *	<alg_name>:<hex>
> + *
> + * Use this function to create a property to audit the digest
> + * consistently.
> + *
> + * Return:
> + * 0 - OK
> + * !0 - Error
> + */
> +void ipe_digest_audit(struct audit_buffer *ab, const void *val)

Another digest_info parameter/type issue.

> +{
> +	const struct digest_info *info = (struct digest_info *)val;
> +
> +	audit_log_untrustedstring(ab, info->alg);
> +	audit_log_format(ab, ":");
> +	audit_log_untrustedstring(ab, info->raw_digest);
> +}

...

> diff --git a/security/ipe/eval.c b/security/ipe/eval.c
> index 78c54ff1fdd3..82ad48d7aa3d 100644
> --- a/security/ipe/eval.c
> +++ b/security/ipe/eval.c
> @@ -69,15 +88,89 @@ void build_eval_ctx(struct ipe_eval_ctx *ctx,
>  		    const struct file *file,
>  		    enum ipe_op_type op)
>  {
> +	struct inode *ino = NULL;
> +
>  	if (op == IPE_OP_EXEC && file)
>  		pin_sb(FILE_SUPERBLOCK(file));
>  
>  	ctx->file = file;
>  	ctx->op = op;
>  
> -	if (file)
> +	if (file) {
>  		ctx->from_init_sb = from_pinned(FILE_SUPERBLOCK(file));
> +		ino = d_real_inode(file->f_path.dentry);
> +		build_ipe_bdev_ctx(ctx, ino);

You don't need @ino.

  build_ipe_bdev_ctx(ctx, d_real_inode(file->f_path.dentry));

> +	}
> +}
> +
> +#ifdef CONFIG_IPE_PROP_DM_VERITY
> +/**
> + * evaluate_dmv_roothash - Evaluate @ctx against a dmv roothash property.
> + * @ctx: Supplies a pointer to the context being evaluated.
> + * @p: Supplies a pointer to the property being evaluated.
> + *
> + * Return:
> + * * true	- The current @ctx match the @p
> + * * false	- The current @ctx doesn't match the @p
> + */
> +static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
> +				  struct ipe_prop *p)
> +{
> +	return !!ctx->ipe_bdev &&
> +	       ipe_digest_eval(p->value,
> +			       ctx->ipe_bdev->digest,
> +			       ctx->ipe_bdev->digest_len,
> +			       ctx->ipe_bdev->digest_algo);

Building on my comments above in ipe_digest_eval(), if you convert it
to use digest_info structs this is simplified:

  ipe_digest_eval(p->vlaue, ctx->ipe_bdev)

> +}
> +
> +/**
> + * evaluate_dmv_sig_false: Analyze @ctx against a dmv sig false property.
> + * @ctx: Supplies a pointer to the context being evaluated.
> + * @p: Supplies a pointer to the property being evaluated.
> + *
> + * Return:
> + * * true	- The current @ctx match the @p
> + * * false	- The current @ctx doesn't match the @p
> + */
> +static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx,
> +				   struct ipe_prop *p)
> +{
> +	return !ctx->ipe_bdev || (!ctx->ipe_bdev->dm_verity_signed);
> +}
> +
> +/**
> + * evaluate_dmv_sig_true: Analyze @ctx against a dmv sig true property.
> + * @ctx: Supplies a pointer to the context being evaluated.
> + * @p: Supplies a pointer to the property being evaluated.
> + *
> + * Return:
> + * * true	- The current @ctx match the @p
> + * * false	- The current @ctx doesn't match the @p
> + */
> +static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx,
> +				  struct ipe_prop *p)
> +{
> +	return ctx->ipe_bdev && (!!ctx->ipe_bdev->dm_verity_signed);
> +}

Do you need both evaluate_dmv_sig_true() and evaluate_dmv_sig_false()?
If yes, you should make one call the other and return the inverse to
help ensure there are no oddities.

> diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
> index e9386762a597..8b8031e66f36 100644
> --- a/security/ipe/hooks.c
> +++ b/security/ipe/hooks.c
> @@ -193,3 +196,51 @@ void ipe_sb_free_security(struct super_block *mnt_sb)
>  {
>  	ipe_invalidate_pinned_sb(mnt_sb);
>  }
> +
> +#ifdef CONFIG_IPE_PROP_DM_VERITY
> +/**
> + * ipe_bdev_free_security - free IPE's LSM blob of block_devices.
> + * @bdev: Supplies a pointer to a block_device that contains the structure
> + *	  to free.
> + */
> +void ipe_bdev_free_security(struct block_device *bdev)
> +{
> +	struct ipe_bdev *blob = ipe_bdev(bdev);
> +
> +	kfree(blob->digest);
> +	kfree(blob->digest_algo);
> +}
> +
> +/**
> + * ipe_bdev_setsecurity - save data from a bdev to IPE's LSM blob.
> + * @bdev: Supplies a pointer to a block_device that contains the LSM blob.
> + * @key: Supplies the string key that uniquely identifies the value.
> + * @value: Supplies the value to store.
> + * @len: The length of @value.
> + */
> +int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
> +			 const void *value, size_t len)
> +{
> +	struct ipe_bdev *blob = ipe_bdev(bdev);
> +
> +	if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) {
> +		const struct dm_verity_digest *digest = value;
> +
> +		blob->digest = kmemdup(digest->digest, digest->digest_len, GFP_KERNEL);
> +		if (!blob->digest)
> +			return -ENOMEM;
> +
> +		blob->digest_algo = kstrdup_const(digest->algo, GFP_KERNEL);
> +		if (!blob->digest_algo)
> +			return -ENOMEM;

You need to cleanup @blob on error so you don't leak ipe_bdev::digest.

> +		blob->digest_len = digest->digest_len;
> +		return 0;
> +	} else if (!strcmp(key, DM_VERITY_SIGNATURE_SEC_NAME)) {
> +		blob->dm_verity_signed = true;
> +		return 0;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +#endif /* CONFIG_IPE_PROP_DM_VERITY */

--
paul-moore.com
Fan Wu Nov. 2, 2023, 10:40 p.m. UTC | #2
On 10/23/2023 8:52 PM, Paul Moore wrote:
> On Oct  4, 2023 Fan Wu <wufan@linux.microsoft.com> wrote:
>>
>> Allows author of IPE policy to indicate trust for a singular dm-verity
>> volume, identified by roothash, through "dmverity_roothash" and all
>> signed dm-verity volumes, through "dmverity_signature".
>>
>> Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
>> Signed-off-by: Fan Wu <wufan@linux.microsoft.com>
>> ---
...
>>   security/ipe/Kconfig         |  18 +++++
>>   security/ipe/Makefile        |   1 +
>>   security/ipe/audit.c         |  31 +++++++-
>>   security/ipe/digest.c        | 142 +++++++++++++++++++++++++++++++++++
>>   security/ipe/digest.h        |  26 +++++++
>>   security/ipe/eval.c          | 101 ++++++++++++++++++++++++-
>>   security/ipe/eval.h          |  13 ++++
>>   security/ipe/hooks.c         |  51 +++++++++++++
>>   security/ipe/hooks.h         |   8 ++
>>   security/ipe/ipe.c           |  15 ++++
>>   security/ipe/ipe.h           |   4 +
>>   security/ipe/policy.h        |   3 +
>>   security/ipe/policy_parser.c |  24 +++++-
>>   13 files changed, 433 insertions(+), 4 deletions(-)
>>   create mode 100644 security/ipe/digest.c
>>   create mode 100644 security/ipe/digest.h
> 
> ...
> 
>> diff --git a/security/ipe/audit.c b/security/ipe/audit.c
>> index 0dd5f10c318f..b5c58655ac74 100644
>> --- a/security/ipe/audit.c
>> +++ b/security/ipe/audit.c
>> @@ -13,6 +13,7 @@
>>   #include "hooks.h"
>>   #include "policy.h"
>>   #include "audit.h"
>> +#include "digest.h"
>>   
>>   #define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY")
>>   
>> @@ -40,8 +41,29 @@ static const char *const audit_op_names[__IPE_OP_MAX] = {
>>   static const char *const audit_prop_names[__IPE_PROP_MAX] = {
>>   	"boot_verified=FALSE",
>>   	"boot_verified=TRUE",
>> +#ifdef CONFIG_IPE_PROP_DM_VERITY
>> +	"dmverity_roothash=",
>> +	"dmverity_signature=FALSE",
>> +	"dmverity_signature=TRUE",
>> +#endif /* CONFIG_IPE_PROP_DM_VERITY */
>>   };
>>   
>> +#ifdef CONFIG_IPE_PROP_DM_VERITY
>> +/**
>> + * audit_dmv_roothash - audit a roothash of a dmverity volume.
>> + * @ab: Supplies a pointer to the audit_buffer to append to.
>> + * @r: Supplies a pointer to the digest structure.
>> + */
>> +static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
>> +{
>> +	ipe_digest_audit(ab, rh);
>> +}
>> +#else
>> +static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
>> +{
>> +}
>> +#endif /* CONFIG_IPE_PROP_DM_VERITY */
> 
> Since the "dmverity_roothash=" field name is going to be written to
> the audit record regardless of CONFIG_IPE_PROP_DM_VERITY we should
> ensure that it has a value of "?" instead of nothing.  To fix that
> I would suggest something like this:
> 
>    #else
>    static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
>    {
>      audit_log_format(ab, "?");
>    }
>    #endif /* CONFIG_IPE_PROP_DM_VERITY */
> 
>>   /**
>>    * audit_rule - audit an IPE policy rule approximation.
>>    * @ab: Supplies a pointer to the audit_buffer to append to.
>> @@ -53,8 +75,13 @@ static void audit_rule(struct audit_buffer *ab, const struct ipe_rule *r)
>>   
>>   	audit_log_format(ab, "rule=\"op=%s ", audit_op_names[r->op]);
>>   
>> -	list_for_each_entry(ptr, &r->props, next)
>> -		audit_log_format(ab, "%s ", audit_prop_names[ptr->type]);
>> +	list_for_each_entry(ptr, &r->props, next) {
>> +		audit_log_format(ab, "%s", audit_prop_names[ptr->type]);
>> +		if (ptr->type == IPE_PROP_DMV_ROOTHASH)
>> +			audit_dmv_roothash(ab, ptr->value);
> 
> If you wanted to avoid the roothash change above, you could change the
> code here so it didn't always write "dmverity_roothash=" into the audit
> record.
> 
Yes this sounds more reasonable to me, I will change this part to a 
switch case statement to avoid unnessary logging.

>> +		audit_log_format(ab, " ");
>> +	}
>>   
>>   	audit_log_format(ab, "action=%s\"", ACTSTR(r->action));
>>   }
>> diff --git a/security/ipe/digest.c b/security/ipe/digest.c
>> new file mode 100644
>> index 000000000000..7a42ca71880c
>> --- /dev/null
>> +++ b/security/ipe/digest.c
>> @@ -0,0 +1,142 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) Microsoft Corporation. All rights reserved.
>> + */
>> +
>> +#include "digest.h"
>> +
>> +/**
>> + * ipe_digest_parse - parse a digest in IPE's policy.
>> + * @valstr: Supplies the string parsed from the policy.
>> + * @value: Supplies a pointer to be populated with the result.
>> + *
>> + * Digests in IPE are defined in a standard way:
>> + *	<alg_name>:<hex>
>> + *
>> + * Use this function to create a property to parse the digest
>> + * consistently. The parsed digest will be saved in @value in IPE's
>> + * policy.
>> + *
>> + * Return:
>> + * * 0	- OK
>> + * * !0	- Error
>> + */
>> +int ipe_digest_parse(const char *valstr, void **value)
> 
> Why is @value void?  You should make it a digest_info type or simply
> skip the second parameter and return a digest_info pointer.
>
Yes it should better just return a digest_info type pointer. I will 
update this part.

>> +{
>> +	char *sep, *raw_digest;
>> +	size_t raw_digest_len;
>> +	int rc = 0;
>> +	u8 *digest = NULL;
>> +	struct digest_info *info = NULL;
>> +
>> +	info = kzalloc(sizeof(*info), GFP_KERNEL);
>> +	if (!info)
>> +		return -ENOMEM;
>> +
>> +	sep = strchr(valstr, ':');
>> +	if (!sep) {
>> +		rc = -EBADMSG;
>> +		goto err;
>> +	}
>> +
>> +	info->alg = kstrndup(valstr, sep - valstr, GFP_KERNEL);
>> +	if (!info->alg) {
>> +		rc = -ENOMEM;
>> +		goto err;
>> +	}
>> +
>> +	raw_digest = sep + 1;
>> +	raw_digest_len = strlen(raw_digest);
>> +	info->raw_digest = kstrndup(raw_digest, raw_digest_len, GFP_KERNEL);
> 
> Since you're running a strlen() over @raw_digest, you might as well
> just use kstrdup() instead of kstrndup().
>
Thanks I will update this part.

>> +	if (!info->raw_digest) {
>> +		rc = -ENOMEM;
>> +		goto err_free_alg;
>> +	}
>> +
>> +	info->digest_len = (raw_digest_len + 1) / 2;
>> +	digest = kzalloc(info->digest_len, GFP_KERNEL);
>> +	if (!digest) {
>> +		rc = -ENOMEM;
>> +		goto err_free_raw;
>> +	}
>> +
>> +	rc = hex2bin(digest, raw_digest, info->digest_len);
>> +	if (rc < 0) {
>> +		rc = -EINVAL;
>> +		goto err_free_raw;
>> +	}
>> +
>> +	info->digest = digest;
>> +	*value = info;
>> +	return 0;
>> +
>> +err_free_raw:
>> +	kfree(info->raw_digest);
>> +err_free_alg:
>> +	kfree(info->alg);
>> +err:
>> +	kfree(digest);
>> +	kfree(info);
>> +	return rc;
>> +}
>> +
>> +/**
>> + * ipe_digest_eval - evaluate an IPE digest against another digest.
>> + * @expect: Supplies the policy-provided digest value.
>> + * @digest: Supplies the digest to compare against the policy digest value.
>> + * @digest_len: The length of @digest.
>> + * @alg: Supplies the name of the algorithm used to calculated @digest.
>> + *
>> + * Return:
>> + * * true	- digests match
>> + * * false	- digests do not match
>> + */
>> +bool ipe_digest_eval(const void *expect, const u8 *digest, size_t digest_len,
>> +		     const char *alg)
> 
> Similar to the above, why not make the @expect parameter a digest_info
> type?  Also, why not pass a second digest_info parameter instead of
> a separate @digest, @digest_len, and @alg?
> 
The digest_info was inteneded to be used for policy only, which also 
contains a raw string of digest in the policy that can be used during 
audit. After second thought I start to think the raw_digest seems 
unnecessary and I can refactor them all to use digest_info instead.

>> +{
>> +	const struct digest_info *info = (struct digest_info *)expect;
>> +
>> +	return (digest_len == info->digest_len) && !strcmp(alg, info->alg) &&
>> +	       (!memcmp(info->digest, digest, info->digest_len));
>> +}
>> +
>> +/**
>> + * ipe_digest_free - free an IPE digest.
>> + * @value: Supplies a pointer the policy-provided digest value to free.
>> + */
>> +void ipe_digest_free(void **value)
> 
> Another digest_info parameter/type issue.
> 
>> +{
>> +	struct digest_info *info = (struct digest_info *)(*value);
>> +
>> +	if (IS_ERR_OR_NULL(info))
>> +		return;
>> +
>> +	kfree(info->alg);
>> +	kfree(info->raw_digest);
>> +	kfree(info->digest);
>> +	kfree(info);
>> +}
>> +
>> +/**
>> + * ipe_digest_audit - audit a digest that was sourced from IPE's policy.
>> + * @ab: Supplies the audit_buffer to append the formatted result.
>> + * @val: Supplies a pointer to source the audit record from.
>> + *
>> + * Digests in IPE are defined in a standard way:
>> + *	<alg_name>:<hex>
>> + *
>> + * Use this function to create a property to audit the digest
>> + * consistently.
>> + *
>> + * Return:
>> + * 0 - OK
>> + * !0 - Error
>> + */
>> +void ipe_digest_audit(struct audit_buffer *ab, const void *val)
> 
> Another digest_info parameter/type issue.
> 
>> +{
>> +	const struct digest_info *info = (struct digest_info *)val;
>> +
>> +	audit_log_untrustedstring(ab, info->alg);
>> +	audit_log_format(ab, ":");
>> +	audit_log_untrustedstring(ab, info->raw_digest);
>> +}
> 
> ...
> 
>> diff --git a/security/ipe/eval.c b/security/ipe/eval.c
>> index 78c54ff1fdd3..82ad48d7aa3d 100644
>> --- a/security/ipe/eval.c
>> +++ b/security/ipe/eval.c
>> @@ -69,15 +88,89 @@ void build_eval_ctx(struct ipe_eval_ctx *ctx,
>>   		    const struct file *file,
>>   		    enum ipe_op_type op)
>>   {
>> +	struct inode *ino = NULL;
>> +
>>   	if (op == IPE_OP_EXEC && file)
>>   		pin_sb(FILE_SUPERBLOCK(file));
>>   
>>   	ctx->file = file;
>>   	ctx->op = op;
>>   
>> -	if (file)
>> +	if (file) {
>>   		ctx->from_init_sb = from_pinned(FILE_SUPERBLOCK(file));
>> +		ino = d_real_inode(file->f_path.dentry);
>> +		build_ipe_bdev_ctx(ctx, ino);
> 
> You don't need @ino.
> 
>    build_ipe_bdev_ctx(ctx, d_real_inode(file->f_path.dentry));
> 
This variable will make sense in the fsverity patch, I do agree it 
doesn't make sense for this one. Will remove it.

>> +	}
>> +}
>> +
>> +#ifdef CONFIG_IPE_PROP_DM_VERITY
>> +/**
>> + * evaluate_dmv_roothash - Evaluate @ctx against a dmv roothash property.
>> + * @ctx: Supplies a pointer to the context being evaluated.
>> + * @p: Supplies a pointer to the property being evaluated.
>> + *
>> + * Return:
>> + * * true	- The current @ctx match the @p
>> + * * false	- The current @ctx doesn't match the @p
>> + */
>> +static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
>> +				  struct ipe_prop *p)
>> +{
>> +	return !!ctx->ipe_bdev &&
>> +	       ipe_digest_eval(p->value,
>> +			       ctx->ipe_bdev->digest,
>> +			       ctx->ipe_bdev->digest_len,
>> +			       ctx->ipe_bdev->digest_algo);
> 
> Building on my comments above in ipe_digest_eval(), if you convert it
> to use digest_info structs this is simplified:
> 
>    ipe_digest_eval(p->vlaue, ctx->ipe_bdev)
> 
Yep but the seucrity blob also contains signature information. I will 
make it like ipe_digest_eval(p->vlaue, ctx->ipe_bdev->dmv_digest_info)

>> +}
>> +
>> +/**
>> + * evaluate_dmv_sig_false: Analyze @ctx against a dmv sig false property.
>> + * @ctx: Supplies a pointer to the context being evaluated.
>> + * @p: Supplies a pointer to the property being evaluated.
>> + *
>> + * Return:
>> + * * true	- The current @ctx match the @p
>> + * * false	- The current @ctx doesn't match the @p
>> + */
>> +static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx,
>> +				   struct ipe_prop *p)
>> +{
>> +	return !ctx->ipe_bdev || (!ctx->ipe_bdev->dm_verity_signed);
>> +}
>> +
>> +/**
>> + * evaluate_dmv_sig_true: Analyze @ctx against a dmv sig true property.
>> + * @ctx: Supplies a pointer to the context being evaluated.
>> + * @p: Supplies a pointer to the property being evaluated.
>> + *
>> + * Return:
>> + * * true	- The current @ctx match the @p
>> + * * false	- The current @ctx doesn't match the @p
>> + */
>> +static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx,
>> +				  struct ipe_prop *p)
>> +{
>> +	return ctx->ipe_bdev && (!!ctx->ipe_bdev->dm_verity_signed);
>> +}
> 
> Do you need both evaluate_dmv_sig_true() and evaluate_dmv_sig_false()?
> If yes, you should make one call the other and return the inverse to
> help ensure there are no oddities.
>
Yes this code is redundant. I also found the ipe_prop *p variable is 
necssary. Will clean them up in the next version.

>> diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
>> index e9386762a597..8b8031e66f36 100644
>> --- a/security/ipe/hooks.c
>> +++ b/security/ipe/hooks.c
>> @@ -193,3 +196,51 @@ void ipe_sb_free_security(struct super_block *mnt_sb)
>>   {
>>   	ipe_invalidate_pinned_sb(mnt_sb);
>>   }
>> +
>> +#ifdef CONFIG_IPE_PROP_DM_VERITY
>> +/**
>> + * ipe_bdev_free_security - free IPE's LSM blob of block_devices.
>> + * @bdev: Supplies a pointer to a block_device that contains the structure
>> + *	  to free.
>> + */
>> +void ipe_bdev_free_security(struct block_device *bdev)
>> +{
>> +	struct ipe_bdev *blob = ipe_bdev(bdev);
>> +
>> +	kfree(blob->digest);
>> +	kfree(blob->digest_algo);
>> +}
>> +
>> +/**
>> + * ipe_bdev_setsecurity - save data from a bdev to IPE's LSM blob.
>> + * @bdev: Supplies a pointer to a block_device that contains the LSM blob.
>> + * @key: Supplies the string key that uniquely identifies the value.
>> + * @value: Supplies the value to store.
>> + * @len: The length of @value.
>> + */
>> +int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
>> +			 const void *value, size_t len)
>> +{
>> +	struct ipe_bdev *blob = ipe_bdev(bdev);
>> +
>> +	if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) {
>> +		const struct dm_verity_digest *digest = value;
>> +
>> +		blob->digest = kmemdup(digest->digest, digest->digest_len, GFP_KERNEL);
>> +		if (!blob->digest)
>> +			return -ENOMEM;
>> +
>> +		blob->digest_algo = kstrdup_const(digest->algo, GFP_KERNEL);
>> +		if (!blob->digest_algo)
>> +			return -ENOMEM;
> 
> You need to cleanup @blob on error so you don't leak ipe_bdev::digest.
>
Thanks for catching that. I will fix this.

-Fan
>> +		blob->digest_len = digest->digest_len;
>> +		return 0;
>> +	} else if (!strcmp(key, DM_VERITY_SIGNATURE_SEC_NAME)) {
>> +		blob->dm_verity_signed = true;
>> +		return 0;
>> +	}
>> +
>> +	return -EOPNOTSUPP;
>> +}
>> +#endif /* CONFIG_IPE_PROP_DM_VERITY */
> 
> --
> paul-moore.com
diff mbox series

Patch

diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index ac4d558e69d5..7afb1ce0cb99 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -8,6 +8,7 @@  menuconfig SECURITY_IPE
 	depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL
 	select PKCS7_MESSAGE_PARSER
 	select SYSTEM_DATA_VERIFICATION
+	select IPE_PROP_DM_VERITY if DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG
 	help
 	  This option enables the Integrity Policy Enforcement LSM
 	  allowing users to define a policy to enforce a trust-based access
@@ -15,3 +16,20 @@  menuconfig SECURITY_IPE
 	  admins to reconfigure trust requirements on the fly.
 
 	  If unsure, answer N.
+
+if SECURITY_IPE
+menu "IPE Trust Providers"
+
+config IPE_PROP_DM_VERITY
+	bool "Enable support for dm-verity volumes"
+	depends on DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG
+	help
+	  This option enables the properties 'dmverity_signature' and
+	  'dmverity_roothash' in IPE policy. These properties evaluates
+	  to TRUE when a file is evaluated against a dm-verity volume
+	  that was mounted with a signed root-hash or the volume's
+	  root hash matches the supplied value in the policy.
+
+endmenu
+
+endif
diff --git a/security/ipe/Makefile b/security/ipe/Makefile
index 2279eaa3cea3..66de53687d11 100644
--- a/security/ipe/Makefile
+++ b/security/ipe/Makefile
@@ -6,6 +6,7 @@ 
 #
 
 obj-$(CONFIG_SECURITY_IPE) += \
+	digest.o \
 	eval.o \
 	hooks.o \
 	fs.o \
diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index 0dd5f10c318f..b5c58655ac74 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -13,6 +13,7 @@ 
 #include "hooks.h"
 #include "policy.h"
 #include "audit.h"
+#include "digest.h"
 
 #define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY")
 
@@ -40,8 +41,29 @@  static const char *const audit_op_names[__IPE_OP_MAX] = {
 static const char *const audit_prop_names[__IPE_PROP_MAX] = {
 	"boot_verified=FALSE",
 	"boot_verified=TRUE",
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+	"dmverity_roothash=",
+	"dmverity_signature=FALSE",
+	"dmverity_signature=TRUE",
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
 };
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+/**
+ * audit_dmv_roothash - audit a roothash of a dmverity volume.
+ * @ab: Supplies a pointer to the audit_buffer to append to.
+ * @r: Supplies a pointer to the digest structure.
+ */
+static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
+{
+	ipe_digest_audit(ab, rh);
+}
+#else
+static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
+{
+}
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 /**
  * audit_rule - audit an IPE policy rule approximation.
  * @ab: Supplies a pointer to the audit_buffer to append to.
@@ -53,8 +75,13 @@  static void audit_rule(struct audit_buffer *ab, const struct ipe_rule *r)
 
 	audit_log_format(ab, "rule=\"op=%s ", audit_op_names[r->op]);
 
-	list_for_each_entry(ptr, &r->props, next)
-		audit_log_format(ab, "%s ", audit_prop_names[ptr->type]);
+	list_for_each_entry(ptr, &r->props, next) {
+		audit_log_format(ab, "%s", audit_prop_names[ptr->type]);
+		if (ptr->type == IPE_PROP_DMV_ROOTHASH)
+			audit_dmv_roothash(ab, ptr->value);
+
+		audit_log_format(ab, " ");
+	}
 
 	audit_log_format(ab, "action=%s\"", ACTSTR(r->action));
 }
diff --git a/security/ipe/digest.c b/security/ipe/digest.c
new file mode 100644
index 000000000000..7a42ca71880c
--- /dev/null
+++ b/security/ipe/digest.c
@@ -0,0 +1,142 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "digest.h"
+
+/**
+ * ipe_digest_parse - parse a digest in IPE's policy.
+ * @valstr: Supplies the string parsed from the policy.
+ * @value: Supplies a pointer to be populated with the result.
+ *
+ * Digests in IPE are defined in a standard way:
+ *	<alg_name>:<hex>
+ *
+ * Use this function to create a property to parse the digest
+ * consistently. The parsed digest will be saved in @value in IPE's
+ * policy.
+ *
+ * Return:
+ * * 0	- OK
+ * * !0	- Error
+ */
+int ipe_digest_parse(const char *valstr, void **value)
+{
+	char *sep, *raw_digest;
+	size_t raw_digest_len;
+	int rc = 0;
+	u8 *digest = NULL;
+	struct digest_info *info = NULL;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	sep = strchr(valstr, ':');
+	if (!sep) {
+		rc = -EBADMSG;
+		goto err;
+	}
+
+	info->alg = kstrndup(valstr, sep - valstr, GFP_KERNEL);
+	if (!info->alg) {
+		rc = -ENOMEM;
+		goto err;
+	}
+
+	raw_digest = sep + 1;
+	raw_digest_len = strlen(raw_digest);
+	info->raw_digest = kstrndup(raw_digest, raw_digest_len, GFP_KERNEL);
+	if (!info->raw_digest) {
+		rc = -ENOMEM;
+		goto err_free_alg;
+	}
+
+	info->digest_len = (raw_digest_len + 1) / 2;
+	digest = kzalloc(info->digest_len, GFP_KERNEL);
+	if (!digest) {
+		rc = -ENOMEM;
+		goto err_free_raw;
+	}
+
+	rc = hex2bin(digest, raw_digest, info->digest_len);
+	if (rc < 0) {
+		rc = -EINVAL;
+		goto err_free_raw;
+	}
+
+	info->digest = digest;
+	*value = info;
+	return 0;
+
+err_free_raw:
+	kfree(info->raw_digest);
+err_free_alg:
+	kfree(info->alg);
+err:
+	kfree(digest);
+	kfree(info);
+	return rc;
+}
+
+/**
+ * ipe_digest_eval - evaluate an IPE digest against another digest.
+ * @expect: Supplies the policy-provided digest value.
+ * @digest: Supplies the digest to compare against the policy digest value.
+ * @digest_len: The length of @digest.
+ * @alg: Supplies the name of the algorithm used to calculated @digest.
+ *
+ * Return:
+ * * true	- digests match
+ * * false	- digests do not match
+ */
+bool ipe_digest_eval(const void *expect, const u8 *digest, size_t digest_len,
+		     const char *alg)
+{
+	const struct digest_info *info = (struct digest_info *)expect;
+
+	return (digest_len == info->digest_len) && !strcmp(alg, info->alg) &&
+	       (!memcmp(info->digest, digest, info->digest_len));
+}
+
+/**
+ * ipe_digest_free - free an IPE digest.
+ * @value: Supplies a pointer the policy-provided digest value to free.
+ */
+void ipe_digest_free(void **value)
+{
+	struct digest_info *info = (struct digest_info *)(*value);
+
+	if (IS_ERR_OR_NULL(info))
+		return;
+
+	kfree(info->alg);
+	kfree(info->raw_digest);
+	kfree(info->digest);
+	kfree(info);
+}
+
+/**
+ * ipe_digest_audit - audit a digest that was sourced from IPE's policy.
+ * @ab: Supplies the audit_buffer to append the formatted result.
+ * @val: Supplies a pointer to source the audit record from.
+ *
+ * Digests in IPE are defined in a standard way:
+ *	<alg_name>:<hex>
+ *
+ * Use this function to create a property to audit the digest
+ * consistently.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - Error
+ */
+void ipe_digest_audit(struct audit_buffer *ab, const void *val)
+{
+	const struct digest_info *info = (struct digest_info *)val;
+
+	audit_log_untrustedstring(ab, info->alg);
+	audit_log_format(ab, ":");
+	audit_log_untrustedstring(ab, info->raw_digest);
+}
diff --git a/security/ipe/digest.h b/security/ipe/digest.h
new file mode 100644
index 000000000000..e40ba4b2799e
--- /dev/null
+++ b/security/ipe/digest.h
@@ -0,0 +1,26 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#ifndef _IPE_DIGEST_H
+#define _IPE_DIGEST_H
+
+#include "policy.h"
+#include <linux/types.h>
+#include <linux/audit.h>
+
+struct digest_info {
+	const char *alg;
+	const char *raw_digest;
+	const u8 *digest;
+	size_t digest_len;
+};
+
+int ipe_digest_parse(const char *valstr, void **value);
+void ipe_digest_free(void **value);
+void ipe_digest_audit(struct audit_buffer *ab, const void *val);
+bool ipe_digest_eval(const void *expect, const u8 *digest, size_t digest_len,
+		     const char *alg);
+
+#endif /* _IPE_DIGEST_H */
diff --git a/security/ipe/eval.c b/security/ipe/eval.c
index 78c54ff1fdd3..82ad48d7aa3d 100644
--- a/security/ipe/eval.c
+++ b/security/ipe/eval.c
@@ -16,10 +16,12 @@ 
 #include "eval.h"
 #include "policy.h"
 #include "audit.h"
+#include "digest.h"
 
 struct ipe_policy __rcu *ipe_active_policy;
 bool success_audit;
 bool enforce = true;
+#define INO_BLOCK_DEV(ino) ((ino)->i_sb->s_bdev)
 
 static const struct super_block *pinned_sb;
 static DEFINE_SPINLOCK(pin_lock);
@@ -59,6 +61,23 @@  static bool from_pinned(const struct super_block *sb)
 	return rv;
 }
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+/**
+ * build_ipe_bdev_ctx - Build ipe_bdev field of an evaluation context.
+ * @ctx: Supplies a pointer to the context to be populdated.
+ * @ino: Supplies the inode struct of the file triggered IPE event.
+ */
+static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
+{
+	if (INO_BLOCK_DEV(ino))
+		ctx->ipe_bdev = ipe_bdev(INO_BLOCK_DEV(ino));
+}
+#else
+static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
+{
+}
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 /**
  * build_eval_ctx - Build an evaluation context.
  * @ctx: Supplies a pointer to the context to be populdated.
@@ -69,15 +88,89 @@  void build_eval_ctx(struct ipe_eval_ctx *ctx,
 		    const struct file *file,
 		    enum ipe_op_type op)
 {
+	struct inode *ino = NULL;
+
 	if (op == IPE_OP_EXEC && file)
 		pin_sb(FILE_SUPERBLOCK(file));
 
 	ctx->file = file;
 	ctx->op = op;
 
-	if (file)
+	if (file) {
 		ctx->from_init_sb = from_pinned(FILE_SUPERBLOCK(file));
+		ino = d_real_inode(file->f_path.dentry);
+		build_ipe_bdev_ctx(ctx, ino);
+	}
+}
+
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+/**
+ * evaluate_dmv_roothash - Evaluate @ctx against a dmv roothash property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @p: Supplies a pointer to the property being evaluated.
+ *
+ * Return:
+ * * true	- The current @ctx match the @p
+ * * false	- The current @ctx doesn't match the @p
+ */
+static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
+				  struct ipe_prop *p)
+{
+	return !!ctx->ipe_bdev &&
+	       ipe_digest_eval(p->value,
+			       ctx->ipe_bdev->digest,
+			       ctx->ipe_bdev->digest_len,
+			       ctx->ipe_bdev->digest_algo);
+}
+
+/**
+ * evaluate_dmv_sig_false: Analyze @ctx against a dmv sig false property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @p: Supplies a pointer to the property being evaluated.
+ *
+ * Return:
+ * * true	- The current @ctx match the @p
+ * * false	- The current @ctx doesn't match the @p
+ */
+static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx,
+				   struct ipe_prop *p)
+{
+	return !ctx->ipe_bdev || (!ctx->ipe_bdev->dm_verity_signed);
+}
+
+/**
+ * evaluate_dmv_sig_true: Analyze @ctx against a dmv sig true property.
+ * @ctx: Supplies a pointer to the context being evaluated.
+ * @p: Supplies a pointer to the property being evaluated.
+ *
+ * Return:
+ * * true	- The current @ctx match the @p
+ * * false	- The current @ctx doesn't match the @p
+ */
+static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx,
+				  struct ipe_prop *p)
+{
+	return ctx->ipe_bdev && (!!ctx->ipe_bdev->dm_verity_signed);
+}
+#else
+static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
+				  struct ipe_prop *p)
+{
+	return false;
+}
+
+static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx,
+				   struct ipe_prop *p)
+{
+	return false;
+}
+
+static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx,
+				  struct ipe_prop *p)
+{
+	return false;
 }
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
 
 /**
  * evaluate_property - Analyze @ctx against a property.
@@ -96,6 +189,12 @@  static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
 		return !ctx->from_init_sb;
 	case IPE_PROP_BOOT_VERIFIED_TRUE:
 		return ctx->from_init_sb;
+	case IPE_PROP_DMV_ROOTHASH:
+		return evaluate_dmv_roothash(ctx, p);
+	case IPE_PROP_DMV_SIG_FALSE:
+		return evaluate_dmv_sig_false(ctx, p);
+	case IPE_PROP_DMV_SIG_TRUE:
+		return evaluate_dmv_sig_true(ctx, p);
 	default:
 		return false;
 	}
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index 3f7f71452618..e575e94804a4 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -17,11 +17,24 @@  extern struct ipe_policy __rcu *ipe_active_policy;
 extern bool success_audit;
 extern bool enforce;
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+struct ipe_bdev {
+	bool dm_verity_signed;
+
+	const u8 *digest;
+	size_t digest_len;
+	const char *digest_algo;
+};
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 struct ipe_eval_ctx {
 	enum ipe_op_type op;
 
 	const struct file *file;
 	bool from_init_sb;
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+	const struct ipe_bdev *ipe_bdev;
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
 };
 
 enum ipe_match {
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index e9386762a597..8b8031e66f36 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -7,6 +7,9 @@ 
 #include <linux/types.h>
 #include <linux/binfmts.h>
 #include <linux/mman.h>
+#include <linux/blk_types.h>
+#include <linux/dm-verity.h>
+#include <crypto/hash_info.h>
 
 #include "ipe.h"
 #include "hooks.h"
@@ -193,3 +196,51 @@  void ipe_sb_free_security(struct super_block *mnt_sb)
 {
 	ipe_invalidate_pinned_sb(mnt_sb);
 }
+
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+/**
+ * ipe_bdev_free_security - free IPE's LSM blob of block_devices.
+ * @bdev: Supplies a pointer to a block_device that contains the structure
+ *	  to free.
+ */
+void ipe_bdev_free_security(struct block_device *bdev)
+{
+	struct ipe_bdev *blob = ipe_bdev(bdev);
+
+	kfree(blob->digest);
+	kfree(blob->digest_algo);
+}
+
+/**
+ * ipe_bdev_setsecurity - save data from a bdev to IPE's LSM blob.
+ * @bdev: Supplies a pointer to a block_device that contains the LSM blob.
+ * @key: Supplies the string key that uniquely identifies the value.
+ * @value: Supplies the value to store.
+ * @len: The length of @value.
+ */
+int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
+			 const void *value, size_t len)
+{
+	struct ipe_bdev *blob = ipe_bdev(bdev);
+
+	if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) {
+		const struct dm_verity_digest *digest = value;
+
+		blob->digest = kmemdup(digest->digest, digest->digest_len, GFP_KERNEL);
+		if (!blob->digest)
+			return -ENOMEM;
+
+		blob->digest_algo = kstrdup_const(digest->algo, GFP_KERNEL);
+		if (!blob->digest_algo)
+			return -ENOMEM;
+
+		blob->digest_len = digest->digest_len;
+		return 0;
+	} else if (!strcmp(key, DM_VERITY_SIGNATURE_SEC_NAME)) {
+		blob->dm_verity_signed = true;
+		return 0;
+	}
+
+	return -EOPNOTSUPP;
+}
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h
index ac0cdfd9877f..d8ccb3728813 100644
--- a/security/ipe/hooks.h
+++ b/security/ipe/hooks.h
@@ -8,6 +8,7 @@ 
 #include <linux/fs.h>
 #include <linux/binfmts.h>
 #include <linux/security.h>
+#include <linux/blk_types.h>
 
 int ipe_bprm_check_security(struct linux_binprm *bprm);
 
@@ -24,4 +25,11 @@  int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents);
 
 void ipe_sb_free_security(struct super_block *mnt_sb);
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+void ipe_bdev_free_security(struct block_device *bdev);
+
+int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
+			 const void *value, size_t len);
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 #endif /* _IPE_HOOKS_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 783058c06cd6..c7e8af554d35 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -5,12 +5,23 @@ 
 
 #include "ipe.h"
 #include "hooks.h"
+#include "eval.h"
 
 bool ipe_enabled;
 
 static struct lsm_blob_sizes ipe_blobs __ro_after_init = {
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+	.lbs_bdev = sizeof(struct ipe_bdev),
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
 };
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+struct ipe_bdev *ipe_bdev(struct block_device *b)
+{
+	return b->security + ipe_blobs.lbs_bdev;
+}
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 static struct security_hook_list ipe_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security),
 	LSM_HOOK_INIT(mmap_file, ipe_mmap_file),
@@ -18,6 +29,10 @@  static struct security_hook_list ipe_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(kernel_read_file, ipe_kernel_read_file),
 	LSM_HOOK_INIT(kernel_load_data, ipe_kernel_load_data),
 	LSM_HOOK_INIT(sb_free_security, ipe_sb_free_security),
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+	LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security),
+	LSM_HOOK_INIT(bdev_setsecurity, ipe_bdev_setsecurity),
+#endif
 };
 
 /**
diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h
index 6dec06403eee..4e8d1a440c4b 100644
--- a/security/ipe/ipe.h
+++ b/security/ipe/ipe.h
@@ -15,4 +15,8 @@ 
 
 extern bool ipe_enabled;
 
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+struct ipe_bdev *ipe_bdev(struct block_device *b);
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
+
 #endif /* _IPE_H */
diff --git a/security/ipe/policy.h b/security/ipe/policy.h
index 32e5e0b941eb..13e5c40e9204 100644
--- a/security/ipe/policy.h
+++ b/security/ipe/policy.h
@@ -33,6 +33,9 @@  enum ipe_action_type {
 enum ipe_prop_type {
 	IPE_PROP_BOOT_VERIFIED_FALSE,
 	IPE_PROP_BOOT_VERIFIED_TRUE,
+	IPE_PROP_DMV_ROOTHASH,
+	IPE_PROP_DMV_SIG_FALSE,
+	IPE_PROP_DMV_SIG_TRUE,
 	__IPE_PROP_MAX
 };
 
diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
index ee7646de72ed..3acb1d681f65 100644
--- a/security/ipe/policy_parser.c
+++ b/security/ipe/policy_parser.c
@@ -10,6 +10,7 @@ 
 
 #include "policy.h"
 #include "policy_parser.h"
+#include "digest.h"
 
 #define START_COMMENT	'#'
 #define IPE_POLICY_DELIM " \t"
@@ -216,6 +217,7 @@  static void free_rule(struct ipe_rule *r)
 
 	list_for_each_entry_safe(p, t, &r->props, next) {
 		list_del(&p->next);
+		ipe_digest_free(&p->value);
 		kfree(p);
 	}
 
@@ -268,6 +270,11 @@  static enum ipe_action_type parse_action(char *t)
 static const match_table_t property_tokens = {
 	{IPE_PROP_BOOT_VERIFIED_FALSE,	"boot_verified=FALSE"},
 	{IPE_PROP_BOOT_VERIFIED_TRUE,	"boot_verified=TRUE"},
+#ifdef CONFIG_IPE_PROP_DM_VERITY
+	{IPE_PROP_DMV_ROOTHASH,		"dmverity_roothash=%s"},
+	{IPE_PROP_DMV_SIG_FALSE,	"dmverity_signature=FALSE"},
+	{IPE_PROP_DMV_SIG_TRUE,		"dmverity_signature=TRUE"},
+#endif /* CONFIG_IPE_PROP_DM_VERITY */
 	{IPE_PROP_INVALID,		NULL}
 };
 
@@ -287,6 +294,7 @@  static int parse_property(char *t, struct ipe_rule *r)
 	struct ipe_prop *p = NULL;
 	int rc = 0;
 	int token;
+	char *dup = NULL;
 
 	p = kzalloc(sizeof(*p), GFP_KERNEL);
 	if (!p)
@@ -295,8 +303,20 @@  static int parse_property(char *t, struct ipe_rule *r)
 	token = match_token(t, property_tokens, args);
 
 	switch (token) {
+	case IPE_PROP_DMV_ROOTHASH:
+		dup = match_strdup(&args[0]);
+		if (!dup) {
+			rc = -ENOMEM;
+			goto err;
+		}
+		rc = ipe_digest_parse(dup, &p->value);
+		if (rc)
+			goto err;
+		fallthrough;
 	case IPE_PROP_BOOT_VERIFIED_FALSE:
 	case IPE_PROP_BOOT_VERIFIED_TRUE:
+	case IPE_PROP_DMV_SIG_FALSE:
+	case IPE_PROP_DMV_SIG_TRUE:
 		p->type = token;
 		break;
 	default:
@@ -307,10 +327,12 @@  static int parse_property(char *t, struct ipe_rule *r)
 		goto err;
 	list_add_tail(&p->next, &r->props);
 
+out:
+	kfree(dup);
 	return rc;
 err:
 	kfree(p);
-	return rc;
+	goto out;
 }
 
 /**