crypto: ablkcipher - fix crash flushing dcache in error path
commit 318abdfbe708aaaa652c79fb500e9bd60521f9dc upstream.
Like the skcipher_walk and blkcipher_walk cases:
scatterwalk_done() is only meant to be called after a nonzero number of
bytes have been processed, since scatterwalk_pagedone() will flush the
dcache of the *previous* page. But in the error case of
ablkcipher_walk_done(), e.g. if the input wasn't an integer number of
blocks, scatterwalk_done() was actually called after advancing 0 bytes.
This caused a crash ("BUG: unable to handle kernel paging request")
during '!PageSlab(page)' on architectures like arm and arm64 that define
ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE, provided that the input was
page-aligned as in that case walk->offset == 0.
Fix it by reorganizing ablkcipher_walk_done() to skip the
scatterwalk_advance() and scatterwalk_done() if an error has occurred.
Reported-by: Liu Chao <liuchao741@huawei.com>
Fixes: bf06099db1
("crypto: skcipher - Add ablkcipher_walk interfaces")
Cc: <stable@vger.kernel.org> # v2.6.35+
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
a55a251282
commit
930787c9cd
1 changed files with 26 additions and 31 deletions
|
@ -73,11 +73,9 @@ static inline u8 *ablkcipher_get_spot(u8 *start, unsigned int len)
|
||||||
return max(start, end_page);
|
return max(start, end_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline unsigned int ablkcipher_done_slow(struct ablkcipher_walk *walk,
|
static inline void ablkcipher_done_slow(struct ablkcipher_walk *walk,
|
||||||
unsigned int bsize)
|
unsigned int n)
|
||||||
{
|
{
|
||||||
unsigned int n = bsize;
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
unsigned int len_this_page = scatterwalk_pagelen(&walk->out);
|
unsigned int len_this_page = scatterwalk_pagelen(&walk->out);
|
||||||
|
|
||||||
|
@ -89,17 +87,13 @@ static inline unsigned int ablkcipher_done_slow(struct ablkcipher_walk *walk,
|
||||||
n -= len_this_page;
|
n -= len_this_page;
|
||||||
scatterwalk_start(&walk->out, sg_next(walk->out.sg));
|
scatterwalk_start(&walk->out, sg_next(walk->out.sg));
|
||||||
}
|
}
|
||||||
|
|
||||||
return bsize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline unsigned int ablkcipher_done_fast(struct ablkcipher_walk *walk,
|
static inline void ablkcipher_done_fast(struct ablkcipher_walk *walk,
|
||||||
unsigned int n)
|
unsigned int n)
|
||||||
{
|
{
|
||||||
scatterwalk_advance(&walk->in, n);
|
scatterwalk_advance(&walk->in, n);
|
||||||
scatterwalk_advance(&walk->out, n);
|
scatterwalk_advance(&walk->out, n);
|
||||||
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ablkcipher_walk_next(struct ablkcipher_request *req,
|
static int ablkcipher_walk_next(struct ablkcipher_request *req,
|
||||||
|
@ -109,39 +103,40 @@ int ablkcipher_walk_done(struct ablkcipher_request *req,
|
||||||
struct ablkcipher_walk *walk, int err)
|
struct ablkcipher_walk *walk, int err)
|
||||||
{
|
{
|
||||||
struct crypto_tfm *tfm = req->base.tfm;
|
struct crypto_tfm *tfm = req->base.tfm;
|
||||||
unsigned int nbytes = 0;
|
unsigned int n; /* bytes processed */
|
||||||
|
bool more;
|
||||||
|
|
||||||
if (likely(err >= 0)) {
|
if (unlikely(err < 0))
|
||||||
unsigned int n = walk->nbytes - err;
|
goto finish;
|
||||||
|
|
||||||
if (likely(!(walk->flags & ABLKCIPHER_WALK_SLOW)))
|
n = walk->nbytes - err;
|
||||||
n = ablkcipher_done_fast(walk, n);
|
walk->total -= n;
|
||||||
else if (WARN_ON(err)) {
|
more = (walk->total != 0);
|
||||||
|
|
||||||
|
if (likely(!(walk->flags & ABLKCIPHER_WALK_SLOW))) {
|
||||||
|
ablkcipher_done_fast(walk, n);
|
||||||
|
} else {
|
||||||
|
if (WARN_ON(err)) {
|
||||||
|
/* unexpected case; didn't process all bytes */
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto err;
|
goto finish;
|
||||||
} else
|
}
|
||||||
n = ablkcipher_done_slow(walk, n);
|
ablkcipher_done_slow(walk, n);
|
||||||
|
|
||||||
nbytes = walk->total - n;
|
|
||||||
err = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scatterwalk_done(&walk->in, 0, nbytes);
|
scatterwalk_done(&walk->in, 0, more);
|
||||||
scatterwalk_done(&walk->out, 1, nbytes);
|
scatterwalk_done(&walk->out, 1, more);
|
||||||
|
|
||||||
err:
|
if (more) {
|
||||||
walk->total = nbytes;
|
|
||||||
walk->nbytes = nbytes;
|
|
||||||
|
|
||||||
if (nbytes) {
|
|
||||||
crypto_yield(req->base.flags);
|
crypto_yield(req->base.flags);
|
||||||
return ablkcipher_walk_next(req, walk);
|
return ablkcipher_walk_next(req, walk);
|
||||||
}
|
}
|
||||||
|
err = 0;
|
||||||
|
finish:
|
||||||
|
walk->nbytes = 0;
|
||||||
if (walk->iv != req->info)
|
if (walk->iv != req->info)
|
||||||
memcpy(req->info, walk->iv, tfm->crt_ablkcipher.ivsize);
|
memcpy(req->info, walk->iv, tfm->crt_ablkcipher.ivsize);
|
||||||
kfree(walk->iv_buffer);
|
kfree(walk->iv_buffer);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(ablkcipher_walk_done);
|
EXPORT_SYMBOL_GPL(ablkcipher_walk_done);
|
||||||
|
|
Loading…
Add table
Reference in a new issue