Skip to contentSkip to conversation

ima: use asynchronous hash API for hash calculation

PATCH In review Dmitry Kasatkin 3 participants 15 messages Jul 1, 2014 – Jul 2, 2014

Subsystems (5)
lore
Share & tools
lore

Dmitry Kasatkin

author maintainer
#0 lore raw

v2 cover ima: use asynchronous hash API for hash calculation

Depending on the IMA policy, it might require to measure huge amount of files. It may be very important to speedup hash calculation or to reduce (bettery) energy required to do it. Currently IMA uses synchronous hash API (shash) which is CPU based. CPU based hash calculation is very CPU intensive and on the battery powered device will be also high energy consuming.

Many platforms provide cryptographic acceleration modules which allow speedup and/or reduce energy consumption, and provide asynchronous way to calculate hashes. Defacto way to implement drivers for such accelerators is using asynchronous hash API (ahash).

The first patch adds use of ahash API to IMA. Performance of using HW acceleration depends very much on amount of data to hash and it depends on particular HW. It is usually inefficient for small data due to HW initialization overhead. In order to make it possible to optimize performance for particular system, the patch provides kernel parameter 'ima_ahash=<min_file_size>', which allows to specify optimal file size when start using ahash. By default ahash is disabled until non-zero value with 'ima_ahash=' is provided.

Second patch introduces multi-page buffers which makes HW acceleration more efficient. It extends 'ima_ahash' kernel parameter to specify buffer size:
'ima_ahash=<min_file_size>[,<bufsize>]'

Third patch introduces double-buffering which allows to readahead next portion of data for hashing while calculating the hash.

Changes to v1:
- ima_ahash_size and ima_ahash_bufsize were combined as ima_ahash
- ahash pre-allocation moved out from __init code to be able to use ahash crypto modules. Ahash allocated once on the first use.
- hash calculation falls back to sahsh if ahash allocation/calculation fails
- complex initialization separated from variable declaration
- improved comments

- Dmitry

Dmitry Kasatkin (3):
ima: use ahash API for file hash calculation
ima: introduce multi-page collect buffers
ima: provide double buffering for hash calculation

 Documentation/kernel-parameters.txt |   6 +
 security/integrity/ima/ima_crypto.c | 287 +++++++++++++++++++++++++++++++++++-
 2 files changed, 290 insertions(+), 3 deletions(-)
Signature · 1 line
1.9.1
author maintainer
#1 lore raw

PATCH v2 1/3 ima: use ahash API for file hash calculation

Async hash API allows to use HW acceleration for hash calculation. It may give significant performance gain or/and reduce power consumption, which might be very beneficial for battery powered devices.

This patch introduces hash calculation using ahash API.

ahash performance depends on data size and particular HW. Under certain limit, depending on the system, shash performance may be better.

This patch defines 'ima_ahash' kernel parameter which can be used to define minimal file size to use with ahash. When file size is not set or smaller than defined by the parameter, shash will be used.

---
 Documentation/kernel-parameters.txt |   5 +
 security/integrity/ima/ima_crypto.c | 185 +++++++++++++++++++++++++++++++++++-
 2 files changed, 186 insertions(+), 4 deletions(-)
Documentation/kernel-parameters.txt +5 -0
index 5a214a3..b406f5c 100644
@@ -1291,6 +1291,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 	ihash_entries=	[KNL]
 			Set number of hash buckets for inode cache.
 
+	ima_ahash=	[IMA] Asynchronous hash usage parameters
+			Format: <min_file_size>
+			Set the minimal file size when use asynchronous hash.
+			If ima_ahash is not provided, ahash usage is disabled.
+
 	ima_appraise=	[IMA] appraise integrity measurements
 			Format: { "off" | "enforce" | "fix" }
 			default: "enforce"
security/integrity/ima/ima_crypto.c +181 -4

210 lines not on this page · raw message

Signature · 1 line
1.9.1
#2 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 4 lines · Dmitry Kasatkin

Async hash API allows to use HW acceleration for hash calculation. It may give significant performance gain or/and reduce power consumption, which might be very beneficial for battery powered devices.

Quoted 8 lines

This patch introduces hash calculation using ahash API.

ahash performance depends on data size and particular HW. Under certain limit, depending on the system, shash performance may be better.

This patch defines 'ima_ahash' kernel parameter which can be used to define minimal file size to use with ahash. When file size is not set or smaller than defined by the parameter, shash will be used.

Thank you for the updated patches. The changes should be listed here in the patch description.

I keep going back and forth as to whether the ahash routines should be totally separate, as posted, from the shash routines. Having separate functions is very clear/clean, but there is quite a bit of code duplication (eg. ima_calc_file_hash()/ima_calc_file_ahash(), ima_free_tfm()/ima_free_atfm(), ima_alloc_tfm()/ima_alloc_atfm()).

Soliciting comments ...

Quoted 28 lines
---
 Documentation/kernel-parameters.txt |   5 +
 security/integrity/ima/ima_crypto.c | 185 +++++++++++++++++++++++++++++++++++-
 2 files changed, 186 insertions(+), 4 deletions(-)
Documentation/kernel-parameters.txt +5 -0
index 5a214a3..b406f5c 100644
@@ -1291,6 +1291,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 	ihash_entries=	[KNL]
 			Set number of hash buckets for inode cache.
 
+	ima_ahash=	[IMA] Asynchronous hash usage parameters
+			Format: <min_file_size>
+			Set the minimal file size when use asynchronous hash.
+			If ima_ahash is not provided, ahash usage is disabled.
+
 	ima_appraise=	[IMA] appraise integrity measurements
 			Format: { "off" | "enforce" | "fix" }
 			default: "enforce"
security/integrity/ima/ima_crypto.c +0 -0
index f82d1d7..5eb19b4 100644

security/integrity/ima/ima_crypto.c line 28

Quoted 4 lines
security/integrity/ima/ima_crypto.c +1 -0
@@ -25,7 +25,27 @@
 #include <crypto/hash_info.h>
 #include "ima.h"
 
+

Extra blank line not needed.

Quoted 183 lines
security/integrity/ima/ima_crypto.c +160 -4
+struct ahash_completion {
+	struct completion completion;
+	int err;
+};
+
 static struct crypto_shash *ima_shash_tfm;
