linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
From: Andrey Konovalov <andreyknvl@gmail.com>
To: Marco Elver <elver@google.com>
Cc: andrey.konovalov@linux.dev,
	Alexander Potapenko <glider@google.com>,
	 Dmitry Vyukov <dvyukov@google.com>,
	Andrey Ryabinin <ryabinin.a.a@gmail.com>,
	 kasan-dev <kasan-dev@googlegroups.com>,
	Peter Collingbourne <pcc@google.com>,
	 Evgenii Stepanov <eugenis@google.com>,
	Florian Mayer <fmayer@google.com>,
	 Andrew Morton <akpm@linux-foundation.org>,
	 Linux Memory Management List <linux-mm@kvack.org>,
	LKML <linux-kernel@vger.kernel.org>,
	 Andrey Konovalov <andreyknvl@google.com>
Subject: Re: [PATCH 31/32] kasan: implement stack ring for tag-based modes
Date: Tue, 19 Jul 2022 00:42:02 +0200	[thread overview]
Message-ID: <CA+fCnZd5iataHnyBv9CVaXKN-2Ac=yLdODweMDiQB70nHZtpOA@mail.gmail.com> (raw)
In-Reply-To: <YrB3l6A4hJmvsFp3@elver.google.com>

On Mon, Jun 20, 2022 at 3:35 PM Marco Elver <elver@google.com> wrote:
>
> > The number of entries in the stack ring is fixed in this version of the
> > patch. We could either implement it as a config option or a command-line
> > argument. I tilt towards the latter option and will implement it in v2
> > unless there are objections.
>
> Yes, that'd be good, along with just not allocating if no stacktraces
> are requested per kasan.stacktrace=.

Sounds good, will do in v2.

