FAILED: patch "[PATCH] crypto: skcipher - fix crash flushing dcache in error path" failed to apply to 3.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 3.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
Quoted 1 line
From
8088d3dd4d7c6933a65aa169393b5d88d8065672Mon Sep 17 00:00:00 2001
From: Eric Biggers <[email protected]>
Date: Mon, 23 Jul 2018 10:54:56 -0700
Subject: [PATCH] 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);
}crypto/skcipher.c +27 -26
@@ -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; - - nbytes = walk->total - n; - -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 )))) { +unsigned int n;/* bytes processed */ +bool more; + +if (unlikely (err <0 )) + goto finish; + + 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 @@ int skcipher_walk_done(struct skcipher_walk *walk, int err)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))