Message ID | 20231204-slub-cleanup-hooks-v1-4-88b65f7cd9d5@suse.cz (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | SLUB: cleanup hook processing | expand |
On 2023/12/5 03:34, Vlastimil Babka wrote: > When freeing an object that was allocated from KFENCE, we do that in the > slowpath __slab_free(), relying on the fact that KFENCE "slab" cannot be > the cpu slab, so the fastpath has to fallback to the slowpath. > > This optimization doesn't help much though, because is_kfence_address() > is checked earlier anyway during the free hook processing or detached > freelist building. Thus we can simplify the code by making the > slab_free_hook() free the KFENCE object immediately, similarly to KASAN > quarantine. > > In slab_free_hook() we can place kfence_free() above init processing, as > callers have been making sure to set init to false for KFENCE objects. > This simplifies slab_free(). This places it also above kasan_slab_free() > which is ok as that skips KFENCE objects anyway. > > While at it also determine the init value in slab_free_freelist_hook() > outside of the loop. > > This change will also make introducing per cpu array caches easier. > > Tested-by: Marco Elver <elver@google.com> > Signed-off-by: Vlastimil Babka <vbabka@suse.cz> > --- > mm/slub.c | 22 ++++++++++------------ > 1 file changed, 10 insertions(+), 12 deletions(-) > > diff --git a/mm/slub.c b/mm/slub.c > index ed2fa92e914c..e38c2b712f6c 100644 > --- a/mm/slub.c > +++ b/mm/slub.c > @@ -2039,7 +2039,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, > * production configuration these hooks all should produce no code at all. > * > * Returns true if freeing of the object can proceed, false if its reuse > - * was delayed by KASAN quarantine. > + * was delayed by KASAN quarantine, or it was returned to KFENCE. > */ > static __always_inline > bool slab_free_hook(struct kmem_cache *s, void *x, bool init) > @@ -2057,6 +2057,9 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) > __kcsan_check_access(x, s->object_size, > KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); > > + if (kfence_free(kasan_reset_tag(x))) I'm wondering if "kasan_reset_tag()" is needed here? The patch looks good to me! Reviewed-by: Chengming Zhou <zhouchengming@bytedance.com> Thanks. > + return false; > + > /* > * As memory initialization might be integrated into KASAN, > * kasan_slab_free and initialization memset's must be > @@ -2086,23 +2089,25 @@ static inline bool slab_free_freelist_hook(struct kmem_cache *s, > void *object; > void *next = *head; > void *old_tail = *tail; > + bool init; > > if (is_kfence_address(next)) { > slab_free_hook(s, next, false); > - return true; > + return false; > } > > /* Head and tail of the reconstructed freelist */ > *head = NULL; > *tail = NULL; > > + init = slab_want_init_on_free(s); > + > do { > object = next; > next = get_freepointer(s, object); > > /* If object's reuse doesn't have to be delayed */ > - if (likely(slab_free_hook(s, object, > - slab_want_init_on_free(s)))) { > + if (likely(slab_free_hook(s, object, init))) { > /* Move object to the new freelist */ > set_freepointer(s, object, *head); > *head = object; > @@ -4103,9 +4108,6 @@ static void __slab_free(struct kmem_cache *s, struct slab *slab, > > stat(s, FREE_SLOWPATH); > > - if (kfence_free(head)) > - return; > - > if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) { > free_to_partial_list(s, slab, head, tail, cnt, addr); > return; > @@ -4290,13 +4292,9 @@ static __fastpath_inline > void slab_free(struct kmem_cache *s, struct slab *slab, void *object, > unsigned long addr) > { > - bool init; > - > memcg_slab_free_hook(s, slab, &object, 1); > > - init = !is_kfence_address(object) && slab_want_init_on_free(s); > - > - if (likely(slab_free_hook(s, object, init))) > + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s)))) > do_slab_free(s, slab, object, object, 1, addr); > } > >
On 12/5/23 14:27, Chengming Zhou wrote: > On 2023/12/5 03:34, Vlastimil Babka wrote: >> When freeing an object that was allocated from KFENCE, we do that in the >> slowpath __slab_free(), relying on the fact that KFENCE "slab" cannot be >> the cpu slab, so the fastpath has to fallback to the slowpath. >> >> This optimization doesn't help much though, because is_kfence_address() >> is checked earlier anyway during the free hook processing or detached >> freelist building. Thus we can simplify the code by making the >> slab_free_hook() free the KFENCE object immediately, similarly to KASAN >> quarantine. >> >> In slab_free_hook() we can place kfence_free() above init processing, as >> callers have been making sure to set init to false for KFENCE objects. >> This simplifies slab_free(). This places it also above kasan_slab_free() >> which is ok as that skips KFENCE objects anyway. >> >> While at it also determine the init value in slab_free_freelist_hook() >> outside of the loop. >> >> This change will also make introducing per cpu array caches easier. >> >> Tested-by: Marco Elver <elver@google.com> >> Signed-off-by: Vlastimil Babka <vbabka@suse.cz> >> --- >> mm/slub.c | 22 ++++++++++------------ >> 1 file changed, 10 insertions(+), 12 deletions(-) >> >> diff --git a/mm/slub.c b/mm/slub.c >> index ed2fa92e914c..e38c2b712f6c 100644 >> --- a/mm/slub.c >> +++ b/mm/slub.c >> @@ -2039,7 +2039,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, >> * production configuration these hooks all should produce no code at all. >> * >> * Returns true if freeing of the object can proceed, false if its reuse >> - * was delayed by KASAN quarantine. >> + * was delayed by KASAN quarantine, or it was returned to KFENCE. >> */ >> static __always_inline >> bool slab_free_hook(struct kmem_cache *s, void *x, bool init) >> @@ -2057,6 +2057,9 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) >> __kcsan_check_access(x, s->object_size, >> KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); >> >> + if (kfence_free(kasan_reset_tag(x))) > > I'm wondering if "kasan_reset_tag()" is needed here? I think so, because AFAICS the is_kfence_address() check in kfence_free() could be a false negative otherwise. In fact now I even question some of the other is_kfence_address() checks in mm/slub.c, mainly build_detached_freelist() which starts from pointers coming directly from slab users. Insight from KASAN/KFENCE folks appreciated :) > The patch looks good to me! > > Reviewed-by: Chengming Zhou <zhouchengming@bytedance.com> Thanks! > Thanks. > >> + return false; >> + >> /* >> * As memory initialization might be integrated into KASAN, >> * kasan_slab_free and initialization memset's must be >> @@ -2086,23 +2089,25 @@ static inline bool slab_free_freelist_hook(struct kmem_cache *s, >> void *object; >> void *next = *head; >> void *old_tail = *tail; >> + bool init; >> >> if (is_kfence_address(next)) { >> slab_free_hook(s, next, false); >> - return true; >> + return false; >> } >> >> /* Head and tail of the reconstructed freelist */ >> *head = NULL; >> *tail = NULL; >> >> + init = slab_want_init_on_free(s); >> + >> do { >> object = next; >> next = get_freepointer(s, object); >> >> /* If object's reuse doesn't have to be delayed */ >> - if (likely(slab_free_hook(s, object, >> - slab_want_init_on_free(s)))) { >> + if (likely(slab_free_hook(s, object, init))) { >> /* Move object to the new freelist */ >> set_freepointer(s, object, *head); >> *head = object; >> @@ -4103,9 +4108,6 @@ static void __slab_free(struct kmem_cache *s, struct slab *slab, >> >> stat(s, FREE_SLOWPATH); >> >> - if (kfence_free(head)) >> - return; >> - >> if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) { >> free_to_partial_list(s, slab, head, tail, cnt, addr); >> return; >> @@ -4290,13 +4292,9 @@ static __fastpath_inline >> void slab_free(struct kmem_cache *s, struct slab *slab, void *object, >> unsigned long addr) >> { >> - bool init; >> - >> memcg_slab_free_hook(s, slab, &object, 1); >> >> - init = !is_kfence_address(object) && slab_want_init_on_free(s); >> - >> - if (likely(slab_free_hook(s, object, init))) >> + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s)))) >> do_slab_free(s, slab, object, object, 1, addr); >> } >> >>
On 2023/12/6 17:58, Vlastimil Babka wrote: > On 12/5/23 14:27, Chengming Zhou wrote: >> On 2023/12/5 03:34, Vlastimil Babka wrote: >>> When freeing an object that was allocated from KFENCE, we do that in the >>> slowpath __slab_free(), relying on the fact that KFENCE "slab" cannot be >>> the cpu slab, so the fastpath has to fallback to the slowpath. >>> >>> This optimization doesn't help much though, because is_kfence_address() >>> is checked earlier anyway during the free hook processing or detached >>> freelist building. Thus we can simplify the code by making the >>> slab_free_hook() free the KFENCE object immediately, similarly to KASAN >>> quarantine. >>> >>> In slab_free_hook() we can place kfence_free() above init processing, as >>> callers have been making sure to set init to false for KFENCE objects. >>> This simplifies slab_free(). This places it also above kasan_slab_free() >>> which is ok as that skips KFENCE objects anyway. >>> >>> While at it also determine the init value in slab_free_freelist_hook() >>> outside of the loop. >>> >>> This change will also make introducing per cpu array caches easier. >>> >>> Tested-by: Marco Elver <elver@google.com> >>> Signed-off-by: Vlastimil Babka <vbabka@suse.cz> >>> --- >>> mm/slub.c | 22 ++++++++++------------ >>> 1 file changed, 10 insertions(+), 12 deletions(-) >>> >>> diff --git a/mm/slub.c b/mm/slub.c >>> index ed2fa92e914c..e38c2b712f6c 100644 >>> --- a/mm/slub.c >>> +++ b/mm/slub.c >>> @@ -2039,7 +2039,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, >>> * production configuration these hooks all should produce no code at all. >>> * >>> * Returns true if freeing of the object can proceed, false if its reuse >>> - * was delayed by KASAN quarantine. >>> + * was delayed by KASAN quarantine, or it was returned to KFENCE. >>> */ >>> static __always_inline >>> bool slab_free_hook(struct kmem_cache *s, void *x, bool init) >>> @@ -2057,6 +2057,9 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) >>> __kcsan_check_access(x, s->object_size, >>> KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); >>> >>> + if (kfence_free(kasan_reset_tag(x))) >> >> I'm wondering if "kasan_reset_tag()" is needed here? > > I think so, because AFAICS the is_kfence_address() check in kfence_free() > could be a false negative otherwise. In fact now I even question some of the Ok. > other is_kfence_address() checks in mm/slub.c, mainly > build_detached_freelist() which starts from pointers coming directly from > slab users. Insight from KASAN/KFENCE folks appreciated :) > I know very little about KASAN/KFENCE, looking forward to their insight. :) Just saw a check in __kasan_slab_alloc(): if (is_kfence_address(object)) return (void *)object; So thought it seems that a kfence object would be skipped by KASAN. Thanks!
On Wed, 6 Dec 2023 at 14:02, Chengming Zhou <chengming.zhou@linux.dev> wrote: > > On 2023/12/6 17:58, Vlastimil Babka wrote: > > On 12/5/23 14:27, Chengming Zhou wrote: > >> On 2023/12/5 03:34, Vlastimil Babka wrote: > >>> When freeing an object that was allocated from KFENCE, we do that in the > >>> slowpath __slab_free(), relying on the fact that KFENCE "slab" cannot be > >>> the cpu slab, so the fastpath has to fallback to the slowpath. > >>> > >>> This optimization doesn't help much though, because is_kfence_address() > >>> is checked earlier anyway during the free hook processing or detached > >>> freelist building. Thus we can simplify the code by making the > >>> slab_free_hook() free the KFENCE object immediately, similarly to KASAN > >>> quarantine. > >>> > >>> In slab_free_hook() we can place kfence_free() above init processing, as > >>> callers have been making sure to set init to false for KFENCE objects. > >>> This simplifies slab_free(). This places it also above kasan_slab_free() > >>> which is ok as that skips KFENCE objects anyway. > >>> > >>> While at it also determine the init value in slab_free_freelist_hook() > >>> outside of the loop. > >>> > >>> This change will also make introducing per cpu array caches easier. > >>> > >>> Tested-by: Marco Elver <elver@google.com> > >>> Signed-off-by: Vlastimil Babka <vbabka@suse.cz> > >>> --- > >>> mm/slub.c | 22 ++++++++++------------ > >>> 1 file changed, 10 insertions(+), 12 deletions(-) > >>> > >>> diff --git a/mm/slub.c b/mm/slub.c > >>> index ed2fa92e914c..e38c2b712f6c 100644 > >>> --- a/mm/slub.c > >>> +++ b/mm/slub.c > >>> @@ -2039,7 +2039,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, > >>> * production configuration these hooks all should produce no code at all. > >>> * > >>> * Returns true if freeing of the object can proceed, false if its reuse > >>> - * was delayed by KASAN quarantine. > >>> + * was delayed by KASAN quarantine, or it was returned to KFENCE. > >>> */ > >>> static __always_inline > >>> bool slab_free_hook(struct kmem_cache *s, void *x, bool init) > >>> @@ -2057,6 +2057,9 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) > >>> __kcsan_check_access(x, s->object_size, > >>> KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); > >>> > >>> + if (kfence_free(kasan_reset_tag(x))) > >> > >> I'm wondering if "kasan_reset_tag()" is needed here? > > > > I think so, because AFAICS the is_kfence_address() check in kfence_free() > > could be a false negative otherwise. In fact now I even question some of the > > Ok. > > > other is_kfence_address() checks in mm/slub.c, mainly > > build_detached_freelist() which starts from pointers coming directly from > > slab users. Insight from KASAN/KFENCE folks appreciated :) > > > I know very little about KASAN/KFENCE, looking forward to their insight. :) > > Just saw a check in __kasan_slab_alloc(): > > if (is_kfence_address(object)) > return (void *)object; > > So thought it seems that a kfence object would be skipped by KASAN. The is_kfence_address() implementation tolerates tagged addresses, i.e. if it receives a tagged non-kfence address, it will never return true. The KASAN_HW_TAGS patches and KFENCE patches were in development concurrently, and at the time there was some conflict resolution that happened when both were merged. The is_kfence_address(kasan_reset_tag(..)) initially came from [1] but was squashed into 2b8305260fb. [1] https://lore.kernel.org/all/9dc196006921b191d25d10f6e611316db7da2efc.1611946152.git.andreyknvl@google.com/ Andrey, do you recall what issue you encountered that needed kasan_reset_tag()?
On Wed, Dec 6, 2023 at 3:45 PM Marco Elver <elver@google.com> wrote: > > The is_kfence_address() implementation tolerates tagged addresses, > i.e. if it receives a tagged non-kfence address, it will never return > true. > > The KASAN_HW_TAGS patches and KFENCE patches were in development > concurrently, and at the time there was some conflict resolution that > happened when both were merged. The > is_kfence_address(kasan_reset_tag(..)) initially came from [1] but was > squashed into 2b8305260fb. > > [1] https://lore.kernel.org/all/9dc196006921b191d25d10f6e611316db7da2efc.1611946152.git.andreyknvl@google.com/ > > Andrey, do you recall what issue you encountered that needed kasan_reset_tag()? I don't remember at this point, but this could have been just a safety measure. If is_kfence_address tolerates tagged addresses, we should be able to drop these kasan_reset_tag calls.
On 12/11/23 23:11, Andrey Konovalov wrote: > On Wed, Dec 6, 2023 at 3:45 PM Marco Elver <elver@google.com> wrote: >> >> The is_kfence_address() implementation tolerates tagged addresses, >> i.e. if it receives a tagged non-kfence address, it will never return >> true. So just to be sure, it can't happen that a genuine kfence address would then become KASAN tagged and handed out, and thus when tested by is_kfence_address() it would be a false negative? >> The KASAN_HW_TAGS patches and KFENCE patches were in development >> concurrently, and at the time there was some conflict resolution that >> happened when both were merged. The >> is_kfence_address(kasan_reset_tag(..)) initially came from [1] but was >> squashed into 2b8305260fb. >> >> [1] https://lore.kernel.org/all/9dc196006921b191d25d10f6e611316db7da2efc.1611946152.git.andreyknvl@google.com/ >> >> Andrey, do you recall what issue you encountered that needed kasan_reset_tag()? > > I don't remember at this point, but this could have been just a safety measure. > > If is_kfence_address tolerates tagged addresses, we should be able to > drop these kasan_reset_tag calls. Will drop it once the above is confirmed. Thanks!
On Tue, Dec 12, 2023 at 12:42 PM Vlastimil Babka <vbabka@suse.cz> wrote: > > On 12/11/23 23:11, Andrey Konovalov wrote: > > On Wed, Dec 6, 2023 at 3:45 PM Marco Elver <elver@google.com> wrote: > >> > >> The is_kfence_address() implementation tolerates tagged addresses, > >> i.e. if it receives a tagged non-kfence address, it will never return > >> true. > > So just to be sure, it can't happen that a genuine kfence address would then > become KASAN tagged and handed out, and thus when tested by > is_kfence_address() it would be a false negative? No, this should not happen. KFENCE objects never get tags assigned to them.
diff --git a/mm/slub.c b/mm/slub.c index ed2fa92e914c..e38c2b712f6c 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2039,7 +2039,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, * production configuration these hooks all should produce no code at all. * * Returns true if freeing of the object can proceed, false if its reuse - * was delayed by KASAN quarantine. + * was delayed by KASAN quarantine, or it was returned to KFENCE. */ static __always_inline bool slab_free_hook(struct kmem_cache *s, void *x, bool init) @@ -2057,6 +2057,9 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) __kcsan_check_access(x, s->object_size, KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); + if (kfence_free(kasan_reset_tag(x))) + return false; + /* * As memory initialization might be integrated into KASAN, * kasan_slab_free and initialization memset's must be @@ -2086,23 +2089,25 @@ static inline bool slab_free_freelist_hook(struct kmem_cache *s, void *object; void *next = *head; void *old_tail = *tail; + bool init; if (is_kfence_address(next)) { slab_free_hook(s, next, false); - return true; + return false; } /* Head and tail of the reconstructed freelist */ *head = NULL; *tail = NULL; + init = slab_want_init_on_free(s); + do { object = next; next = get_freepointer(s, object); /* If object's reuse doesn't have to be delayed */ - if (likely(slab_free_hook(s, object, - slab_want_init_on_free(s)))) { + if (likely(slab_free_hook(s, object, init))) { /* Move object to the new freelist */ set_freepointer(s, object, *head); *head = object; @@ -4103,9 +4108,6 @@ static void __slab_free(struct kmem_cache *s, struct slab *slab, stat(s, FREE_SLOWPATH); - if (kfence_free(head)) - return; - if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) { free_to_partial_list(s, slab, head, tail, cnt, addr); return; @@ -4290,13 +4292,9 @@ static __fastpath_inline void slab_free(struct kmem_cache *s, struct slab *slab, void *object, unsigned long addr) { - bool init; - memcg_slab_free_hook(s, slab, &object, 1); - init = !is_kfence_address(object) && slab_want_init_on_free(s); - - if (likely(slab_free_hook(s, object, init))) + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s)))) do_slab_free(s, slab, object, object, 1, addr); }