Message ID | 20230328095807.7014-6-songmuchun@bytedance.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp2098211vqo; Tue, 28 Mar 2023 03:08:09 -0700 (PDT) X-Google-Smtp-Source: AKy350ZPSqeMF0Zt35XMmeH1kbGwdAipj4pREJpu9QhprhFxGrPe+mtrU+iFy67E7wyCJGHg/W7Y X-Received: by 2002:a17:906:fc2:b0:923:c199:dab1 with SMTP id c2-20020a1709060fc200b00923c199dab1mr17317417ejk.55.1679998088999; Tue, 28 Mar 2023 03:08:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1679998088; cv=none; d=google.com; s=arc-20160816; b=rMqKyig3t+yLQsJ0sCbhIXgrBRA6oDgjPgJKC1ZsJhDV3kOkLEVRzEjDnly+EFw5vj sTDVkSU7qNHw0zRfVIwlakf+zN7ZN9ZFXxAR/lrcrN0yTQe3+w+2BwJ6A6St3sN9zBhN eI4VWV3yiyTkK3iLOv3RHkgcDI/mvcUPJPt3p+IipXgRmBE6Ao5wq6N0sHHZmKqud7C6 2dw1CxmEKtwJSFg1nczxFJivsRxO3iCNJ1xzII7yI7V1FHH/3eeK3VVgqsFQ/563G0Nq FKnF1bo+FXopk5ORpjlXTYFhRj5wfGdFwMrUdke7e2oQzr6qH96PBrJs1NiPNxMohk5G 5CuA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=IMHBZhrpM5z4k2ZtEf9hxQYrO1Qyd30oWmty+49rOpw=; b=lEaV9ZFlI6o/FItRGFuQHez2PdE/bFZBkUfml0so2iIF9rfzWhgWI+pPstztu+YyCy poSCkmNpdsvmjSzB8cjRg9HZgspnd6ObIz1Wj1EfzHblVNG1Am4v6R+rFTrSDmtemvHB 5fjpumBoRfy8XYzXkpeYZV2zqFJO6VRoteRANykqJ7CPPvJ8iAMzsbjkJByP8kewi1Lu Z/+ytewPG3f6SM0YMIKycx7EbTn4AUt8ilGKFgfypY+gxnr8F0y0aSUnQLNlTtFKdbdn pbxb4aa59Jf+86M5WbbS4Z9gCA6WwGwFWp2pqSZQJrmNHM3bc7OM1EfwMQa7/txOC7yW fRMA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@bytedance.com header.s=google header.b=cRycGiJJ; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=bytedance.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id gr17-20020a170906e2d100b008b17b550d11si26539730ejb.382.2023.03.28.03.07.45; Tue, 28 Mar 2023 03:08:08 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@bytedance.com header.s=google header.b=cRycGiJJ; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=bytedance.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232891AbjC1J7E (ORCPT <rfc822;kartikey406@gmail.com> + 99 others); Tue, 28 Mar 2023 05:59:04 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47176 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232544AbjC1J64 (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Tue, 28 Mar 2023 05:58:56 -0400 Received: from mail-pj1-x102c.google.com (mail-pj1-x102c.google.com [IPv6:2607:f8b0:4864:20::102c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 61D8165A1 for <linux-kernel@vger.kernel.org>; Tue, 28 Mar 2023 02:58:55 -0700 (PDT) Received: by mail-pj1-x102c.google.com with SMTP id lr16-20020a17090b4b9000b0023f187954acso11941804pjb.2 for <linux-kernel@vger.kernel.org>; Tue, 28 Mar 2023 02:58:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance.com; s=google; t=1679997535; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=IMHBZhrpM5z4k2ZtEf9hxQYrO1Qyd30oWmty+49rOpw=; b=cRycGiJJnkVXyhfrk/dJRNSCMoFpZNGGzl7dazDlUieFTT+oQ+yoOI0svPNXNgtJqi ueJI4DHM5wT7gQ5RfXmNTIxsoHKaTtsJl7K7gn59uLOroQOiZT59BCAtb8NqmrT1Mh3k Gmp9KRHfZU2HK7LFnjWVQlIsu8uap2Y3Occ0QtUuXKCCiQ/RNy7ku8loyA64bCWAh8nu YarRVEK3zgJVrD1NAyG+Rw6Yp31+UQjj/8IL6k6ldDxLShPxNG7chryjPAjYipTl6RtD e5Kw7dStq3e/3/leQwjmSZgKAQc77VNntEv25vM1Y9n+FcT/YOolm+LYWL6kjyf2byr7 cH1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1679997535; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=IMHBZhrpM5z4k2ZtEf9hxQYrO1Qyd30oWmty+49rOpw=; b=U/BQWjgeegBbGv1pBkwvZYCXwfrA+iGrTUU4/CEy4SyXeQdglpPo2csmA0Ssz8wJrU 22AA96maC2uYo1HXiENJgo3TCmHmuFKtmCJVJm2tQ+wMze5orzGLdZla+Duonh6t0ovI 5iFb7h5IAFDiv2OikUavB94i3E6nGHdoRJO5lx7xLVbX4Iu19PX+66GheCifNawXrige CsBkjWZiyWEDywY7jaGcebP8nLNcgcUZqe2R8LFRLbabkgrhUcbASq6lDURyOcvqpcix Z8zAHpkTlZScWWq17bxZJIiNv7Z2GdtSMFK8EuOlfqjSV8KXrOf/6hFs+rQbLOzdUoAI JQPw== X-Gm-Message-State: AO0yUKVku8Nwp+YlRL8CSf2rTXvW0ihkfNHeV7mtij7T+HRAQjUtesat L9j8wM7k2jwAUop2EWhsL1p1iMEhsmdZYQ+Xdmk/qg== X-Received: by 2002:a05:6a20:6baf:b0:da:1e1:3f46 with SMTP id bu47-20020a056a206baf00b000da01e13f46mr13145721pzb.31.1679997534793; Tue, 28 Mar 2023 02:58:54 -0700 (PDT) Received: from PXLDJ45XCM.bytedance.net ([139.177.225.236]) by smtp.gmail.com with ESMTPSA id m26-20020aa78a1a000000b005a8a5be96b2sm17207556pfa.104.2023.03.28.02.58.48 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Tue, 28 Mar 2023 02:58:54 -0700 (PDT) From: Muchun Song <songmuchun@bytedance.com> To: glider@google.com, elver@google.com, dvyukov@google.com, akpm@linux-foundation.org, jannh@google.com, sjpark@amazon.de, muchun.song@linux.dev Cc: kasan-dev@googlegroups.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, Muchun Song <songmuchun@bytedance.com> Subject: [PATCH 5/6] mm: kfence: change kfence pool page layout Date: Tue, 28 Mar 2023 17:58:06 +0800 Message-Id: <20230328095807.7014-6-songmuchun@bytedance.com> X-Mailer: git-send-email 2.37.1 (Apple Git-137.1) In-Reply-To: <20230328095807.7014-1-songmuchun@bytedance.com> References: <20230328095807.7014-1-songmuchun@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1761605676008663251?= X-GMAIL-MSGID: =?utf-8?q?1761605676008663251?= |
Series |
Simplify kfence code
|
|
Commit Message
Muchun Song
March 28, 2023, 9:58 a.m. UTC
The original kfence pool layout (Given a layout with 2 objects):
+------------+------------+------------+------------+------------+------------+
| guard page | guard page | object | guard page | object | guard page |
+------------+------------+------------+------------+------------+------------+
| | |
+----kfence_metadata[0]---+----kfence_metadata[1]---+
The comment says "the additional page in the beginning gives us an even
number of pages, which simplifies the mapping of address to metadata index".
However, removing the additional page does not complicate any mapping
calculations. So changing it to the new layout to save a page. And remmove
the KFENCE_ERROR_INVALID test since we cannot test this case easily.
The new kfence pool layout (Given a layout with 2 objects):
+------------+------------+------------+------------+------------+
| guard page | object | guard page | object | guard page |
+------------+------------+------------+------------+------------+
| | |
+----kfence_metadata[0]---+----kfence_metadata[1]---+
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/kfence.h | 8 ++------
mm/kfence/core.c | 40 ++++++++--------------------------------
mm/kfence/kfence.h | 2 +-
mm/kfence/kfence_test.c | 14 --------------
4 files changed, 11 insertions(+), 53 deletions(-)
Comments
On Tue, 28 Mar 2023 at 11:58, 'Muchun Song' via kasan-dev <kasan-dev@googlegroups.com> wrote: > > The original kfence pool layout (Given a layout with 2 objects): > > +------------+------------+------------+------------+------------+------------+ > | guard page | guard page | object | guard page | object | guard page | > +------------+------------+------------+------------+------------+------------+ > | | | > +----kfence_metadata[0]---+----kfence_metadata[1]---+ > > The comment says "the additional page in the beginning gives us an even > number of pages, which simplifies the mapping of address to metadata index". > > However, removing the additional page does not complicate any mapping > calculations. So changing it to the new layout to save a page. And remmove > the KFENCE_ERROR_INVALID test since we cannot test this case easily. > > The new kfence pool layout (Given a layout with 2 objects): > > +------------+------------+------------+------------+------------+ > | guard page | object | guard page | object | guard page | > +------------+------------+------------+------------+------------+ > | | | > +----kfence_metadata[0]---+----kfence_metadata[1]---+ > > Signed-off-by: Muchun Song <songmuchun@bytedance.com> > --- > include/linux/kfence.h | 8 ++------ > mm/kfence/core.c | 40 ++++++++-------------------------------- > mm/kfence/kfence.h | 2 +- > mm/kfence/kfence_test.c | 14 -------------- > 4 files changed, 11 insertions(+), 53 deletions(-) > > diff --git a/include/linux/kfence.h b/include/linux/kfence.h > index 726857a4b680..25b13a892717 100644 > --- a/include/linux/kfence.h > +++ b/include/linux/kfence.h > @@ -19,12 +19,8 @@ > > extern unsigned long kfence_sample_interval; > > -/* > - * We allocate an even number of pages, as it simplifies calculations to map > - * address to metadata indices; effectively, the very first page serves as an > - * extended guard page, but otherwise has no special purpose. > - */ > -#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE) > +/* The last page serves as an extended guard page. */ The last page is just a normal guard page? I.e. the last 2 pages are: <object page> | <guard page> Or did I misunderstand? > +#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS * 2 + 1) * PAGE_SIZE) > extern char *__kfence_pool; > > DECLARE_STATIC_KEY_FALSE(kfence_allocation_key); > diff --git a/mm/kfence/core.c b/mm/kfence/core.c > index 41befcb3b069..f205b860f460 100644 > --- a/mm/kfence/core.c > +++ b/mm/kfence/core.c > @@ -240,24 +240,7 @@ static inline void kfence_unprotect(unsigned long addr) > > static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta) > { > - unsigned long offset = (meta - kfence_metadata + 1) * PAGE_SIZE * 2; > - unsigned long pageaddr = (unsigned long)&__kfence_pool[offset]; > - > - /* The checks do not affect performance; only called from slow-paths. */ > - > - /* Only call with a pointer into kfence_metadata. */ > - if (KFENCE_WARN_ON(meta < kfence_metadata || > - meta >= kfence_metadata + CONFIG_KFENCE_NUM_OBJECTS)) > - return 0; Could we retain this WARN_ON? Or just get rid of metadata_to_pageaddr() altogether, because there's only 1 use left and the function would now just be a simple ALIGN_DOWN() anyway. > - /* > - * This metadata object only ever maps to 1 page; verify that the stored > - * address is in the expected range. > - */ > - if (KFENCE_WARN_ON(ALIGN_DOWN(meta->addr, PAGE_SIZE) != pageaddr)) > - return 0; > - > - return pageaddr; > + return ALIGN_DOWN(meta->addr, PAGE_SIZE); > } > > /* > @@ -535,34 +518,27 @@ static void kfence_init_pool(void) > unsigned long addr = (unsigned long)__kfence_pool; > int i; > > - /* > - * Protect the first 2 pages. The first page is mostly unnecessary, and > - * merely serves as an extended guard page. However, adding one > - * additional page in the beginning gives us an even number of pages, > - * which simplifies the mapping of address to metadata index. > - */ > - for (i = 0; i < 2; i++, addr += PAGE_SIZE) > - kfence_protect(addr); > - > for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++, addr += 2 * PAGE_SIZE) { > struct kfence_metadata *meta = &kfence_metadata[i]; > - struct slab *slab = page_slab(virt_to_page(addr)); > + struct slab *slab = page_slab(virt_to_page(addr + PAGE_SIZE)); > > /* Initialize metadata. */ > INIT_LIST_HEAD(&meta->list); > raw_spin_lock_init(&meta->lock); > meta->state = KFENCE_OBJECT_UNUSED; > - meta->addr = addr; /* Initialize for validation in metadata_to_pageaddr(). */ > + meta->addr = addr + PAGE_SIZE; > list_add_tail(&meta->list, &kfence_freelist); > > - /* Protect the right redzone. */ > - kfence_protect(addr + PAGE_SIZE); > + /* Protect the left redzone. */ > + kfence_protect(addr); > > __folio_set_slab(slab_folio(slab)); > #ifdef CONFIG_MEMCG > slab->memcg_data = (unsigned long)&meta->objcg | MEMCG_DATA_OBJCGS; > #endif > } > + > + kfence_protect(addr); > } > > static bool __init kfence_init_pool_early(void) > @@ -1043,7 +1019,7 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs > > atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]); > > - if (page_index % 2) { > + if (page_index % 2 == 0) { > /* This is a redzone, report a buffer overflow. */ > struct kfence_metadata *meta; > int distance = 0; > diff --git a/mm/kfence/kfence.h b/mm/kfence/kfence.h > index 600f2e2431d6..249d420100a7 100644 > --- a/mm/kfence/kfence.h > +++ b/mm/kfence/kfence.h > @@ -110,7 +110,7 @@ static inline struct kfence_metadata *addr_to_metadata(unsigned long addr) > * __kfence_pool, in which case we would report an "invalid access" > * error. > */ > - index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2) - 1; > + index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2); > if (index < 0 || index >= CONFIG_KFENCE_NUM_OBJECTS) > return NULL; Assume there is a right OOB that hit the last guard page. In this case addr >= __kfence_pool + (NUM_OBJECTS * 2 * PAGE_SIZE) && addr < __kfence_pool + POOL_SIZE therefore index >= (NUM_OBJECTS * 2 * PAGE_SIZE) / (PAGE_SIZE * 2) && index < POOL_SIZE / (PAGE_SIZE * 2) index == NUM_OBJECTS And according to the above comparison, this will return NULL and report KFENCE_ERROR_INVALID, which is wrong. > diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c > index b5d66a69200d..d479f9c8afb1 100644 > --- a/mm/kfence/kfence_test.c > +++ b/mm/kfence/kfence_test.c > @@ -637,19 +637,6 @@ static void test_gfpzero(struct kunit *test) > KUNIT_EXPECT_FALSE(test, report_available()); > } > > -static void test_invalid_access(struct kunit *test) > -{ > - const struct expect_report expect = { > - .type = KFENCE_ERROR_INVALID, > - .fn = test_invalid_access, > - .addr = &__kfence_pool[10], > - .is_write = false, > - }; > - > - READ_ONCE(__kfence_pool[10]); > - KUNIT_EXPECT_TRUE(test, report_matches(&expect)); > -} > - > /* Test SLAB_TYPESAFE_BY_RCU works. */ > static void test_memcache_typesafe_by_rcu(struct kunit *test) > { > @@ -787,7 +774,6 @@ static struct kunit_case kfence_test_cases[] = { > KUNIT_CASE(test_kmalloc_aligned_oob_write), > KUNIT_CASE(test_shrink_memcache), > KUNIT_CASE(test_memcache_ctor), > - KUNIT_CASE(test_invalid_access), The test can be retained by doing an access to a guard page in between 2 unallocated objects. But it's probably not that easy to reliably set that up (could try to allocate 2 objects and see if they're next to each other, then free them).
> On Mar 28, 2023, at 20:59, Marco Elver <elver@google.com> wrote: > > On Tue, 28 Mar 2023 at 11:58, 'Muchun Song' via kasan-dev > <kasan-dev@googlegroups.com> wrote: >> >> The original kfence pool layout (Given a layout with 2 objects): >> >> +------------+------------+------------+------------+------------+------------+ >> | guard page | guard page | object | guard page | object | guard page | >> +------------+------------+------------+------------+------------+------------+ >> | | | >> +----kfence_metadata[0]---+----kfence_metadata[1]---+ >> >> The comment says "the additional page in the beginning gives us an even >> number of pages, which simplifies the mapping of address to metadata index". >> >> However, removing the additional page does not complicate any mapping >> calculations. So changing it to the new layout to save a page. And remmove >> the KFENCE_ERROR_INVALID test since we cannot test this case easily. >> >> The new kfence pool layout (Given a layout with 2 objects): >> >> +------------+------------+------------+------------+------------+ >> | guard page | object | guard page | object | guard page | >> +------------+------------+------------+------------+------------+ >> | | | >> +----kfence_metadata[0]---+----kfence_metadata[1]---+ >> >> Signed-off-by: Muchun Song <songmuchun@bytedance.com> >> --- >> include/linux/kfence.h | 8 ++------ >> mm/kfence/core.c | 40 ++++++++-------------------------------- >> mm/kfence/kfence.h | 2 +- >> mm/kfence/kfence_test.c | 14 -------------- >> 4 files changed, 11 insertions(+), 53 deletions(-) >> >> diff --git a/include/linux/kfence.h b/include/linux/kfence.h >> index 726857a4b680..25b13a892717 100644 >> --- a/include/linux/kfence.h >> +++ b/include/linux/kfence.h >> @@ -19,12 +19,8 @@ >> >> extern unsigned long kfence_sample_interval; >> >> -/* >> - * We allocate an even number of pages, as it simplifies calculations to map >> - * address to metadata indices; effectively, the very first page serves as an >> - * extended guard page, but otherwise has no special purpose. >> - */ >> -#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE) >> +/* The last page serves as an extended guard page. */ > > The last page is just a normal guard page? I.e. the last 2 pages are: > <object page> | <guard page> Right. The new kfence pool layout (Given a layout with 2 objects): +------------+------------+------------+------------+------------+ | guard page | object | guard page | object | guard page | +------------+------------+------------+------------+------------+ | | | ^ +----kfence_metadata[0]---+----kfence_metadata[1]---+ | | | the last page > > Or did I misunderstand? > >> +#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS * 2 + 1) * PAGE_SIZE) >> extern char *__kfence_pool; >> >> DECLARE_STATIC_KEY_FALSE(kfence_allocation_key); >> diff --git a/mm/kfence/core.c b/mm/kfence/core.c >> index 41befcb3b069..f205b860f460 100644 >> --- a/mm/kfence/core.c >> +++ b/mm/kfence/core.c >> @@ -240,24 +240,7 @@ static inline void kfence_unprotect(unsigned long addr) >> >> static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta) >> { >> - unsigned long offset = (meta - kfence_metadata + 1) * PAGE_SIZE * 2; >> - unsigned long pageaddr = (unsigned long)&__kfence_pool[offset]; >> - >> - /* The checks do not affect performance; only called from slow-paths. */ >> - >> - /* Only call with a pointer into kfence_metadata. */ >> - if (KFENCE_WARN_ON(meta < kfence_metadata || >> - meta >= kfence_metadata + CONFIG_KFENCE_NUM_OBJECTS)) >> - return 0; > > Could we retain this WARN_ON? Or just get rid of > metadata_to_pageaddr() altogether, because there's only 1 use left and > the function would now just be a simple ALIGN_DOWN() anyway. I'll inline this function to its caller since the warning is unlikely. > >> - /* >> - * This metadata object only ever maps to 1 page; verify that the stored >> - * address is in the expected range. >> - */ >> - if (KFENCE_WARN_ON(ALIGN_DOWN(meta->addr, PAGE_SIZE) != pageaddr)) >> - return 0; >> - >> - return pageaddr; >> + return ALIGN_DOWN(meta->addr, PAGE_SIZE); >> } >> >> /* >> @@ -535,34 +518,27 @@ static void kfence_init_pool(void) >> unsigned long addr = (unsigned long)__kfence_pool; >> int i; >> >> - /* >> - * Protect the first 2 pages. The first page is mostly unnecessary, and >> - * merely serves as an extended guard page. However, adding one >> - * additional page in the beginning gives us an even number of pages, >> - * which simplifies the mapping of address to metadata index. >> - */ >> - for (i = 0; i < 2; i++, addr += PAGE_SIZE) >> - kfence_protect(addr); >> - >> for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++, addr += 2 * PAGE_SIZE) { >> struct kfence_metadata *meta = &kfence_metadata[i]; >> - struct slab *slab = page_slab(virt_to_page(addr)); >> + struct slab *slab = page_slab(virt_to_page(addr + PAGE_SIZE)); >> >> /* Initialize metadata. */ >> INIT_LIST_HEAD(&meta->list); >> raw_spin_lock_init(&meta->lock); >> meta->state = KFENCE_OBJECT_UNUSED; >> - meta->addr = addr; /* Initialize for validation in metadata_to_pageaddr(). */ >> + meta->addr = addr + PAGE_SIZE; >> list_add_tail(&meta->list, &kfence_freelist); >> >> - /* Protect the right redzone. */ >> - kfence_protect(addr + PAGE_SIZE); >> + /* Protect the left redzone. */ >> + kfence_protect(addr); >> >> __folio_set_slab(slab_folio(slab)); >> #ifdef CONFIG_MEMCG >> slab->memcg_data = (unsigned long)&meta->objcg | MEMCG_DATA_OBJCGS; >> #endif >> } >> + >> + kfence_protect(addr); >> } >> >> static bool __init kfence_init_pool_early(void) >> @@ -1043,7 +1019,7 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs >> >> atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]); >> >> - if (page_index % 2) { >> + if (page_index % 2 == 0) { >> /* This is a redzone, report a buffer overflow. */ >> struct kfence_metadata *meta; >> int distance = 0; >> diff --git a/mm/kfence/kfence.h b/mm/kfence/kfence.h >> index 600f2e2431d6..249d420100a7 100644 >> --- a/mm/kfence/kfence.h >> +++ b/mm/kfence/kfence.h >> @@ -110,7 +110,7 @@ static inline struct kfence_metadata *addr_to_metadata(unsigned long addr) >> * __kfence_pool, in which case we would report an "invalid access" >> * error. >> */ >> - index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2) - 1; >> + index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2); >> if (index < 0 || index >= CONFIG_KFENCE_NUM_OBJECTS) >> return NULL; > > Assume there is a right OOB that hit the last guard page. In this case > > addr >= __kfence_pool + (NUM_OBJECTS * 2 * PAGE_SIZE) && addr < > __kfence_pool + POOL_SIZE > > therefore > > index >= (NUM_OBJECTS * 2 * PAGE_SIZE) / (PAGE_SIZE * 2) && index < > POOL_SIZE / (PAGE_SIZE * 2) > index == NUM_OBJECTS > > And according to the above comparison, this will return NULL and > report KFENCE_ERROR_INVALID, which is wrong. Look at kfence_handle_page_fault(), which first look up "addr - PAGE_SIZE" (passed to addr_to_metadata()) and then look up "addr + PAGE_SIZE", the former will not return NULL, the latter will return NULL. So kfence will report KFENCE_ERROR_OOB in this case, right? Or what I missed here? > >> diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c >> index b5d66a69200d..d479f9c8afb1 100644 >> --- a/mm/kfence/kfence_test.c >> +++ b/mm/kfence/kfence_test.c >> @@ -637,19 +637,6 @@ static void test_gfpzero(struct kunit *test) >> KUNIT_EXPECT_FALSE(test, report_available()); >> } >> >> -static void test_invalid_access(struct kunit *test) >> -{ >> - const struct expect_report expect = { >> - .type = KFENCE_ERROR_INVALID, >> - .fn = test_invalid_access, >> - .addr = &__kfence_pool[10], >> - .is_write = false, >> - }; >> - >> - READ_ONCE(__kfence_pool[10]); >> - KUNIT_EXPECT_TRUE(test, report_matches(&expect)); >> -} >> - >> /* Test SLAB_TYPESAFE_BY_RCU works. */ >> static void test_memcache_typesafe_by_rcu(struct kunit *test) >> { >> @@ -787,7 +774,6 @@ static struct kunit_case kfence_test_cases[] = { >> KUNIT_CASE(test_kmalloc_aligned_oob_write), >> KUNIT_CASE(test_shrink_memcache), >> KUNIT_CASE(test_memcache_ctor), >> - KUNIT_CASE(test_invalid_access), > > The test can be retained by doing an access to a guard page in between > 2 unallocated objects. But it's probably not that easy to reliably set > that up (could try to allocate 2 objects and see if they're next to > each other, then free them). Yes, it's not easy to trigger it 100%. So I removed the test.
On Tue, 28 Mar 2023 at 15:33, Muchun Song <muchun.song@linux.dev> wrote: > > > > > On Mar 28, 2023, at 20:59, Marco Elver <elver@google.com> wrote: > > > > On Tue, 28 Mar 2023 at 11:58, 'Muchun Song' via kasan-dev > > <kasan-dev@googlegroups.com> wrote: > >> > >> The original kfence pool layout (Given a layout with 2 objects): > >> > >> +------------+------------+------------+------------+------------+------------+ > >> | guard page | guard page | object | guard page | object | guard page | > >> +------------+------------+------------+------------+------------+------------+ > >> | | | > >> +----kfence_metadata[0]---+----kfence_metadata[1]---+ > >> > >> The comment says "the additional page in the beginning gives us an even > >> number of pages, which simplifies the mapping of address to metadata index". > >> > >> However, removing the additional page does not complicate any mapping > >> calculations. So changing it to the new layout to save a page. And remmove > >> the KFENCE_ERROR_INVALID test since we cannot test this case easily. > >> > >> The new kfence pool layout (Given a layout with 2 objects): > >> > >> +------------+------------+------------+------------+------------+ > >> | guard page | object | guard page | object | guard page | > >> +------------+------------+------------+------------+------------+ > >> | | | > >> +----kfence_metadata[0]---+----kfence_metadata[1]---+ > >> > >> Signed-off-by: Muchun Song <songmuchun@bytedance.com> > >> --- > >> include/linux/kfence.h | 8 ++------ > >> mm/kfence/core.c | 40 ++++++++-------------------------------- > >> mm/kfence/kfence.h | 2 +- > >> mm/kfence/kfence_test.c | 14 -------------- > >> 4 files changed, 11 insertions(+), 53 deletions(-) > >> > >> diff --git a/include/linux/kfence.h b/include/linux/kfence.h > >> index 726857a4b680..25b13a892717 100644 > >> --- a/include/linux/kfence.h > >> +++ b/include/linux/kfence.h > >> @@ -19,12 +19,8 @@ > >> > >> extern unsigned long kfence_sample_interval; > >> > >> -/* > >> - * We allocate an even number of pages, as it simplifies calculations to map > >> - * address to metadata indices; effectively, the very first page serves as an > >> - * extended guard page, but otherwise has no special purpose. > >> - */ > >> -#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE) > >> +/* The last page serves as an extended guard page. */ > > > > The last page is just a normal guard page? I.e. the last 2 pages are: > > <object page> | <guard page> > > Right. > > The new kfence pool layout (Given a layout with 2 objects): > > +------------+------------+------------+------------+------------+ > | guard page | object | guard page | object | guard page | > +------------+------------+------------+------------+------------+ > | | | ^ > +----kfence_metadata[0]---+----kfence_metadata[1]---+ | > | > | > the last page > > > > > Or did I misunderstand? > > > >> +#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS * 2 + 1) * PAGE_SIZE) > >> extern char *__kfence_pool; > >> > >> DECLARE_STATIC_KEY_FALSE(kfence_allocation_key); > >> diff --git a/mm/kfence/core.c b/mm/kfence/core.c > >> index 41befcb3b069..f205b860f460 100644 > >> --- a/mm/kfence/core.c > >> +++ b/mm/kfence/core.c > >> @@ -240,24 +240,7 @@ static inline void kfence_unprotect(unsigned long addr) > >> > >> static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta) > >> { > >> - unsigned long offset = (meta - kfence_metadata + 1) * PAGE_SIZE * 2; > >> - unsigned long pageaddr = (unsigned long)&__kfence_pool[offset]; > >> - > >> - /* The checks do not affect performance; only called from slow-paths. */ > >> - > >> - /* Only call with a pointer into kfence_metadata. */ > >> - if (KFENCE_WARN_ON(meta < kfence_metadata || > >> - meta >= kfence_metadata + CONFIG_KFENCE_NUM_OBJECTS)) > >> - return 0; > > > > Could we retain this WARN_ON? Or just get rid of > > metadata_to_pageaddr() altogether, because there's only 1 use left and > > the function would now just be a simple ALIGN_DOWN() anyway. > > I'll inline this function to its caller since the warning is unlikely. > > > > >> - /* > >> - * This metadata object only ever maps to 1 page; verify that the stored > >> - * address is in the expected range. > >> - */ > >> - if (KFENCE_WARN_ON(ALIGN_DOWN(meta->addr, PAGE_SIZE) != pageaddr)) > >> - return 0; > >> - > >> - return pageaddr; > >> + return ALIGN_DOWN(meta->addr, PAGE_SIZE); > >> } > >> > >> /* > >> @@ -535,34 +518,27 @@ static void kfence_init_pool(void) > >> unsigned long addr = (unsigned long)__kfence_pool; > >> int i; > >> > >> - /* > >> - * Protect the first 2 pages. The first page is mostly unnecessary, and > >> - * merely serves as an extended guard page. However, adding one > >> - * additional page in the beginning gives us an even number of pages, > >> - * which simplifies the mapping of address to metadata index. > >> - */ > >> - for (i = 0; i < 2; i++, addr += PAGE_SIZE) > >> - kfence_protect(addr); > >> - > >> for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++, addr += 2 * PAGE_SIZE) { > >> struct kfence_metadata *meta = &kfence_metadata[i]; > >> - struct slab *slab = page_slab(virt_to_page(addr)); > >> + struct slab *slab = page_slab(virt_to_page(addr + PAGE_SIZE)); > >> > >> /* Initialize metadata. */ > >> INIT_LIST_HEAD(&meta->list); > >> raw_spin_lock_init(&meta->lock); > >> meta->state = KFENCE_OBJECT_UNUSED; > >> - meta->addr = addr; /* Initialize for validation in metadata_to_pageaddr(). */ > >> + meta->addr = addr + PAGE_SIZE; > >> list_add_tail(&meta->list, &kfence_freelist); > >> > >> - /* Protect the right redzone. */ > >> - kfence_protect(addr + PAGE_SIZE); > >> + /* Protect the left redzone. */ > >> + kfence_protect(addr); > >> > >> __folio_set_slab(slab_folio(slab)); > >> #ifdef CONFIG_MEMCG > >> slab->memcg_data = (unsigned long)&meta->objcg | MEMCG_DATA_OBJCGS; > >> #endif > >> } > >> + > >> + kfence_protect(addr); > >> } > >> > >> static bool __init kfence_init_pool_early(void) > >> @@ -1043,7 +1019,7 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs > >> > >> atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]); > >> > >> - if (page_index % 2) { > >> + if (page_index % 2 == 0) { > >> /* This is a redzone, report a buffer overflow. */ > >> struct kfence_metadata *meta; > >> int distance = 0; > >> diff --git a/mm/kfence/kfence.h b/mm/kfence/kfence.h > >> index 600f2e2431d6..249d420100a7 100644 > >> --- a/mm/kfence/kfence.h > >> +++ b/mm/kfence/kfence.h > >> @@ -110,7 +110,7 @@ static inline struct kfence_metadata *addr_to_metadata(unsigned long addr) > >> * __kfence_pool, in which case we would report an "invalid access" > >> * error. > >> */ > >> - index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2) - 1; > >> + index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2); > >> if (index < 0 || index >= CONFIG_KFENCE_NUM_OBJECTS) > >> return NULL; > > > > Assume there is a right OOB that hit the last guard page. In this case > > > > addr >= __kfence_pool + (NUM_OBJECTS * 2 * PAGE_SIZE) && addr < > > __kfence_pool + POOL_SIZE > > > > therefore > > > > index >= (NUM_OBJECTS * 2 * PAGE_SIZE) / (PAGE_SIZE * 2) && index < > > POOL_SIZE / (PAGE_SIZE * 2) > > index == NUM_OBJECTS > > > > And according to the above comparison, this will return NULL and > > report KFENCE_ERROR_INVALID, which is wrong. > > Look at kfence_handle_page_fault(), which first look up "addr - PAGE_SIZE" (passed > to addr_to_metadata()) and then look up "addr + PAGE_SIZE", the former will not > return NULL, the latter will return NULL. So kfence will report KFENCE_ERROR_OOB > in this case, right? Or what I missed here? Yes, you're right. Thanks, -- Marco
diff --git a/include/linux/kfence.h b/include/linux/kfence.h index 726857a4b680..25b13a892717 100644 --- a/include/linux/kfence.h +++ b/include/linux/kfence.h @@ -19,12 +19,8 @@ extern unsigned long kfence_sample_interval; -/* - * We allocate an even number of pages, as it simplifies calculations to map - * address to metadata indices; effectively, the very first page serves as an - * extended guard page, but otherwise has no special purpose. - */ -#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE) +/* The last page serves as an extended guard page. */ +#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS * 2 + 1) * PAGE_SIZE) extern char *__kfence_pool; DECLARE_STATIC_KEY_FALSE(kfence_allocation_key); diff --git a/mm/kfence/core.c b/mm/kfence/core.c index 41befcb3b069..f205b860f460 100644 --- a/mm/kfence/core.c +++ b/mm/kfence/core.c @@ -240,24 +240,7 @@ static inline void kfence_unprotect(unsigned long addr) static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta) { - unsigned long offset = (meta - kfence_metadata + 1) * PAGE_SIZE * 2; - unsigned long pageaddr = (unsigned long)&__kfence_pool[offset]; - - /* The checks do not affect performance; only called from slow-paths. */ - - /* Only call with a pointer into kfence_metadata. */ - if (KFENCE_WARN_ON(meta < kfence_metadata || - meta >= kfence_metadata + CONFIG_KFENCE_NUM_OBJECTS)) - return 0; - - /* - * This metadata object only ever maps to 1 page; verify that the stored - * address is in the expected range. - */ - if (KFENCE_WARN_ON(ALIGN_DOWN(meta->addr, PAGE_SIZE) != pageaddr)) - return 0; - - return pageaddr; + return ALIGN_DOWN(meta->addr, PAGE_SIZE); } /* @@ -535,34 +518,27 @@ static void kfence_init_pool(void) unsigned long addr = (unsigned long)__kfence_pool; int i; - /* - * Protect the first 2 pages. The first page is mostly unnecessary, and - * merely serves as an extended guard page. However, adding one - * additional page in the beginning gives us an even number of pages, - * which simplifies the mapping of address to metadata index. - */ - for (i = 0; i < 2; i++, addr += PAGE_SIZE) - kfence_protect(addr); - for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++, addr += 2 * PAGE_SIZE) { struct kfence_metadata *meta = &kfence_metadata[i]; - struct slab *slab = page_slab(virt_to_page(addr)); + struct slab *slab = page_slab(virt_to_page(addr + PAGE_SIZE)); /* Initialize metadata. */ INIT_LIST_HEAD(&meta->list); raw_spin_lock_init(&meta->lock); meta->state = KFENCE_OBJECT_UNUSED; - meta->addr = addr; /* Initialize for validation in metadata_to_pageaddr(). */ + meta->addr = addr + PAGE_SIZE; list_add_tail(&meta->list, &kfence_freelist); - /* Protect the right redzone. */ - kfence_protect(addr + PAGE_SIZE); + /* Protect the left redzone. */ + kfence_protect(addr); __folio_set_slab(slab_folio(slab)); #ifdef CONFIG_MEMCG slab->memcg_data = (unsigned long)&meta->objcg | MEMCG_DATA_OBJCGS; #endif } + + kfence_protect(addr); } static bool __init kfence_init_pool_early(void) @@ -1043,7 +1019,7 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]); - if (page_index % 2) { + if (page_index % 2 == 0) { /* This is a redzone, report a buffer overflow. */ struct kfence_metadata *meta; int distance = 0; diff --git a/mm/kfence/kfence.h b/mm/kfence/kfence.h index 600f2e2431d6..249d420100a7 100644 --- a/mm/kfence/kfence.h +++ b/mm/kfence/kfence.h @@ -110,7 +110,7 @@ static inline struct kfence_metadata *addr_to_metadata(unsigned long addr) * __kfence_pool, in which case we would report an "invalid access" * error. */ - index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2) - 1; + index = (addr - (unsigned long)__kfence_pool) / (PAGE_SIZE * 2); if (index < 0 || index >= CONFIG_KFENCE_NUM_OBJECTS) return NULL; diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c index b5d66a69200d..d479f9c8afb1 100644 --- a/mm/kfence/kfence_test.c +++ b/mm/kfence/kfence_test.c @@ -637,19 +637,6 @@ static void test_gfpzero(struct kunit *test) KUNIT_EXPECT_FALSE(test, report_available()); } -static void test_invalid_access(struct kunit *test) -{ - const struct expect_report expect = { - .type = KFENCE_ERROR_INVALID, - .fn = test_invalid_access, - .addr = &__kfence_pool[10], - .is_write = false, - }; - - READ_ONCE(__kfence_pool[10]); - KUNIT_EXPECT_TRUE(test, report_matches(&expect)); -} - /* Test SLAB_TYPESAFE_BY_RCU works. */ static void test_memcache_typesafe_by_rcu(struct kunit *test) { @@ -787,7 +774,6 @@ static struct kunit_case kfence_test_cases[] = { KUNIT_CASE(test_kmalloc_aligned_oob_write), KUNIT_CASE(test_shrink_memcache), KUNIT_CASE(test_memcache_ctor), - KUNIT_CASE(test_invalid_access), KUNIT_CASE(test_gfpzero), KUNIT_CASE(test_memcache_typesafe_by_rcu), KUNIT_CASE(test_krealloc),