diff options
-rw-r--r-- | security/integrity/ima/ima_crypto.c | 65 |
1 files changed, 49 insertions, 16 deletions
diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 84b938f86bea..0bd732843fe7 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -253,12 +253,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); @@ -284,36 +284,69 @@ static int ima_calc_file_hash_atfm(struct file *file, * Try to allocate maximum size of memory. * Fail if even a 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) { + /* Not using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + 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) { + /* Using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + 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 update 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); |