+static struct crypto_ahash *ima_ahash_tfm;
+
+/* minimal file size for ahash use */
+static loff_t ima_ahash_size;
+
+static int __init ima_ahash_setup(char *str)
+{
+	int rc;
+
+	rc = kstrtoll(str, 10, &ima_ahash_size);
+
+	return !rc;
+}
+__setup("ima_ahash=", ima_ahash_setup);
 
 /**
  * ima_kernel_read - read file content
@@ -93,9 +113,146 @@ static void ima_free_tfm(struct crypto_shash *tfm)
 		crypto_free_shash(tfm);
 }
 
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+	struct crypto_ahash *tfm = ima_ahash_tfm;
+	int rc;
+
+	if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+		tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);
+		if (!IS_ERR(tfm)) {
+			if (algo == ima_hash_algo)
+				ima_ahash_tfm = tfm;
+		} else {
+			rc = PTR_ERR(tfm);
+			pr_err("Can not allocate %s (reason: %d)\n",
+			       hash_algo_name[algo], rc);
+		}
+	}
+	return tfm;
+}
+
+static void ima_free_atfm(struct crypto_ahash *tfm)
+{
+	if (tfm != ima_ahash_tfm)
+		crypto_free_ahash(tfm);
+}
+
+static void ahash_complete(struct crypto_async_request *req, int err)
+{
+	struct ahash_completion *res = req->data;
+
+	if (err == -EINPROGRESS)
+		return;
+	res->err = err;
+	complete(&res->completion);
+}
+
+static int ahash_wait(int err, struct ahash_completion *res)
+{
+	switch (err) {
+	case 0:
+		break;
+	case -EINPROGRESS:
+	case -EBUSY:
+		wait_for_completion(&res->completion);
+		reinit_completion(&res->completion);
+		err = res->err;
+		/* fall through */
+	default:
+		pr_crit("ahash calculation failed: err: %d\n", err);
+	}
+
+	return err;
+}
+
+static int ima_calc_file_hash_atfm(struct file *file,
+				   struct ima_digest_data *hash,
+				   struct crypto_ahash *tfm)
+{
+	loff_t i_size, offset;
+	char *rbuf;
+	int rc, read = 0, rbuf_len;
+	struct ahash_request *req;
+	struct scatterlist sg[1];
+	struct ahash_completion res;
+
+	hash->length = crypto_ahash_digestsize(tfm);
+
+	req = ahash_request_alloc(tfm, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	init_completion(&res.completion);
+	ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+				   CRYPTO_TFM_REQ_MAY_SLEEP,
+				   ahash_complete, &res);
+
+	rc = ahash_wait(crypto_ahash_init(req), &res);
+	if (rc)
+		goto out1;
+
+	i_size = i_size_read(file_inode(file));
+
+	if (i_size == 0)
+		goto out2;
+
+	rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!rbuf) {
+		rc = -ENOMEM;
+		goto out1;
+	}
+
+	if (!(file->f_mode & FMODE_READ)) {
+		file->f_mode |= FMODE_READ;
+		read = 1;
+	}
+
+	for (offset = 0; offset < i_size; offset += rbuf_len) {
+		rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE);
+		if (rbuf_len < 0) {
+			rc = rbuf_len;
+			vi break;
+		}
+		if (rbuf_len == 0)
+			break;
+
+		sg_init_one(&sg[0], rbuf, rbuf_len);
+		ahash_request_set_crypt(req, sg, NULL, rbuf_len);
+
+		rc = ahash_wait(crypto_ahash_update(req), &res);
+		if (rc)
+			break;
+	}
+	if (read)
+		file->f_mode &= ~FMODE_READ;
+	kfree(rbuf);
+out2:
+	if (!rc) {
+		ahash_request_set_crypt(req, NULL, hash->digest, 0);
+		rc = ahash_wait(crypto_ahash_final(req), &res);
+	}
+out1:
+	ahash_request_free(req);
+	return rc;
+}
+
+static int ima_calc_file_ahash(struct file *file, struct ima_digest_data *hash)
+{
+	struct crypto_ahash *tfm;
+	int rc;
+
+	tfm = ima_alloc_atfm(hash->algo);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	rc = ima_calc_file_hash_atfm(file, hash, tfm);
+
+	ima_free_atfm(tfm);
+
+	return rc;
+}
+
 static int ima_calc_file_hash_tfm(struct file *file,
 				  struct ima_digest_data *hash,
 				  struct crypto_shash *tfm)
@@ -156,7 +313,7 @@ out:
 	return rc;
 }
 
-int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+static int ima_calc_file_shash(struct file *file, struct ima_digest_data *hash)
 {
 	struct crypto_shash *tfm;
 	int rc;

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 337

Quoted 9 lines
security/integrity/ima/ima_crypto.c +6 -0
@@ -172,6 +329,26 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
 	return rc;
 }
 
+int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+{
+	loff_t i_size;
+	int rc;
+
+	i_size = i_size_read(file_inode(file));

Depending on the config options, i_size_read() does more than just
access the inode's i_size. Instead of calling it again, perhaps it
should be passed to ima_calc_file_hash()/ahash().

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 342

Quoted 5 lines
security/integrity/ima/ima_crypto.c +5 -0
+
+	/* shash is more efficient for small data
+	 * ahash performance depends on data size and particular HW
+	 * ima_ahash_size allows to specify the best value for the system
+	 */

Function descriptions belong above the function, not inside.

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 348

Quoted 6 lines
security/integrity/ima/ima_crypto.c +6 -0
+	if (ima_ahash_size && i_size >= ima_ahash_size) {
+		rc = ima_calc_file_ahash(file, hash);
+		if (!rc)
+			return 0;
+	}
+	/* fallback to shash if ahash failed */

Same here.

thanks,

Mimi

Quoted 6 lines
security/integrity/ima/ima_crypto.c +3 -0
+	return ima_calc_file_shash(file, hash);
+}
+
 /*
  * Calculate the hash of template data
  */
author maintainer
#3 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 17 lines · Mimi Zohar
Quoted 4 lines · Dmitry Kasatkin

Async hash API allows to use HW acceleration for hash calculation. It may give significant performance gain or/and reduce power consumption, which might be very beneficial for battery powered devices.

Quoted 8 lines

This patch introduces hash calculation using ahash API.

ahash performance depends on data size and particular HW. Under certain limit, depending on the system, shash performance may be better.

This patch defines 'ima_ahash' kernel parameter which can be used to define minimal file size to use with ahash. When file size is not set or smaller than defined by the parameter, shash will be used.

Thank you for the updated patches. The changes should be listed here in the patch description.

Quoted 8 lines

I keep going back and forth as to whether the ahash routines should be totally separate, as posted, from the shash routines. Having separate functions is very clear/clean, but there is quite a bit of code duplication (eg. ima_calc_file_hash()/ima_calc_file_ahash(), ima_free_tfm()/ima_free_atfm(), ima_alloc_tfm()/ima_alloc_atfm()).

Soliciting comments ...

I think what you say is a "pattern": alloc/calc/free.
But the code uses different API mostly and there very small duplication...

In fact with this 'clean' separation we can have CONFIG_ option to ifdef the code ot have it in separate file for those who really want to make smallest kernel possible...

Quoted 221 lines
Quoted 28 lines
---
 Documentation/kernel-parameters.txt |   5 +
 security/integrity/ima/ima_crypto.c | 185 +++++++++++++++++++++++++++++++++++-
 2 files changed, 186 insertions(+), 4 deletions(-)
Documentation/kernel-parameters.txt +5 -0
index 5a214a3..b406f5c 100644
@@ -1291,6 +1291,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
      ihash_entries=  [KNL]
                      Set number of hash buckets for inode cache.
 
+     ima_ahash=      [IMA] Asynchronous hash usage parameters
+                     Format: <min_file_size>
+                     Set the minimal file size when use asynchronous hash.
+                     If ima_ahash is not provided, ahash usage is disabled.
+
      ima_appraise=   [IMA] appraise integrity measurements
                      Format: { "off" | "enforce" | "fix" }
                      default: "enforce"
