ASAN read-only poisoned by user shadow byte

104 views
Skip to first unread message

Andrea Fioraldi

unread,
Dec 12, 2019, 7:03:00 AM12/12/19
to address-sanitizer
Hi to all,
there is a way to poison bytes as read-only in the ASAN runtime?
For instance, I have a type field in a dynamic allocated structure that is assigned only at creation
and I want to mark it as read-only after the first assignment to detect type-confusions.
I found nothing searching about it in compiler-rt and so this is more a feature request, but maybe I missed it and already exists.

Something like the following snippet would be useful:

enum {
  IS_INT,
  IS_FLOAT
};
struct foo {
  int type;
  union { int i; float f };
};

struct foo* create_int_foo(int i) {
  struct foo * f = malloc(sizeof(struct foo));
  f->i = i;
  f->type = IS_INT;
  ASAN_POISON_RDONLY_MEMORY_REGION(&f->type, sizeof(int));
}

Thank you!

Alexander Potapenko

unread,
Dec 12, 2019, 7:08:19 AM12/12/19
to address-...@googlegroups.com
On Thu, Dec 12, 2019 at 1:03 PM Andrea Fioraldi
<andreaf...@gmail.com> wrote:
>
> Hi to all,

Hi Andrea,

> there is a way to poison bytes as read-only in the ASAN runtime?
> For instance, I have a type field in a dynamic allocated structure that is assigned only at creation
> and I want to mark it as read-only after the first assignment to detect type-confusions.
> I found nothing searching about it in compiler-rt and so this is more a feature request, but maybe I missed it and already exists.

No, right now it's not possible. ASan instrumentation doesn't
distinguish between a read and a write.
This can be done on LLVM side, but additional complication doesn't
sound necessary, especially given that there won't be any automated
way to mark data read-only.
Without that this feature will have limited use.

If you need a one-off solution for marking some small amount of data
read-only, you can allocate a page-aligned memory chunk with mmap(),
store your data in it and seal it with mprotect().

HTH,
Alex

>
> Something like the following snippet would be useful:
>
> enum {
> IS_INT,
> IS_FLOAT
> };
> struct foo {
> int type;
> union { int i; float f };
> };
>
> struct foo* create_int_foo(int i) {
> struct foo * f = malloc(sizeof(struct foo));
> f->i = i;
> f->type = IS_INT;
> ASAN_POISON_RDONLY_MEMORY_REGION(&f->type, sizeof(int));
> }
>
> Thank you!
>
> --
> You received this message because you are subscribed to the Google Groups "address-sanitizer" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to address-saniti...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/address-sanitizer/29bb9619-d1ba-4426-8eaf-068b3c795337%40googlegroups.com.

Andrea Fioraldi

unread,
Dec 12, 2019, 8:12:42 AM12/12/19
to address-sanitizer
Hi Glider, thank you for the quick answer.
I've some doubt, correct me if I'm wrong.

> No, right now it's not possible. ASan instrumentation doesn't distinguish between a read and a write.

if ((addr >> 3) + offset) __asan_report_load8(addr);
This is inserted before loads, a quick implementations would be a simple

void __asan_report_load8(uptr add) {

  if (((addr >> 3) + offset == ASAN_USER_RDONLY_SHADOW) return;

  // .. old __asan_report_load

}

And the same for all other variants like __asan_load8 , __asan_report_loadN etc...


> If you need a one-off solution for marking some small amount of data
read-only, you can allocate a page-aligned memory chunk with mmap(),
store your data in it and seal it with mprotect().

The libdislocator approach doesn't work if a want a single byte read only inside a RW region
> To unsubscribe from this group and stop receiving emails from it, send an email to address-...@googlegroups.com.

Alexander Potapenko

unread,
Dec 12, 2019, 9:54:47 AM12/12/19
to address-...@googlegroups.com
On Thu, Dec 12, 2019 at 2:12 PM Andrea Fioraldi
<andreaf...@gmail.com> wrote:
>
> Hi Glider, thank you for the quick answer.
> I've some doubt, correct me if I'm wrong.
>
> > No, right now it's not possible. ASan instrumentation doesn't distinguish between a read and a write.
>
> if ((addr >> 3) + offset) __asan_report_load8(addr);
> This is inserted before loads, a quick implementations would be a simple
>
> void __asan_report_load8(uptr add) {
>
> if (((addr >> 3) + offset == ASAN_USER_RDONLY_SHADOW) return;
>
> // .. old __asan_report_load
>
> }
>
> And the same for all other variants like __asan_load8 , __asan_report_loadN etc...

There's a couple of problems with this solution:
1. ASAN_USER_RDONLY_SHADOW will still need to encode the number of
addressable bytes in the lower 3 bits, otherwise you'll get problems
with smaller accesses.
2. If we're talking about the inline ASan instrumentation, where the
fast path shadow check is inserted next to every memory access by the
compiler, it's already too late to return from __asan_report_load8(),
because that's a noreturn function.
The compiler may allow it to clobber the local state, so the program
won't be able to proceed normally.

What you could probably do is to reserve a shadow bit for the
'readonly' state and either make LLVM emit the extra branch checking
that bit, or use the out-of-line instrumentation and perform this
check in the runtime library.
Either way, doing so will slow down the common use case, so this is
probably a no-go for other users.

>
> > If you need a one-off solution for marking some small amount of data
> read-only, you can allocate a page-aligned memory chunk with mmap(),
> store your data in it and seal it with mprotect().
>
> The libdislocator approach doesn't work if a want a single byte read only inside a RW region

Another possible ad-hoc solution that I sometimes use to detect
corruptions of a field in a struct involves adding another field with
the same value to that struct.
Initialization of the "protected" field will have to update both
fields, and reads will have to ensure that both fields hold the same
values - this will prevent random writes to that field.
If, on the other hand, you suspect that the initialization is
performed twice, the second field can just hold a counter that's
incremented on each initialization.
> To unsubscribe from this group and stop receiving emails from it, send an email to address-saniti...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/address-sanitizer/fd668973-ffba-421c-b890-866f95f86174%40googlegroups.com.



--
Alexander Potapenko
Software Engineer

Google Germany GmbH
Erika-Mann-Straße, 33
80636 München

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Reply all
Reply to author
Forward
0 new messages