From patchwork Wed Jan 13 16:21:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrey Konovalov X-Patchwork-Id: 12017329 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-17.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_ADSP_CUSTOM_MED,DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3A361C433E6 for ; Wed, 13 Jan 2021 16:28:11 +0000 (UTC) Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id CACC523370 for ; Wed, 13 Jan 2021 16:28:10 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org CACC523370 Authentication-Results: mail.kernel.org; dmarc=fail (p=reject dis=none) header.from=google.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:Cc:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:To:From:Subject:References:Mime-Version:Message-Id: In-Reply-To:Date:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=ZZ7Icl8MhVxFEOEI8a68kTwvg47U9r3izOQlskM+B+I=; b=yoYLyLx1PTvvWjmM81hbNxv2n vzdqwauHQbAk71WAdj8MbpqKgjCuAddd/c31gbjJzQzvax1Z7g7nhge1T5k7jzYmOwazu0eYgYTC6 uTVk70kM3HHG+4JORGxubx9Y9Wm6mm7GQD5/ho+hZyd7Q6y8QyyeYraEe1hTWjhpPDC/SJbpqXUyg vi7TMOYd6mTdrIt9JFgoxnJgDjJM0W/giP/++UBSeuz657YYDj/0pF75zoDmwJGeU+7GjVoYNU/PR ECURrvQfFILUE2VehGW6eVkF3p7StntCK03FVt83zjONBEvmqLun7k7aLwJ3sPemgcx9f8melNLzo XsItyQl2Q==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1kzixQ-0000ET-US; Wed, 13 Jan 2021 16:25:17 +0000 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1kziwy-0008Tb-7g for linux-arm-kernel@merlin.infradead.org; Wed, 13 Jan 2021 16:24:48 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Content-Type:Cc:To:From:Subject: References:Mime-Version:Message-Id:In-Reply-To:Date:Sender:Reply-To: Content-Transfer-Encoding:Content-ID:Content-Description; bh=zPFgqVWTe4vdkLS3+pFA2cFBjG4oBenQZmF7SeNlQSE=; b=MsDhU17NDesLFbs8OkjDwFTsKl 8dZtnkUtWjkNACKAovLtpbaAJUx+57ymyi8IM/wlr9SVvZodrYoxOHOkcxknZzpTmIc23y6ty6liT Ujqz/JMHfU2jz7RKyfzPp53/lQ72OtYbrEMScdwf1q+0iJUnK+xjU9UANIO7RpnxF3ZaYlX3mwDh3 LVycwfdjKwaAJn32N6znYi55ggZynlciYWiU7rkPU8Zu02s+QY9B1/xduv26TbQ/bdxPG4n2cnf7+ 8zPDdmMdMg7btROS0wlqEP1uIBDL/XHZuFV2d9r7eyOzALw0+sFEe8Jffq0d3ltLfY8cH5yLgM0xM nmRHljTg==; Received: from mail-wm1-x34a.google.com ([2a00:1450:4864:20::34a]) by casper.infradead.org with esmtps (Exim 4.94 #2 (Red Hat Linux)) id 1kziul-006SNU-Lz for linux-arm-kernel@lists.infradead.org; Wed, 13 Jan 2021 16:23:21 +0000 Received: by mail-wm1-x34a.google.com with SMTP id u18so1010733wmu.4 for ; Wed, 13 Jan 2021 08:22:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=sender:date:in-reply-to:message-id:mime-version:references:subject :from:to:cc; bh=zPFgqVWTe4vdkLS3+pFA2cFBjG4oBenQZmF7SeNlQSE=; b=OsvQLtdizsxjjP/SOBDX+TcBbK9aTHe3xYEJRPuaKxHy8j6nZ4NX+T1EPfdVYE1iu+ LPRlwtM/hOqnSeKwUuPmhpUFC4wBWoxiwbBoYRZh3uM/7hJ+CPplLKqIfHgUDaFpzupz 4qF5vaee93l8k8Oh/O5+gcvDvh5GFwC0Zgru/mXinRkEbl4LeGZmjQkO+df5PdhnTEYQ jURzxzrGnGNjl01ZJqxO4mpHt/A/+ErqUeT7M2WFLF0DAKO6E1EGWgIZnP2/o5h0cwPe RM7cTsWqUa5PyX5/G+ymbwbVsHx9071wJvflNnjukL/BPqGtThXhDQNSpe1zIhn+RiJy TODw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=zPFgqVWTe4vdkLS3+pFA2cFBjG4oBenQZmF7SeNlQSE=; b=B0tFX1/c6QvvfHnJAWzZx1hF2JB0gPgydaBoeYhn6UHudaPT1/QgsnWxhOZPkS+WXO swkrSYysWzZCMaWMDq8L/QT4PX5y7O/av0nB7tEfRTHbmzAmUh06+mhDi7u6fMjpErfF Cd82OikXA1gBZjZeRkZqEl8EobAFARv9kmCuCb2Qa16VJC94RHCxJeLV6fX9asRyTrfI s5/S7S/uaBb/caztCZfMlpoYkSiwM5VJ/VEXB5FQCqfhV4Gdt8k+Xk1T508bSYq4lQlc RqWjsZ+Ivgg3tV3op9yF09DTo/RQMobr4B679Eo0YN1EF8dh0YocKI6L49nfXORgyipq p2Pg== X-Gm-Message-State: AOAM533gD73PJWoQ4soGiA3F4N7PFEE1BxdeX5hXYBUwIzMcEQ7TYCrN g0HAujbjUkxTJ3iLpM80r5Ss+1ErXzqAa12Z X-Google-Smtp-Source: ABdhPJyocMYNK5XpL0CmclU/x+eqt+hpjE9L1wYmS0rYJFEnXbLgobgpkg6CAskodpSosBVH8m3P9nHDfmXOCjMa X-Received: from andreyknvl3.muc.corp.google.com ([2a00:79e0:15:13:7220:84ff:fe09:7e9d]) (user=andreyknvl job=sendgmr) by 2002:a1c:cc14:: with SMTP id h20mr45339wmb.180.1610554932643; Wed, 13 Jan 2021 08:22:12 -0800 (PST) Date: Wed, 13 Jan 2021 17:21:38 +0100 In-Reply-To: Message-Id: <77015767eb7cfe1cc112a564d31e749d68615a0f.1610554432.git.andreyknvl@google.com> Mime-Version: 1.0 References: X-Mailer: git-send-email 2.30.0.284.gd98b1dd5eaa7-goog Subject: [PATCH v2 11/14] kasan: fix bug detection via ksize for HW_TAGS mode From: Andrey Konovalov To: Catalin Marinas , Vincenzo Frascino , Dmitry Vyukov , Alexander Potapenko , Marco Elver X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210113_162321_124455_3964BA07 X-CRM114-Status: GOOD ( 23.43 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Branislav Rankov , Andrey Konovalov , Kevin Brodsky , Will Deacon , linux-kernel@vger.kernel.org, kasan-dev@googlegroups.com, linux-mm@kvack.org, linux-arm-kernel@lists.infradead.org, Andrey Ryabinin , Andrew Morton , Peter Collingbourne , Evgenii Stepanov Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org The currently existing kasan_check_read/write() annotations are intended to be used for kernel modules that have KASAN compiler instrumentation disabled. Thus, they are only relevant for the software KASAN modes that rely on compiler instrumentation. However there's another use case for these annotations: ksize() checks that the object passed to it is indeed accessible before unpoisoning the whole object. This is currently done via __kasan_check_read(), which is compiled away for the hardware tag-based mode that doesn't rely on compiler instrumentation. This leads to KASAN missing detecting some memory corruptions. Provide another annotation called kasan_check_byte() that is available for all KASAN modes. As the implementation rename and reuse kasan_check_invalid_free(). Use this new annotation in ksize(). Also add a new ksize_uaf() test that checks that a use-after-free is detected via ksize() itself, and via plain accesses that happen later. Link: https://linux-review.googlesource.com/id/Iaabf771881d0f9ce1b969f2a62938e99d3308ec5 Signed-off-by: Andrey Konovalov --- include/linux/kasan-checks.h | 6 ++++++ include/linux/kasan.h | 16 ++++++++++++++++ lib/test_kasan.c | 20 ++++++++++++++++++++ mm/kasan/common.c | 11 ++++++++++- mm/kasan/generic.c | 4 ++-- mm/kasan/kasan.h | 10 +++++----- mm/kasan/sw_tags.c | 6 +++--- mm/slab_common.c | 15 +++++++++------ 8 files changed, 71 insertions(+), 17 deletions(-) diff --git a/include/linux/kasan-checks.h b/include/linux/kasan-checks.h index ca5e89fb10d3..3d6d22a25bdc 100644 --- a/include/linux/kasan-checks.h +++ b/include/linux/kasan-checks.h @@ -4,6 +4,12 @@ #include +/* + * The annotations present in this file are only relevant for the software + * KASAN modes that rely on compiler instrumentation, and will be optimized + * away for the hardware tag-based KASAN mode. Use kasan_check_byte() instead. + */ + /* * __kasan_check_*: Always available when KASAN is enabled. This may be used * even in compilation units that selectively disable KASAN, but must use KASAN diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 5e0655fb2a6f..b723895b157c 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -243,6 +243,18 @@ static __always_inline void kasan_kfree_large(void *ptr, unsigned long ip) __kasan_kfree_large(ptr, ip); } +/* + * Unlike kasan_check_read/write(), kasan_check_byte() is performed even for + * the hardware tag-based mode that doesn't rely on compiler instrumentation. + */ +bool __kasan_check_byte(const void *addr, unsigned long ip); +static __always_inline bool kasan_check_byte(const void *addr, unsigned long ip) +{ + if (kasan_enabled()) + return __kasan_check_byte(addr, ip); + return true; +} + bool kasan_save_enable_multi_shot(void); void kasan_restore_multi_shot(bool enabled); @@ -299,6 +311,10 @@ static inline void *kasan_krealloc(const void *object, size_t new_size, return (void *)object; } static inline void kasan_kfree_large(void *ptr, unsigned long ip) {} +static inline bool kasan_check_byte(const void *address, unsigned long ip) +{ + return true; +} #endif /* CONFIG_KASAN */ diff --git a/lib/test_kasan.c b/lib/test_kasan.c index 63252d1fd58c..710e714dc0cb 100644 --- a/lib/test_kasan.c +++ b/lib/test_kasan.c @@ -496,6 +496,7 @@ static void kasan_global_oob(struct kunit *test) KUNIT_EXPECT_KASAN_FAIL(test, *(volatile char *)p); } +/* Check that ksize() makes the whole object accessible. */ static void ksize_unpoisons_memory(struct kunit *test) { char *ptr; @@ -514,6 +515,24 @@ static void ksize_unpoisons_memory(struct kunit *test) kfree(ptr); } +/* + * Check that a use-after-free is detected by ksize() and via normal accesses + * after it. + */ +static void ksize_uaf(struct kunit *test) +{ + char *ptr; + int size = 128 - KASAN_GRANULE_SIZE; + + ptr = kmalloc(size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); + kfree(ptr); + + KUNIT_EXPECT_KASAN_FAIL(test, ksize(ptr)); + KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result = *ptr); + KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result = *(ptr + size)); +} + static void kasan_stack_oob(struct kunit *test) { char stack_array[10]; @@ -907,6 +926,7 @@ static struct kunit_case kasan_kunit_test_cases[] = { KUNIT_CASE(kasan_alloca_oob_left), KUNIT_CASE(kasan_alloca_oob_right), KUNIT_CASE(ksize_unpoisons_memory), + KUNIT_CASE(ksize_uaf), KUNIT_CASE(kmem_cache_double_free), KUNIT_CASE(kmem_cache_invalid_free), KUNIT_CASE(kasan_memchr), diff --git a/mm/kasan/common.c b/mm/kasan/common.c index eedc3e0fe365..b18189ef3a92 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -345,7 +345,7 @@ static bool ____kasan_slab_free(struct kmem_cache *cache, void *object, if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU)) return false; - if (kasan_check_invalid_free(tagged_object)) { + if (!kasan_byte_accessible(tagged_object)) { kasan_report_invalid_free(tagged_object, ip); return true; } @@ -490,3 +490,12 @@ void __kasan_kfree_large(void *ptr, unsigned long ip) kasan_report_invalid_free(ptr, ip); /* The object will be poisoned by kasan_free_pages(). */ } + +bool __kasan_check_byte(const void *address, unsigned long ip) +{ + if (!kasan_byte_accessible(address)) { + kasan_report((unsigned long)address, 1, false, ip); + return false; + } + return true; +} diff --git a/mm/kasan/generic.c b/mm/kasan/generic.c index acab8862dc67..3f17a1218055 100644 --- a/mm/kasan/generic.c +++ b/mm/kasan/generic.c @@ -185,11 +185,11 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write, return check_region_inline(addr, size, write, ret_ip); } -bool kasan_check_invalid_free(void *addr) +bool kasan_byte_accessible(const void *addr) { s8 shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(addr)); - return shadow_byte < 0 || shadow_byte >= KASAN_GRANULE_SIZE; + return shadow_byte >= 0 && shadow_byte < KASAN_GRANULE_SIZE; } void kasan_cache_shrink(struct kmem_cache *cache) diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 292dfbc37deb..bd4ee6fab648 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -329,20 +329,20 @@ static inline void kasan_unpoison(const void *address, size_t size) round_up(size, KASAN_GRANULE_SIZE), get_tag(address)); } -static inline bool kasan_check_invalid_free(void *addr) +static inline bool kasan_byte_accessible(const void *addr) { u8 ptr_tag = get_tag(addr); - u8 mem_tag = hw_get_mem_tag(addr); + u8 mem_tag = hw_get_mem_tag((void *)addr); - return (mem_tag == KASAN_TAG_INVALID) || - (ptr_tag != KASAN_TAG_KERNEL && ptr_tag != mem_tag); + return (mem_tag != KASAN_TAG_INVALID) && + (ptr_tag == KASAN_TAG_KERNEL || ptr_tag == mem_tag); } #else /* CONFIG_KASAN_HW_TAGS */ void kasan_poison(const void *address, size_t size, u8 value); void kasan_unpoison(const void *address, size_t size); -bool kasan_check_invalid_free(void *addr); +bool kasan_byte_accessible(const void *addr); #endif /* CONFIG_KASAN_HW_TAGS */ diff --git a/mm/kasan/sw_tags.c b/mm/kasan/sw_tags.c index cc271fceb5d5..94c2d33be333 100644 --- a/mm/kasan/sw_tags.c +++ b/mm/kasan/sw_tags.c @@ -118,13 +118,13 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write, return true; } -bool kasan_check_invalid_free(void *addr) +bool kasan_byte_accessible(const void *addr) { u8 tag = get_tag(addr); u8 shadow_byte = READ_ONCE(*(u8 *)kasan_mem_to_shadow(kasan_reset_tag(addr))); - return (shadow_byte == KASAN_TAG_INVALID) || - (tag != KASAN_TAG_KERNEL && tag != shadow_byte); + return (shadow_byte != KASAN_TAG_INVALID) && + (tag == KASAN_TAG_KERNEL || tag == shadow_byte); } #define DEFINE_HWASAN_LOAD_STORE(size) \ diff --git a/mm/slab_common.c b/mm/slab_common.c index e981c80d216c..a3bb44516623 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -1157,11 +1157,13 @@ size_t ksize(const void *objp) size_t size; /* - * We need to check that the pointed to object is valid, and only then - * unpoison the shadow memory below. We use __kasan_check_read(), to - * generate a more useful report at the time ksize() is called (rather - * than later where behaviour is undefined due to potential - * use-after-free or double-free). + * We need to first check that the pointer to the object is valid, and + * only then unpoison the memory. The report printed from ksize() is + * more useful, then when it's printed later when the behaviour could + * be undefined due to a potential use-after-free or double-free. + * + * We use kasan_check_byte(), which is supported for hardware tag-based + * KASAN mode, unlike kasan_check_read/write(). * * If the pointed to memory is invalid we return 0, to avoid users of * ksize() writing to and potentially corrupting the memory region. @@ -1169,7 +1171,8 @@ size_t ksize(const void *objp) * We want to perform the check before __ksize(), to avoid potentially * crashing in __ksize() due to accessing invalid metadata. */ - if (unlikely(ZERO_OR_NULL_PTR(objp)) || !__kasan_check_read(objp, 1)) + if (unlikely(ZERO_OR_NULL_PTR(objp)) || + !kasan_check_byte(objp, _RET_IP_)) return 0; size = __ksize(objp);