security/integrity/ima/ima_crypto.c +0 -0
index f82d1d7..5eb19b4 100644

security/integrity/ima/ima_crypto.c line 28

Quoted 4 lines
security/integrity/ima/ima_crypto.c +1 -0
@@ -25,7 +25,27 @@
 #include <crypto/hash_info.h>
 #include "ima.h"
 
+

Extra blank line not needed.

Quoted 186 lines
security/integrity/ima/ima_crypto.c +160 -4
+struct ahash_completion {
+     struct completion completion;
+     int err;
+};
+
 static struct crypto_shash *ima_shash_tfm;
+static struct crypto_ahash *ima_ahash_tfm;
+
+/* minimal file size for ahash use */
+static loff_t ima_ahash_size;
+
+static int __init ima_ahash_setup(char *str)
+{
+     int rc;
+
+     rc = kstrtoll(str, 10, &ima_ahash_size);
+
+     return !rc;
+}
+__setup("ima_ahash=", ima_ahash_setup);
 
 /**
  * ima_kernel_read - read file content
@@ -93,9 +113,146 @@ static void ima_free_tfm(struct crypto_shash *tfm)
              crypto_free_shash(tfm);
 }
 
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+     struct crypto_ahash *tfm = ima_ahash_tfm;
+     int rc;
+
+     if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+             tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);
+             if (!IS_ERR(tfm)) {
+                     if (algo == ima_hash_algo)
+                             ima_ahash_tfm = tfm;
+             } else {
+                     rc = PTR_ERR(tfm);
+                     pr_err("Can not allocate %s (reason: %d)\n",
+                            hash_algo_name[algo], rc);
+             }
+     }
+     return tfm;
+}
+
+static void ima_free_atfm(struct crypto_ahash *tfm)
+{
+     if (tfm != ima_ahash_tfm)
+             crypto_free_ahash(tfm);
+}
+
+static void ahash_complete(struct crypto_async_request *req, int err)
+{
+     struct ahash_completion *res = req->data;
+
+     if (err == -EINPROGRESS)
+             return;
+     res->err = err;
+     complete(&res->completion);
+}
+
+static int ahash_wait(int err, struct ahash_completion *res)
+{
+     switch (err) {
+     case 0:
+             break;
+     case -EINPROGRESS:
+     case -EBUSY:
+             wait_for_completion(&res->completion);
+             reinit_completion(&res->completion);
+             err = res->err;
+             /* fall through */
+     default:
+             pr_crit("ahash calculation failed: err: %d\n", err);
+     }
+
+     return err;
+}
+
+static int ima_calc_file_hash_atfm(struct file *file,
+                                struct ima_digest_data *hash,
+                                struct crypto_ahash *tfm)
+{
+     loff_t i_size, offset;
+     char *rbuf;
+     int rc, read = 0, rbuf_len;
+     struct ahash_request *req;
+     struct scatterlist sg[1];
+     struct ahash_completion res;
+
+     hash->length = crypto_ahash_digestsize(tfm);
+
+     req = ahash_request_alloc(tfm, GFP_KERNEL);
+     if (!req)
+             return -ENOMEM;
+
+     init_completion(&res.completion);
+     ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+                                CRYPTO_TFM_REQ_MAY_SLEEP,
+                                ahash_complete, &res);
+
+     rc = ahash_wait(crypto_ahash_init(req), &res);
+     if (rc)
+             goto out1;
+
+     i_size = i_size_read(file_inode(file));
+
+     if (i_size == 0)
+             goto out2;
+
+     rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+     if (!rbuf) {
+             rc = -ENOMEM;
+             goto out1;
+     }
+
+     if (!(file->f_mode & FMODE_READ)) {
+             file->f_mode |= FMODE_READ;
+             read = 1;
+     }
+
+     for (offset = 0; offset < i_size; offset += rbuf_len) {
+             rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE);
+             if (rbuf_len < 0) {
+                     rc = rbuf_len;
+                     vi break;
+             }
+             if (rbuf_len == 0)
+                     break;
+
+             sg_init_one(&sg[0], rbuf, rbuf_len);
+             ahash_request_set_crypt(req, sg, NULL, rbuf_len);
+
+             rc = ahash_wait(crypto_ahash_update(req), &res);
+             if (rc)
+                     break;
+     }
+     if (read)
+             file->f_mode &= ~FMODE_READ;
+     kfree(rbuf);
+out2:
+     if (!rc) {
+             ahash_request_set_crypt(req, NULL, hash->digest, 0);
+             rc = ahash_wait(crypto_ahash_final(req), &res);
+     }
+out1:
+     ahash_request_free(req);
+     return rc;
+}
+
+static int ima_calc_file_ahash(struct file *file, struct ima_digest_data *hash)
+{
+     struct crypto_ahash *tfm;
+     int rc;
+
+     tfm = ima_alloc_atfm(hash->algo);
+     if (IS_ERR(tfm))
+             return PTR_ERR(tfm);
+
+     rc = ima_calc_file_hash_atfm(file, hash, tfm);
+
+     ima_free_atfm(tfm);
+
+     return rc;
+}
+
 static int ima_calc_file_hash_tfm(struct file *file,
                                struct ima_digest_data *hash,
                                struct crypto_shash *tfm)
@@ -156,7 +313,7 @@ out:
      return rc;
 }
 
-int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+static int ima_calc_file_shash(struct file *file, struct ima_digest_data *hash)
 {
      struct crypto_shash *tfm;
      int rc;
@@ -172,6 +329,26 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
      return rc;
 }
 
Quoted 11 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 337

Quoted 6 lines
security/integrity/ima/ima_crypto.c +6 -0
+int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+{
+     loff_t i_size;
+     int rc;
+
+     i_size = i_size_read(file_inode(file));

Depending on the config options, i_size_read() does more than just
access the inode's i_size. Instead of calling it again, perhaps it
should be passed to ima_calc_file_hash()/ahash().

I thought about it.. Indeed let's make it like that.

Quoted 8 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 342

Quoted 5 lines
security/integrity/ima/ima_crypto.c +5 -0
+
+     /* shash is more efficient for small data
+      * ahash performance depends on data size and particular HW
+      * ima_ahash_size allows to specify the best value for the system
+      */

Function descriptions belong above the function, not inside.

This is a comment to the code..

Quoted 9 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 348

Quoted 6 lines
security/integrity/ima/ima_crypto.c +6 -0
+     if (ima_ahash_size && i_size >= ima_ahash_size) {
+             rc = ima_calc_file_ahash(file, hash);
+             if (!rc)
+                     return 0;
+     }
+     /* fallback to shash if ahash failed */

Same here.

Same here...

But sure it can move to function description...

Quoted 12 lines

thanks,

Mimi

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 354

Quoted 6 lines
security/integrity/ima/ima_crypto.c +3 -0
+     return ima_calc_file_shash(file, hash);
+}
+
 /*
  * Calculate the hash of template data
  */

Thanks.

#4 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 27 lines · Dmitry Kasatkin
Quoted 17 lines · Mimi Zohar
Quoted 4 lines · Dmitry Kasatkin

Async hash API allows to use HW acceleration for hash calculation. It may give significant performance gain or/and reduce power consumption, which might be very beneficial for battery powered devices.

Quoted 8 lines

This patch introduces hash calculation using ahash API.

ahash performance depends on data size and particular HW. Under certain limit, depending on the system, shash performance may be better.

This patch defines 'ima_ahash' kernel parameter which can be used to define minimal file size to use with ahash. When file size is not set or smaller than defined by the parameter, shash will be used.

Thank you for the updated patches. The changes should be listed here in the patch description.

Quoted 8 lines

I keep going back and forth as to whether the ahash routines should be totally separate, as posted, from the shash routines. Having separate functions is very clear/clean, but there is quite a bit of code duplication (eg. ima_calc_file_hash()/ima_calc_file_ahash(), ima_free_tfm()/ima_free_atfm(), ima_alloc_tfm()/ima_alloc_atfm()).

Soliciting comments ...

Quoted 6 lines

I think what you say is a "pattern": alloc/calc/free.
But the code uses different API mostly and there very small duplication...

In fact with this 'clean' separation we can have CONFIG_ option to ifdef the code ot have it in separate file for those who really want to make smallest kernel possible...

Ok, this is an acceptable reason.

thanks,

Mimi

Quoted 281 lines
Quoted 222 lines
Quoted 29 lines
---
 Documentation/kernel-parameters.txt |   5 +
 security/integrity/ima/ima_crypto.c | 185 +++++++++++++++++++++++++++++++++++-
 2 files changed, 186 insertions(+), 4 deletions(-)
Documentation/kernel-parameters.txt +5 -0
index 5a214a3..b406f5c 100644
@@ -1291,6 +1291,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
      ihash_entries=  [KNL]
                      Set number of hash buckets for inode cache.
 
+     ima_ahash=      [IMA] Asynchronous hash usage parameters
+                     Format: <min_file_size>
+                     Set the minimal file size when use asynchronous hash.
+                     If ima_ahash is not provided, ahash usage is disabled.
+
      ima_appraise=   [IMA] appraise integrity measurements
                      Format: { "off" | "enforce" | "fix" }
                      default: "enforce"
security/integrity/ima/ima_crypto.c +0 -0
index f82d1d7..5eb19b4 100644

security/integrity/ima/ima_crypto.c line 28

Quoted 4 lines
security/integrity/ima/ima_crypto.c +1 -0
@@ -25,7 +25,27 @@
 #include <crypto/hash_info.h>
 #include "ima.h"
 
+

Extra blank line not needed.

Quoted 186 lines
security/integrity/ima/ima_crypto.c +160 -4
+struct ahash_completion {
+     struct completion completion;
+     int err;
+};
+
 static struct crypto_shash *ima_shash_tfm;
+static struct crypto_ahash *ima_ahash_tfm;
+
+/* minimal file size for ahash use */
+static loff_t ima_ahash_size;
+
+static int __init ima_ahash_setup(char *str)
+{
+     int rc;
+
+     rc = kstrtoll(str, 10, &ima_ahash_size);
+
+     return !rc;
+}
+__setup("ima_ahash=", ima_ahash_setup);
 
 /**
  * ima_kernel_read - read file content
@@ -93,9 +113,146 @@ static void ima_free_tfm(struct crypto_shash *tfm)
              crypto_free_shash(tfm);
 }
 
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+     struct crypto_ahash *tfm = ima_ahash_tfm;
+     int rc;
+
+     if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+             tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);
+             if (!IS_ERR(tfm)) {
+                     if (algo == ima_hash_algo)
+                             ima_ahash_tfm = tfm;
+             } else {
+                     rc = PTR_ERR(tfm);
+                     pr_err("Can not allocate %s (reason: %d)\n",
+                            hash_algo_name[algo], rc);
+             }
+     }
+     return tfm;
+}
+
+static void ima_free_atfm(struct crypto_ahash *tfm)
+{
+     if (tfm != ima_ahash_tfm)
+             crypto_free_ahash(tfm);
+}
+
+static void ahash_complete(struct crypto_async_request *req, int err)
+{
+     struct ahash_completion *res = req->data;
+
+     if (err == -EINPROGRESS)
+             return;
+     res->err = err;
+     complete(&res->completion);
+}
+
+static int ahash_wait(int err, struct ahash_completion *res)
+{
+     switch (err) {
+     case 0:
+             break;
+     case -EINPROGRESS:
+     case -EBUSY:
+             wait_for_completion(&res->completion);
+             reinit_completion(&res->completion);
+             err = res->err;
+             /* fall through */
+     default:
+             pr_crit("ahash calculation failed: err: %d\n", err);
+     }
+
+     return err;
+}
+
+static int ima_calc_file_hash_atfm(struct file *file,
+                                struct ima_digest_data *hash,
+                                struct crypto_ahash *tfm)
+{
+     loff_t i_size, offset;
+     char *rbuf;
+     int rc, read = 0, rbuf_len;
+     struct ahash_request *req;
+     struct scatterlist sg[1];
+     struct ahash_completion res;
+
+     hash->length = crypto_ahash_digestsize(tfm);
+
+     req = ahash_request_alloc(tfm, GFP_KERNEL);
+     if (!req)
+             return -ENOMEM;
+
+     init_completion(&res.completion);
+     ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+                                CRYPTO_TFM_REQ_MAY_SLEEP,
+                                ahash_complete, &res);
+
+     rc = ahash_wait(crypto_ahash_init(req), &res);
+     if (rc)
+             goto out1;
+
+     i_size = i_size_read(file_inode(file));
+
+     if (i_size == 0)
+             goto out2;
+
+     rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+     if (!rbuf) {
+             rc = -ENOMEM;
+             goto out1;
+     }
+
+     if (!(file->f_mode & FMODE_READ)) {
+             file->f_mode |= FMODE_READ;
+             read = 1;
+     }
+
+     for (offset = 0; offset < i_size; offset += rbuf_len) {
+             rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE);
+             if (rbuf_len < 0) {
+                     rc = rbuf_len;
+                     vi break;
+             }
+             if (rbuf_len == 0)
+                     break;
+
+             sg_init_one(&sg[0], rbuf, rbuf_len);
+             ahash_request_set_crypt(req, sg, NULL, rbuf_len);
+
+             rc = ahash_wait(crypto_ahash_update(req), &res);
+             if (rc)
+                     break;
+     }
+     if (read)
+             file->f_mode &= ~FMODE_READ;
+     kfree(rbuf);
+out2:
+     if (!rc) {
+             ahash_request_set_crypt(req, NULL, hash->digest, 0);
+             rc = ahash_wait(crypto_ahash_final(req), &res);
+     }
+out1:
+     ahash_request_free(req);
+     return rc;
+}
+
+static int ima_calc_file_ahash(struct file *file, struct ima_digest_data *hash)
+{
+     struct crypto_ahash *tfm;
+     int rc;
+
+     tfm = ima_alloc_atfm(hash->algo);
+     if (IS_ERR(tfm))
+             return PTR_ERR(tfm);
+
+     rc = ima_calc_file_hash_atfm(file, hash, tfm);
+
+     ima_free_atfm(tfm);
+
+     return rc;
+}
+
 static int ima_calc_file_hash_tfm(struct file *file,
                                struct ima_digest_data *hash,
                                struct crypto_shash *tfm)