> > +struct kasan_stack_ring_entry {
> > +     atomic64_t ptr;         /* void * */
> > +     atomic64_t size;        /* size_t */
> > +     atomic_t pid;           /* u32 */
> > +     atomic_t stack;         /* depot_stack_handle_t */
> > +     atomic_t is_free;       /* bool */
>
> Per comments below, consider making these non-atomic.

Will do in v2.

> >  void kasan_complete_mode_report_info(struct kasan_report_info *info)
> >  {
> > +     u64 pos;
> > +     struct kasan_stack_ring_entry *entry;
> > +     void *object;
> > +     u32 pid;
> > +     depot_stack_handle_t stack;
> > +     bool is_free;
>
> If you switch away from atomic for kasan_stack_ring_entry members, you
> can just replace the above with a 'struct kasan_stack_ring_entry' and
> READ_ONCE() each entry into it below.

It would be a bit confusing to have two kasan_stack_ring_entry-based
variable in the function. I'll keep the current code if you don't
mind.

> > +     bool alloc_found = false, free_found = false;
> > +
> >       info->bug_type = get_bug_type(info);
> > +
> > +     if (!info->cache || !info->object)
> > +             return;
> > +
> > +     pos = atomic64_read(&stack_ring.pos);
> > +
> > +     for (u64 i = pos - 1; i != pos - 1 - KASAN_STACK_RING_ENTRIES; i--) {
> > +             if (alloc_found && free_found)
> > +                     break;
> > +
> > +             entry = &stack_ring.entries[i % KASAN_STACK_RING_ENTRIES];
> > +
> > +             /* Paired with atomic64_set_release() in save_stack_info(). */
> > +             object = (void *)atomic64_read_acquire(&entry->ptr);
> > +
> > +             if (kasan_reset_tag(object) != info->object ||
> > +                 get_tag(object) != get_tag(info->access_addr))
> > +                     continue;
> > +
> > +             pid = atomic_read(&entry->pid);
> > +             stack = atomic_read(&entry->stack);
> > +             is_free = atomic_read(&entry->is_free);
> > +
> > +             /* Try detecting if the entry was changed while being read. */
> > +             smp_mb();
> > +             if (object != (void *)atomic64_read(&entry->ptr))
> > +                     continue;
>
> What if the object was changed, but 'ptr' is the same? It might very
> well be possible to then read half of the info of the previous object,
> and half of the new object (e.g. pid is old, stack is new).
>
> Is the assumption that it is extremely unlikely that this will happen
> where 1) address is the same, and 2) tags are the same? And if it does
> happen, it is unlikely that there'll be a bug on that address?
>
> It might be worth stating this in comments.

This part will be removed in v2 due to the addition of an rwlock, but
I'll add a comment about the stack ring being best-effort anyway.

> Another thing is, if there's a bug, but concurrently you have tons of
> allocations/frees that change the ring's entries at a very high rate,
> how likely is it that the entire ring will have been wiped before the
> entry of interest is found again?
>
> One way to guard against this is to prevent modifications of the ring
> while the ring is searched. This could be implemented with a
> percpu-rwsem, which is almost free for read-lockers but very expensive
> for write-lockers. Insertions only acquire a read-lock, but on a bug
> when searching the ring, you have to acquire a write-lock. Although you
> currently take the contention hit for incrementing 'pos', so a plain
> rwlock might also be ok.

Will add an rwlock in v2.

> It would be good to understand the probabilities of these corner cases
> with some average to worst case workloads, and optimize based on that.

With the new synchronizations and checks added in v2, the only
problematic issue is when the stack ring overflows. Please see my
response to your cover letter comment wrt this.

> > +struct kasan_stack_ring stack_ring;
>
> This is a very large struct. Can it be allocated by memblock_alloc()
> very early on only if required (kasan.stacktrace= can still switch it
> off, right?).

Will do in v2.

> > +void save_stack_info(struct kmem_cache *cache, void *object,
> > +                     gfp_t flags, bool is_free)
>
> static void save_stack_info(...)

Right, will do in v2.

> > +{
> > +     u64 pos;
> > +     struct kasan_stack_ring_entry *entry;
> > +     depot_stack_handle_t stack;
> > +
> > +     stack = kasan_save_stack(flags, true);
> > +
> > +     pos = atomic64_fetch_add(1, &stack_ring.pos);
> > +     entry = &stack_ring.entries[pos % KASAN_STACK_RING_ENTRIES];
> > +
> > +     atomic64_set(&entry->size, cache->object_size);
> > +     atomic_set(&entry->pid, current->pid);
> > +     atomic_set(&entry->stack, stack);
> > +     atomic_set(&entry->is_free, is_free);
> > +
>
> I don't see the point of these being atomic. You can make them normal
> variables with the proper types, and use READ_ONCE() / WRITE_ONCE().
>
> The only one where you truly need the atomic type is 'pos'.

Will do in v2.

> > +     /*
> > +      * Paired with atomic64_read_acquire() in
> > +      * kasan_complete_mode_report_info().
> > +      */
> > +     atomic64_set_release(&entry->ptr, (s64)object);
>
> This could be smp_store_release() and 'ptr' can be just a normal pointer.

Will do in v2.

> One thing that is not entirely impossible though (vs. re-reading same
> pointer but inconsistent fields I mentioned above), is if something
> wants to write to the ring, but stalls for a very long time before the
> release of 'ptr', giving 'pos' the chance to wrap around and another
> writer writing the same entry. Something like:
>
>   T0                                    | T1
>   --------------------------------------+--------------------------------
>   WRITE_ONCE(entry->size, ..)           |
>   WRITE_ONCE(entry->pid, ..)            |
>                                         | WRITE_ONCE(entry->size, ..)
>                                         | WRITE_ONCE(entry->pid, ..)
>                                         | WRITE_ONCE(entry->stack, ..)
>                                         | WRITE_ONCE(entry->is_free, ..)
>                                         | smp_store_release(entry->ptr, ...)
>   WRITE_ONCE(entry->stack, ..)          |
>   WRITE_ONCE(entry->is_free, ..)        |
>   smp_store_release(entry->ptr, ...)    |
>
> Which results in some mix of T0's and T1's data.
>
> The way to solve this is to implement a try-lock using 'ptr':
>
>         #define BUSY_PTR ((void*)1)  // non-zero because initial values are 0
>         old_ptr = READ_ONCE(entry->ptr);
>         if (old_ptr == BUSY_PTR)
>                 goto next; /* Busy slot. */
>         if (!try_cmpxchg(&entry->ptr, &old_ptr, BUSY_PTR))
>                 goto next; /* Busy slot. */
>         ... set fields as before ...
>         smp_store_release(&entry->ptr, object);

Sounds good, will do in v2.

Thank you, Marco!


  reply	other threads:[~2022-07-18 22:42 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-13 20:13 [PATCH 00/32] kasan: switch tag-based modes to stack ring from per-object metadata andrey.konovalov
2022-06-13 20:13 ` [PATCH 01/32] kasan: check KASAN_NO_FREE_META in __kasan_metadata_size andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 02/32] kasan: rename kasan_set_*_info to kasan_save_*_info andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 03/32] kasan: move is_kmalloc check out of save_alloc_info andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 04/32] kasan: split save_alloc_info implementations andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 05/32] kasan: drop CONFIG_KASAN_TAGS_IDENTIFY andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 06/32] kasan: introduce kasan_print_aux_stacks andrey.konovalov
2022-06-17 11:34   ` Marco Elver
2022-07-18 22:41     ` Andrey Konovalov
2022-06-13 20:13 ` [PATCH 07/32] kasan: introduce kasan_get_alloc_track andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:13 ` [PATCH 08/32] kasan: introduce kasan_init_object_meta andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:14 ` [PATCH 09/32] kasan: clear metadata functions for tag-based modes andrey.konovalov
2022-06-20 13:39   ` Marco Elver
2022-06-13 20:14 ` [PATCH 10/32] kasan: move kasan_get_*_meta to generic.c andrey.konovalov
2022-06-13 20:14 ` [PATCH 11/32] kasan: introduce kasan_requires_meta andrey.konovalov
2022-06-13 20:14 ` [PATCH 12/32] kasan: introduce kasan_init_cache_meta andrey.konovalov
2022-06-13 20:14 ` [PATCH 13/32] kasan: drop CONFIG_KASAN_GENERIC check from kasan_init_cache_meta andrey.konovalov
2022-06-13 20:14 ` [PATCH 14/32] kasan: only define kasan_metadata_size for Generic mode andrey.konovalov
2022-06-13 20:14 ` [PATCH 15/32] kasan: only define kasan_never_merge " andrey.konovalov
2022-06-13 20:14 ` [PATCH 16/32] kasan: only define metadata offsets " andrey.konovalov
2022-06-13 20:14 ` [PATCH 17/32] kasan: only define metadata structs " andrey.konovalov
2022-06-13 20:14 ` [PATCH 18/32] kasan: only define kasan_cache_create " andrey.konovalov
2022-06-13 20:14 ` [PATCH 19/32] kasan: pass tagged pointers to kasan_save_alloc/free_info andrey.konovalov
2022-06-20  9:54   ` Marco Elver
2022-07-18 22:41     ` Andrey Konovalov
2022-06-13 20:14 ` [PATCH 20/32] kasan: move kasan_get_alloc/free_track definitions andrey.konovalov
2022-06-13 20:14 ` [PATCH 21/32] kasan: simplify invalid-free reporting andrey.konovalov
2022-06-21  7:17   ` Kuan-Ying Lee
2022-07-12 20:38     ` Andrey Konovalov
2022-06-13 20:14 ` [PATCH 22/32] kasan: cosmetic changes in report.c andrey.konovalov
2022-06-13 20:14 ` [PATCH 23/32] kasan: use kasan_addr_to_slab in print_address_description andrey.konovalov
2022-06-13 20:14 ` [PATCH 24/32] kasan: move kasan_addr_to_slab to common.c andrey.konovalov
2022-06-15 13:27   ` kernel test robot
2022-07-18 22:41     ` Andrey Konovalov
2022-06-13 20:14 ` [PATCH 25/32] kasan: make kasan_addr_to_page static andrey.konovalov
2022-06-13 20:14 ` [PATCH 26/32] kasan: simplify print_report andrey.konovalov
2022-06-13 20:14 ` [PATCH 27/32] kasan: introduce complete_report_info andrey.konovalov
2022-06-13 20:14 ` [PATCH 28/32] kasan: fill in cache and object in complete_report_info andrey.konovalov
2022-06-13 20:14 ` [PATCH 29/32] kasan: rework function arguments in report.c andrey.konovalov
2022-06-13 20:14 ` [PATCH 30/32] kasan: introduce kasan_complete_mode_report_info andrey.konovalov
2022-06-13 20:14 ` [PATCH 31/32] kasan: implement stack ring for tag-based modes andrey.konovalov
2022-06-20 13:35   ` Marco Elver
2022-07-18 22:42     ` Andrey Konovalov [this message]
2022-06-13 20:14 ` [PATCH 32/32] kasan: better identify bug types " andrey.konovalov
2022-06-17  9:32 ` [PATCH 00/32] kasan: switch tag-based modes to stack ring from per-object metadata Marco Elver
2022-07-18 22:41   ` Andrey Konovalov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CA+fCnZd5iataHnyBv9CVaXKN-2Ac=yLdODweMDiQB70nHZtpOA@mail.gmail.com' \
    --to=andreyknvl@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=andrey.konovalov@linux.dev \
    --cc=andreyknvl@google.com \
    --cc=dvyukov@google.com \
    --cc=elver@google.com \
    --cc=eugenis@google.com \
    --cc=fmayer@google.com \
    --cc=glider@google.com \
    --cc=kasan-dev@googlegroups.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=pcc@google.com \
    --cc=ryabinin.a.a@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox