crypto: skcipher - fix crash flushing dcache in error path
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
skcipher_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 skcipher_walk_done() to skip the
scatterwalk_advance() and scatterwalk_done() if an error has occurred.
This bug was found by syzkaller fuzzing.
Reproducer, assuming ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE:
#include <linux/if_alg.h>
#include <sys/socket.h>
#include <unistd.h>
int main()
{
struct sockaddr_alg addr = {
.salg_type = "skcipher",
.salg_name = "cbc(aes-generic)",
};
char buffer[4096] __attribute__((aligned(4096))) = { 0 };
int fd;
fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(fd, (void *)&addr, sizeof(addr));
setsockopt(fd, SOL_ALG, ALG_SET_KEY, buffer, 16);
fd = accept(fd, NULL, NULL);
write(fd, buffer, 15);
read(fd, buffer, 15);
}
Reported-by: Liu Chao <liuchao741@huawei.com>
Fixes: b286d8b1a6
("crypto: skcipher - Add skcipher walk interface")
Cc: <stable@vger.kernel.org> # v4.10+
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
parent
2a57c0be22
commit
8088d3dd4d
|
@ -95,7 +95,7 @@ static inline u8 *skcipher_get_spot(u8 *start, unsigned int len)
|
|||
return max(start, end_page);
|
||||
}
|
||||
|
||||
static int skcipher_done_slow(struct skcipher_walk *walk, unsigned int bsize)
|
||||
static void skcipher_done_slow(struct skcipher_walk *walk, unsigned int bsize)
|
||||
{
|
||||
u8 *addr;
|
||||
|
||||
|
@ -103,23 +103,24 @@ static int skcipher_done_slow(struct skcipher_walk *walk, unsigned int bsize)
|
|||
addr = skcipher_get_spot(addr, bsize);
|
||||
scatterwalk_copychunks(addr, &walk->out, bsize,
|
||||
(walk->flags & SKCIPHER_WALK_PHYS) ? 2 : 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skcipher_walk_done(struct skcipher_walk *walk, int err)
|
||||
{
|
||||
unsigned int n = walk->nbytes - err;
|
||||
unsigned int nbytes;
|
||||
unsigned int n; /* bytes processed */
|
||||
bool more;
|
||||
|
||||
nbytes = walk->total - n;
|
||||
if (unlikely(err < 0))
|
||||
goto finish;
|
||||
|
||||
if (unlikely(err < 0)) {
|
||||
nbytes = 0;
|
||||
n = 0;
|
||||
} else if (likely(!(walk->flags & (SKCIPHER_WALK_PHYS |
|
||||
SKCIPHER_WALK_SLOW |
|
||||
SKCIPHER_WALK_COPY |
|
||||
SKCIPHER_WALK_DIFF)))) {
|
||||
n = walk->nbytes - err;
|
||||
walk->total -= n;
|
||||
more = (walk->total != 0);
|
||||
|
||||
if (likely(!(walk->flags & (SKCIPHER_WALK_PHYS |
|
||||
SKCIPHER_WALK_SLOW |
|
||||
SKCIPHER_WALK_COPY |
|
||||
SKCIPHER_WALK_DIFF)))) {
|
||||
unmap_src:
|
||||
skcipher_unmap_src(walk);
|
||||
} else if (walk->flags & SKCIPHER_WALK_DIFF) {
|
||||
|
@ -131,28 +132,28 @@ unmap_src:
|
|||
skcipher_unmap_dst(walk);
|
||||
} else if (unlikely(walk->flags & SKCIPHER_WALK_SLOW)) {
|
||||
if (WARN_ON(err)) {
|
||||
/* unexpected case; didn't process all bytes */
|
||||
err = -EINVAL;
|
||||
nbytes = 0;
|
||||
} else
|
||||
n = skcipher_done_slow(walk, n);
|
||||
goto finish;
|
||||
}
|
||||
skcipher_done_slow(walk, n);
|
||||
goto already_advanced;
|
||||
}
|
||||
|
||||
if (err > 0)
|
||||
err = 0;
|
||||
|
||||
walk->total = nbytes;
|
||||
walk->nbytes = nbytes;
|
||||
|
||||
scatterwalk_advance(&walk->in, n);
|
||||
scatterwalk_advance(&walk->out, n);
|
||||
scatterwalk_done(&walk->in, 0, nbytes);
|
||||
scatterwalk_done(&walk->out, 1, nbytes);
|
||||
already_advanced:
|
||||
scatterwalk_done(&walk->in, 0, more);
|
||||
scatterwalk_done(&walk->out, 1, more);
|
||||
|
||||
if (nbytes) {
|
||||
if (more) {
|
||||
crypto_yield(walk->flags & SKCIPHER_WALK_SLEEP ?
|
||||
CRYPTO_TFM_REQ_MAY_SLEEP : 0);
|
||||
return skcipher_walk_next(walk);
|
||||
}
|
||||
err = 0;
|
||||
finish:
|
||||
walk->nbytes = 0;
|
||||
|
||||
/* Short-circuit for the common/fast path. */
|
||||
if (!((unsigned long)walk->buffer | (unsigned long)walk->page))
|
||||
|
|
Loading…
Reference in New Issue