@@ -156,7 +313,7 @@ out:
      return rc;
 }
 
-int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+static int ima_calc_file_shash(struct file *file, struct ima_digest_data *hash)
 {
      struct crypto_shash *tfm;
      int rc;
@@ -172,6 +329,26 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
      return rc;
 }
 
Quoted 11 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 337

Quoted 6 lines
security/integrity/ima/ima_crypto.c +6 -0
+int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
+{
+     loff_t i_size;
+     int rc;
+
+     i_size = i_size_read(file_inode(file));

Depending on the config options, i_size_read() does more than just
access the inode's i_size. Instead of calling it again, perhaps it
should be passed to ima_calc_file_hash()/ahash().

I thought about it.. Indeed let's make it like that.

Quoted 8 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 342

Quoted 5 lines
security/integrity/ima/ima_crypto.c +5 -0
+
+     /* shash is more efficient for small data
+      * ahash performance depends on data size and particular HW
+      * ima_ahash_size allows to specify the best value for the system
+      */

Function descriptions belong above the function, not inside.

This is a comment to the code..

Quoted 9 lines

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 348

Quoted 6 lines
security/integrity/ima/ima_crypto.c +6 -0
+     if (ima_ahash_size && i_size >= ima_ahash_size) {
+             rc = ima_calc_file_ahash(file, hash);
+             if (!rc)
+                     return 0;
+     }
+     /* fallback to shash if ahash failed */

Same here.

Same here...

But sure it can move to function description...

Quoted 12 lines

thanks,

Mimi

security/integrity/ima/ima_crypto.c int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) · line 354

Quoted 6 lines
security/integrity/ima/ima_crypto.c +3 -0
+     return ima_calc_file_shash(file, hash);
+}
+
 /*
  * Calculate the hash of template data
  */

Thanks.
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#5 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

In reply to Dmitry Kasatkin

security/integrity/ima/ima_crypto.c static void ima_free_tfm(struct crypto_shash *tfm) · line 122

Quoted 10 lines · Dmitry Kasatkin
security/integrity/ima/ima_crypto.c +7 -3
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+	struct crypto_ahash *tfm = ima_ahash_tfm;
+	int rc;
+
+	if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+		tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);

In the case where algo isn't the same as ima_hash_algo, won't this replace the existing ima_ahash_tfm without freeing it?

Mimi

Quoted 17 lines
security/integrity/ima/ima_crypto.c +17 -0
+		if (!IS_ERR(tfm)) {
+			if (algo == ima_hash_algo)
+				ima_ahash_tfm = tfm;
+		} else {
+			rc = PTR_ERR(tfm);
+			pr_err("Can not allocate %s (reason: %d)\n",
+			       hash_algo_name[algo], rc);
+		}
+	}
+	return tfm;
+}
+
+static void ima_free_atfm(struct crypto_ahash *tfm)
+{
+	if (tfm != ima_ahash_tfm)
+		crypto_free_ahash(tfm);
+}
author maintainer
#6 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 16 lines · Mimi Zohar

security/integrity/ima/ima_crypto.c static void ima_free_tfm(struct crypto_shash *tfm) · line 122

Quoted 10 lines · Dmitry Kasatkin
security/integrity/ima/ima_crypto.c +7 -3
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+     struct crypto_ahash *tfm = ima_ahash_tfm;
+     int rc;
+
+     if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+             tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);

In the case where algo isn't the same as ima_hash_algo, won't this replace the existing ima_ahash_tfm without freeing it?

Look to next comment...

Quoted 5 lines

Mimi

Quoted 3 lines
security/integrity/ima/ima_crypto.c +3 -0
+             if (!IS_ERR(tfm)) {
+                     if (algo == ima_hash_algo)
+                             ima_ahash_tfm = tfm;

Above will set only new tfm for default ima_hash_algo...

Dmitry

Quoted 17 lines
Quoted 14 lines
security/integrity/ima/ima_crypto.c +14 -0
+             } else {
+                     rc = PTR_ERR(tfm);
+                     pr_err("Can not allocate %s (reason: %d)\n",
+                            hash_algo_name[algo], rc);
+             }
+     }
+     return tfm;
+}
+
+static void ima_free_atfm(struct crypto_ahash *tfm)
+{
+     if (tfm != ima_ahash_tfm)
+             crypto_free_ahash(tfm);
+}
Signature · 2 lines
Thanks,
Dmitry
#7 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 18 lines · Dmitry Kasatkin
Quoted 16 lines · Mimi Zohar

security/integrity/ima/ima_crypto.c static void ima_free_tfm(struct crypto_shash *tfm) · line 122

Quoted 10 lines · Dmitry Kasatkin
security/integrity/ima/ima_crypto.c +7 -3
-/*
- * Calculate the MD5/SHA1 file digest
- */
+static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
+{
+     struct crypto_ahash *tfm = ima_ahash_tfm;
+     int rc;
+
+     if ((algo != ima_hash_algo && algo < HASH_ALGO__LAST) || !tfm) {
+             tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);

In the case where algo isn't the same as ima_hash_algo, won't this replace the existing ima_ahash_tfm without freeing it?

Quoted 1 line

Look to next comment...

Yep, my mistake in reading the code.

Mimi

#8 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

In reply to Dmitry Kasatkin

Documentation/kernel-parameters.txt bytes respectively. Such letter suffixes can also be entirely omitted. · line 1297

Quoted 4 lines · Dmitry Kasatkin
Documentation/kernel-parameters.txt +4 -0
+	ima_ahash=	[IMA] Asynchronous hash usage parameters
+			Format: <min_file_size>
+			Set the minimal file size when use asynchronous hash.
+			If ima_ahash is not provided, ahash usage is disabled.

<groan> ... another boot option...

Can we just set this to something sane, and then make a sysctl or something else at runtime to tweak it? The kernel won't use IMA much before userspace comes up, and it can surely live with a slightly suboptimal tuning until the boot scripts have a chance to go bang the tunable.

We should reserve command-line parameters for things that really need tweaking in early boot or are _needed_ to boot.

author maintainer
#9 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 8 lines · Dave Hansen

Documentation/kernel-parameters.txt bytes respectively. Such letter suffixes can also be entirely omitted. · line 1297

Quoted 4 lines · Dmitry Kasatkin
Documentation/kernel-parameters.txt +4 -0
+     ima_ahash=      [IMA] Asynchronous hash usage parameters
+                     Format: <min_file_size>
+                     Set the minimal file size when use asynchronous hash.
+                     If ima_ahash is not provided, ahash usage is disabled.

<groan> ... another boot option...

Quoted 8 lines

Can we just set this to something sane, and then make a sysctl or something else at runtime to tweak it? The kernel won't use IMA much before userspace comes up, and it can surely live with a slightly suboptimal tuning until the boot scripts have a chance to go bang the tunable.

We should reserve command-line parameters for things that really need tweaking in early boot or are _needed_ to boot.

Thanks... Good that you commented about it.
I thought to have module_param, but as IMA is not a module, ended up with __setup..
Quite many always-builtin stuff use module_param... Also in LSM... Runtime can then tweak it for better performance...

Is module param good enough or it should be sysctl?

- Dmitry

#10 lore raw

Re: [PATCH v2 1/3] ima: use ahash API for file hash calculation

Quoted 2 lines · Dmitry Kasatkin
Quoted 2 lines
Quoted 2 lines

We should reserve command-line parameters for things that really need tweaking in early boot or are _needed_ to boot.

...

Quoted 1 line

Is module param good enough or it should be sysctl?

Doesn't matter to me much. sysctls seem to be the easiest things to configure and make persistent. I'm not sure if the normal /etc/modprobe.d things work for built-in features, but if they do I'd say a modparam is fine.

author maintainer
#11 lore raw

PATCH v2 2/3 ima: introduce multi-page collect buffers

In reply to Dmitry Kasatkin

Use of multiple-page collect buffers reduces:
1) the number of block IO requests
2) the number of asynchronous hash update requests

Second is important for HW accelerated hashing, because significant amount of time is spent for preparation of hash update operation, which includes configuring acceleration HW, DMA engine, etc... Thus, HW accelerators are more efficient when working on large chunks of data.

This patch introduces usage of multi-page collect buffers. Buffer size can be specified by providing additional option to the 'ima_ahash=' kernel parameter. 'ima_ahash=2048,16384' specifies that minimal file size to use ahash is 2048 byes and buffer size is 16384 bytes. Default buffer size is 4096 bytes.

---
 Documentation/kernel-parameters.txt |  3 +-
 security/integrity/ima/ima_crypto.c | 85 ++++++++++++++++++++++++++++++++++---
 2 files changed, 81 insertions(+), 7 deletions(-)
Documentation/kernel-parameters.txt +2 -1
index b406f5c..529ba58 100644
@@ -1292,9 +1292,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 			Set number of hash buckets for inode cache.
 
 	ima_ahash=	[IMA] Asynchronous hash usage parameters
-			Format: <min_file_size>
+			Format: <min_file_size>[,<bufsize>]
 			Set the minimal file size when use asynchronous hash.
 			If ima_ahash is not provided, ahash usage is disabled.
+			bufsize - hashing buffer size. 4k if not specified.
 
 	ima_appraise=	[IMA] appraise integrity measurements
 			Format: { "off" | "enforce" | "fix" }
security/integrity/ima/ima_crypto.c +79 -6

126 lines not on this page · raw message

Signature · 1 line
1.9.1
#12 lore raw

Re: [PATCH v2 2/3] ima: introduce multi-page collect buffers

Quoted 58 lines · Dmitry Kasatkin

Use of multiple-page collect buffers reduces:
1) the number of block IO requests
2) the number of asynchronous hash update requests

Second is important for HW accelerated hashing, because significant amount of time is spent for preparation of hash update operation, which includes configuring acceleration HW, DMA engine, etc... Thus, HW accelerators are more efficient when working on large chunks of data.

This patch introduces usage of multi-page collect buffers. Buffer size can be specified by providing additional option to the 'ima_ahash=' kernel parameter. 'ima_ahash=2048,16384' specifies that minimal file size to use ahash is 2048 byes and buffer size is 16384 bytes. Default buffer size is 4096 bytes.

---
 Documentation/kernel-parameters.txt |  3 +-
 security/integrity/ima/ima_crypto.c | 85 ++++++++++++++++++++++++++++++++++---
 2 files changed, 81 insertions(+), 7 deletions(-)
Documentation/kernel-parameters.txt +2 -1
index b406f5c..529ba58 100644
@@ -1292,9 +1292,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 			Set number of hash buckets for inode cache.
 
 	ima_ahash=	[IMA] Asynchronous hash usage parameters
-			Format: <min_file_size>
+			Format: <min_file_size>[,<bufsize>]
 			Set the minimal file size when use asynchronous hash.
 			If ima_ahash is not provided, ahash usage is disabled.
+			bufsize - hashing buffer size. 4k if not specified.
 
 	ima_appraise=	[IMA] appraise integrity measurements
 			Format: { "off" | "enforce" | "fix" }
security/integrity/ima/ima_crypto.c +2 -1
index 5eb19b4..41f2695 100644
@@ -25,7 +25,6 @@
 #include <crypto/hash_info.h>
 #include "ima.h"
 
-
 struct ahash_completion {
 	struct completion completion;
 	int err;
@@ -36,14 +35,24 @@ static struct crypto_ahash *ima_ahash_tfm;
 
 /* minimal file size for ahash use */
 static loff_t ima_ahash_size;
+/* default is 0 - 1 page. */
+static int ima_max_order;
 
 static int __init ima_ahash_setup(char *str)

security/integrity/ima/ima_crypto.c static struct crypto_ahash *ima_ahash_tfm; · line 50

Quoted 10 lines
security/integrity/ima/ima_crypto.c +8 -1
 {
-	int rc;
+	int ints[3] = { 0, };
+
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	if (!ints[0])
+		return 0;
+
+	ima_ahash_size = ints[1];
+	ima_max_order = get_order(ints[2]);

get_options() returns the number of options processed in ints[0]. Before assigning ima_max_order, we normally check to see if it exists. I guess in this case it doesn't matter since it would return 0 anyway.

Should there be any value checking here? Should the values be some multiple of 1024?

Quoted 85 lines
security/integrity/ima/ima_crypto.c +63 -2
-	rc = kstrtoll(str, 10, &ima_ahash_size);
+	pr_info("ima_ahash: minsize=%lld, bufsize=%lu\n",
+		ima_ahash_size, (PAGE_SIZE << ima_max_order));
 
-	return !rc;
+	return 1;
 }
 __setup("ima_ahash=", ima_ahash_setup);
 
@@ -166,6 +175,65 @@ static int ahash_wait(int err, struct ahash_completion *res)
 	return err;
 }
 
+/**
+ * ima_alloc_pages() - Allocated contiguous pages.
+ * @max_size:       Maximum amount of memory to allocate.
+ * @allocated_size: Returned size of actual allocation.
+ * @last_warn:      Should the min_size allocation warn or not.
+ *
+ * Tries to do opportunistic allocation for memory first trying to allocate
+ * max_size amount of memory and then splitting that until zero order is
+ * reached. Allocation is tried without generating allocation warnings unless
+ * last_warn is set. Last_warn set affects only last allocation of zero order.
+ *
+ * Return pointer to allocated memory, or NULL on failure.
+ */
+static void *ima_alloc_pages(loff_t max_size, size_t *allocated_size,
+			     int last_warn)
+{
+	void *ptr;
+	unsigned int order;
+	gfp_t gfp_mask = __GFP_NOWARN | __GFP_WAIT | __GFP_NORETRY;
+
+	order = min(get_order(max_size), ima_max_order);
+
+	for (; order; order--) {
+		ptr = (void *)__get_free_pages(gfp_mask, order);
+		if (ptr) {
+			*allocated_size = PAGE_SIZE << order;
+			return ptr;
+		}
+	}
+
+	/* order is zero - one page */
+
+	gfp_mask = GFP_KERNEL;
+
+	if (!last_warn)
+		gfp_mask |= __GFP_NOWARN;
+
+	ptr = (void *)__get_free_pages(gfp_mask, 0);
+	if (ptr) {
+		*allocated_size = PAGE_SIZE;
+		return ptr;
+	}
+
+	*allocated_size = 0;
+	return NULL;
+}
+
+/**
+ * ima_free_pages() - Free pages allocated by ima_alloc_pages().
+ * @ptr:  Pointer to allocated pages.
+ * @size: Size of allocated buffer.
+ */
+static void ima_free_pages(void *ptr, size_t size)
+{
+	if (!ptr)
+		return;
+	free_pages((unsigned long)ptr, get_order(size));
+}
+
 static int ima_calc_file_hash_atfm(struct file *file,
 				   struct ima_digest_data *hash,
 				   struct crypto_ahash *tfm)
