lkml.org 
[lkml]   [2018]   [Aug]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 4.18 19/22] crypto: skcipher - fix crash flushing dcache in error path
    Date
    4.18-stable review patch.  If anyone has any objections, please let me know.

    ------------------

    From: Eric Biggers <ebiggers@google.com>

    commit 8088d3dd4d7c6933a65aa169393b5d88d8065672 upstream.

    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: b286d8b1a690 ("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>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

    ---
    crypto/skcipher.c | 49 +++++++++++++++++++++++++------------------------
    1 file changed, 25 insertions(+), 24 deletions(-)

    --- a/crypto/skcipher.c
    +++ b/crypto/skcipher.c
    @@ -95,7 +95,7 @@ static inline u8 *skcipher_get_spot(u8 *
    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 skc
    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))

    \
     
     \ /
      Last update: 2018-08-16 20:52    [W:2.485 / U:0.024 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site