@@ -176,6 +244,7 @@ static int ima_calc_file_hash_atfm(struct file *file,
 	struct ahash_request *req;
 	struct scatterlist sg[1];
 	struct ahash_completion res;
+	size_t rbuf_size;
 
 	hash->length = crypto_ahash_digestsize(tfm);
 

security/integrity/ima/ima_crypto.c static int ima_calc_file_hash_atfm(struct file *file, · line 272

Quoted 8 lines
security/integrity/ima/ima_crypto.c +4 -1
@@ -197,7 +266,11 @@ static int ima_calc_file_hash_atfm(struct file *file,
 	if (i_size == 0)
 		goto out2;
 
-	rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	/*
+	 * Try to allocate maximum size of memory, fail if not even single
+	 * page cannot be allocated.
+	 */

The comment is nice explanation, but might be unnecessary if there was a function comment.

Mimi

Quoted 13 lines
security/integrity/ima/ima_crypto.c +2 -1
+	rbuf = ima_alloc_pages(i_size, &rbuf_size, 1);
 	if (!rbuf) {
 		rc = -ENOMEM;
 		goto out1;
@@ -226,7 +299,7 @@ static int ima_calc_file_hash_atfm(struct file *file,
 	}
 	if (read)
 		file->f_mode &= ~FMODE_READ;
-	kfree(rbuf);
+	ima_free_pages(rbuf, rbuf_size);
 out2:
 	if (!rc) {
 		ahash_request_set_crypt(req, NULL, hash->digest, 0);
author maintainer
#13 lore raw

Re: [PATCH v2 2/3] ima: introduce multi-page collect buffers

Quoted 74 lines · Mimi Zohar
Quoted 58 lines · Dmitry Kasatkin

Use of multiple-page collect buffers reduces:
1) the number of block IO requests
2) the number of asynchronous hash update requests

Second is important for HW accelerated hashing, because significant amount of time is spent for preparation of hash update operation, which includes configuring acceleration HW, DMA engine, etc... Thus, HW accelerators are more efficient when working on large chunks of data.

This patch introduces usage of multi-page collect buffers. Buffer size can be specified by providing additional option to the 'ima_ahash=' kernel parameter. 'ima_ahash=2048,16384' specifies that minimal file size to use ahash is 2048 byes and buffer size is 16384 bytes. Default buffer size is 4096 bytes.

---
 Documentation/kernel-parameters.txt |  3 +-
 security/integrity/ima/ima_crypto.c | 85 ++++++++++++++++++++++++++++++++++---
 2 files changed, 81 insertions(+), 7 deletions(-)
Documentation/kernel-parameters.txt +2 -1
index b406f5c..529ba58 100644
@@ -1292,9 +1292,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
                      Set number of hash buckets for inode cache.
 
      ima_ahash=      [IMA] Asynchronous hash usage parameters
-                     Format: <min_file_size>
+                     Format: <min_file_size>[,<bufsize>]
                      Set the minimal file size when use asynchronous hash.
                      If ima_ahash is not provided, ahash usage is disabled.
+                     bufsize - hashing buffer size. 4k if not specified.
 
      ima_appraise=   [IMA] appraise integrity measurements
                      Format: { "off" | "enforce" | "fix" }
security/integrity/ima/ima_crypto.c +2 -1
index 5eb19b4..41f2695 100644
@@ -25,7 +25,6 @@
 #include <crypto/hash_info.h>
 #include "ima.h"
 
-
 struct ahash_completion {
      struct completion completion;
      int err;
@@ -36,14 +35,24 @@ static struct crypto_ahash *ima_ahash_tfm;
 
 /* minimal file size for ahash use */
 static loff_t ima_ahash_size;
+/* default is 0 - 1 page. */
+static int ima_max_order;
 
 static int __init ima_ahash_setup(char *str)

security/integrity/ima/ima_crypto.c static struct crypto_ahash *ima_ahash_tfm; · line 50

Quoted 10 lines
security/integrity/ima/ima_crypto.c +8 -1
 {
-     int rc;
+     int ints[3] = { 0, };
+
+     str = get_options(str, ARRAY_SIZE(ints), ints);
+     if (!ints[0])
+             return 0;
+
+     ima_ahash_size = ints[1];
+     ima_max_order = get_order(ints[2]);

get_options() returns the number of options processed in ints[0]. Before assigning ima_max_order, we normally check to see if it exists. I guess in this case it doesn't matter since it would return 0 anyway.

Quoted 3 lines

Should there be any value checking here? Should the values be some multiple of 1024?

It is a goo question. I was looking how get_options is used. Here as ints initialized to 0, get_order returns the same value for anyhing which is rounded up to PAGE_SIZE. So it works in all cases.

But if we now go to module param or sysctl, then this can be discarded?

Thanks for reviewing.

Quoted 114 lines
Quoted 85 lines
security/integrity/ima/ima_crypto.c +63 -2
-     rc = kstrtoll(str, 10, &ima_ahash_size);
+     pr_info("ima_ahash: minsize=%lld, bufsize=%lu\n",
+             ima_ahash_size, (PAGE_SIZE << ima_max_order));
 
-     return !rc;
+     return 1;
 }
 __setup("ima_ahash=", ima_ahash_setup);
 
@@ -166,6 +175,65 @@ static int ahash_wait(int err, struct ahash_completion *res)
      return err;
 }
 
+/**
+ * ima_alloc_pages() - Allocated contiguous pages.
+ * @max_size:       Maximum amount of memory to allocate.
+ * @allocated_size: Returned size of actual allocation.
+ * @last_warn:      Should the min_size allocation warn or not.
+ *
+ * Tries to do opportunistic allocation for memory first trying to allocate
+ * max_size amount of memory and then splitting that until zero order is
+ * reached. Allocation is tried without generating allocation warnings unless
+ * last_warn is set. Last_warn set affects only last allocation of zero order.
+ *
+ * Return pointer to allocated memory, or NULL on failure.
+ */
+static void *ima_alloc_pages(loff_t max_size, size_t *allocated_size,
+                          int last_warn)
+{
+     void *ptr;
+     unsigned int order;
+     gfp_t gfp_mask = __GFP_NOWARN | __GFP_WAIT | __GFP_NORETRY;
+
+     order = min(get_order(max_size), ima_max_order);
+
+     for (; order; order--) {
+             ptr = (void *)__get_free_pages(gfp_mask, order);
+             if (ptr) {
+                     *allocated_size = PAGE_SIZE << order;
+                     return ptr;
+             }
+     }
+
+     /* order is zero - one page */
+
+     gfp_mask = GFP_KERNEL;
+
+     if (!last_warn)
+             gfp_mask |= __GFP_NOWARN;
+
+     ptr = (void *)__get_free_pages(gfp_mask, 0);
+     if (ptr) {
+             *allocated_size = PAGE_SIZE;
+             return ptr;
+     }
+
+     *allocated_size = 0;
+     return NULL;
+}
+
+/**
+ * ima_free_pages() - Free pages allocated by ima_alloc_pages().
+ * @ptr:  Pointer to allocated pages.
+ * @size: Size of allocated buffer.
+ */
+static void ima_free_pages(void *ptr, size_t size)
+{
+     if (!ptr)
+             return;
+     free_pages((unsigned long)ptr, get_order(size));
+}
+
 static int ima_calc_file_hash_atfm(struct file *file,
                                 struct ima_digest_data *hash,
                                 struct crypto_ahash *tfm)
@@ -176,6 +244,7 @@ static int ima_calc_file_hash_atfm(struct file *file,
      struct ahash_request *req;
      struct scatterlist sg[1];
      struct ahash_completion res;
+     size_t rbuf_size;
 
      hash->length = crypto_ahash_digestsize(tfm);
 

security/integrity/ima/ima_crypto.c static int ima_calc_file_hash_atfm(struct file *file, · line 272

Quoted 8 lines
security/integrity/ima/ima_crypto.c +4 -1
@@ -197,7 +266,11 @@ static int ima_calc_file_hash_atfm(struct file *file,
      if (i_size == 0)
              goto out2;
 
-     rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+     /*
+      * Try to allocate maximum size of memory, fail if not even single
+      * page cannot be allocated.
+      */

The comment is nice explanation, but might be unnecessary if there was a function comment.

Mimi

Quoted 13 lines
security/integrity/ima/ima_crypto.c +2 -1
+     rbuf = ima_alloc_pages(i_size, &rbuf_size, 1);
      if (!rbuf) {
              rc = -ENOMEM;
              goto out1;
@@ -226,7 +299,7 @@ static int ima_calc_file_hash_atfm(struct file *file,
      }
      if (read)
              file->f_mode &= ~FMODE_READ;
-     kfree(rbuf);
+     ima_free_pages(rbuf, rbuf_size);
 out2:
      if (!rc) {
              ahash_request_set_crypt(req, NULL, hash->digest, 0);
Signature · 2 lines
Thanks,
Dmitry
author maintainer
#14 lore raw

PATCH v2 3/3 ima: provide double buffering for hash calculation

In reply to Dmitry Kasatkin

Asynchronous hash API allows initiate hash calculation and perform other tasks while hash is calculated.

This patch introduces usage of double buffering for simultaneous hashing and reading of the next chunk of data from the storage.

---
 security/integrity/ima/ima_crypto.c | 63 +++++++++++++++++++++++++++----------
 1 file changed, 47 insertions(+), 16 deletions(-)
security/integrity/ima/ima_crypto.c +47 -16
index 41f2695..69d55a7 100644
@@ -239,12 +239,12 @@ static int ima_calc_file_hash_atfm(struct file *file,
 				   struct crypto_ahash *tfm)
 {
 	loff_t i_size, offset;
-	char *rbuf;
-	int rc, read = 0, rbuf_len;
+	char *rbuf[2] = { NULL, };
+	int rc, read = 0, rbuf_len, active = 0, ahash_rc = 0;
 	struct ahash_request *req;
 	struct scatterlist sg[1];
 	struct ahash_completion res;
-	size_t rbuf_size;
+	size_t rbuf_size[2];
 
 	hash->length = crypto_ahash_digestsize(tfm);
 
@@ -270,36 +270,67 @@ static int ima_calc_file_hash_atfm(struct file *file,
 	 * Try to allocate maximum size of memory, fail if not even single
 	 * page cannot be allocated.
 	 */
-	rbuf = ima_alloc_pages(i_size, &rbuf_size, 1);
-	if (!rbuf) {
+	rbuf[0] = ima_alloc_pages(i_size, &rbuf_size[0], 1);
+	if (!rbuf[0]) {
 		rc = -ENOMEM;
 		goto out1;
 	}
 
+	/* Only allocate one buffer if that is enough. */
+	if (i_size > rbuf_size[0]) {
+		/*
+		 * Try to allocate secondary buffer if that fails fallback to
+		 * using single buffering. Use previous memory allocation size
+		 * as baseline for possible allocation size.
+		 */
+		rbuf[1] = ima_alloc_pages(i_size - rbuf_size[0],
+					  &rbuf_size[1], 0);
+	}
+
 	if (!(file->f_mode & FMODE_READ)) {
 		file->f_mode |= FMODE_READ;
 		read = 1;
 	}
 
 	for (offset = 0; offset < i_size; offset += rbuf_len) {
-		rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE);
-		if (rbuf_len < 0) {
-			rc = rbuf_len;
-			break;
+		if (!rbuf[1] && offset) {
+			/* if we do not use second buffer and it is not the
+			 * the first read/request, then wait for completion
+			 */
+			rc = ahash_wait(ahash_rc, &res);
+			if (rc)
+				goto out3;
+		}
+		/* read buffer */
+		rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]);
+		rc = ima_kernel_read(file, offset, rbuf[active], rbuf_len);
+		if (rc != rbuf_len)
+			goto out3;
+
+		if (rbuf[1] && offset) {
+			/* if we use second buffer and it is not the the first
+			 * read/request, then wait for completion
+			 */
+			rc = ahash_wait(ahash_rc, &res);
+			if (rc)
+				goto out3;
 		}
-		if (rbuf_len == 0)
-			break;
 
-		sg_init_one(&sg[0], rbuf, rbuf_len);
+		sg_init_one(&sg[0], rbuf[active], rbuf_len);
 		ahash_request_set_crypt(req, sg, NULL, rbuf_len);
 
-		rc = ahash_wait(crypto_ahash_update(req), &res);
-		if (rc)
-			break;
+		ahash_rc = crypto_ahash_update(req);
+
+		if (rbuf[1])
+			active = !active; /* swap buffers, if we use two */
 	}
+	/* wait for the last request to complete */
+	rc = ahash_wait(ahash_rc, &res);
+out3:
 	if (read)
 		file->f_mode &= ~FMODE_READ;
-	ima_free_pages(rbuf, rbuf_size);
+	ima_free_pages(rbuf[0], rbuf_size[0]);
+	ima_free_pages(rbuf[1], rbuf_size[1]);
 out2:
 	if (!rc) {
 		ahash_request_set_crypt(req, NULL, hash->digest, 0);
Signature · 1 line
1.9.1