bug in emergency cxa pool free()

Message ID CAGejDpB0W4MBzV5kQ5QUpMGvwdVbMy7gp1r_M-wEUkR7Ysw_=Q@mail.gmail.com
State New, archived
Headers
Series bug in emergency cxa pool free() |

Commit Message

Keef Aragon Aug. 16, 2022, 7:14 p.m. UTC
  This probably has never actually affected anyone in practice. The normal
ABI implementation just uses malloc and only falls back to the pool on
malloc failure. But if that happens a bunch of times the freelist gets out
of order which violates some of the invariants of the freelist (as well as
the comments that follow the bug). The bug is just a comparison reversal
when traversing the freelist in the case where the pointer being returned
to the pool is after the existing freelist.

I'm not sure what to do as far as the test suite is concerned. It's a
private part of the implementation of the exception handling ABI and it can
only ever be triggered if malloc fails (repeatedly). So it seems like
reproducing it from the external interface will require hooking malloc to
forcibly return NULL.

But I'm a newb on these lists, so will obediently do as instructed.
  

Comments

Richard Biener Aug. 17, 2022, 6:45 a.m. UTC | #1
On Tue, Aug 16, 2022 at 9:15 PM Keef Aragon <keef.aragon@konscious.net> wrote:
>
> This probably has never actually affected anyone in practice. The normal
> ABI implementation just uses malloc and only falls back to the pool on
> malloc failure. But if that happens a bunch of times the freelist gets out
> of order which violates some of the invariants of the freelist (as well as
> the comments that follow the bug). The bug is just a comparison reversal
> when traversing the freelist in the case where the pointer being returned
> to the pool is after the existing freelist.
>
> I'm not sure what to do as far as the test suite is concerned. It's a
> private part of the implementation of the exception handling ABI and it can
> only ever be triggered if malloc fails (repeatedly). So it seems like
> reproducing it from the external interface will require hooking malloc to
> forcibly return NULL.
>
> But I'm a newb on these lists, so will obediently do as instructed.

Oops, that's my fault.
For consistency it's probably best written
as reinterpret_cast <char *> (e) + sz > reinterpret_cast <char *> ((*fe))
thus

diff --git a/libstdc++-v3/libsupc++/eh_alloc.cc
b/libstdc++-v3/libsupc++/eh_alloc.cc
index c85b9aed40b..68f319869f9 100644
--- a/libstdc++-v3/libsupc++/eh_alloc.cc
+++ b/libstdc++-v3/libsupc++/eh_alloc.cc
@@ -224,8 +224,8 @@ namespace
          free_entry **fe;
          for (fe = &first_free_entry;
               (*fe)->next
-              && (reinterpret_cast <char *> ((*fe)->next)
-                  > reinterpret_cast <char *> (e) + sz);
+              && (reinterpret_cast <char *> (e) + sz
+                  > reinterpret_cast <char *> ((*fe)->next));
               fe = &(*fe)->next)
            ;
          // If we can merge the next block into us do so and continue

The change is OK with that adjustment.  I see you do not have write access so
I'll test & push it for you.

I'm curious how you noticed?

Thanks,
Richard.
  
Keef Aragon Aug. 17, 2022, 7:02 p.m. UTC | #2
Thank you!

I was working on a modified version that I could LD_PRELOAD to investigate
some issues I was having in my asynchronous application code. It used a
higher order collection of pool instances instead of malloc/free, with
extra context (an array of pc register values) packed into the buffer
before the refcounted exception. in_pool on the pools in the collection is
the safety check to know that the extra context can be safely pulled from
the pointer.

With that implementation, memory was being leaked because the destruction
of one of the pools in the collection requires there to be no more uses of
it and quickly checking for that to be the case also needed the ordered
invariant to hold (just checking if the freelist the whole arena).

On Tue, Aug 16, 2022 at 11:45 PM Richard Biener <richard.guenther@gmail.com>
wrote:

> On Tue, Aug 16, 2022 at 9:15 PM Keef Aragon <keef.aragon@konscious.net>
> wrote:
> >
> > This probably has never actually affected anyone in practice. The normal
> > ABI implementation just uses malloc and only falls back to the pool on
> > malloc failure. But if that happens a bunch of times the freelist gets
> out
> > of order which violates some of the invariants of the freelist (as well
> as
> > the comments that follow the bug). The bug is just a comparison reversal
> > when traversing the freelist in the case where the pointer being returned
> > to the pool is after the existing freelist.
> >
> > I'm not sure what to do as far as the test suite is concerned. It's a
> > private part of the implementation of the exception handling ABI and it
> can
> > only ever be triggered if malloc fails (repeatedly). So it seems like
> > reproducing it from the external interface will require hooking malloc to
> > forcibly return NULL.
> >
> > But I'm a newb on these lists, so will obediently do as instructed.
>
> Oops, that's my fault.
> For consistency it's probably best written
> as reinterpret_cast <char *> (e) + sz > reinterpret_cast <char *> ((*fe))
> thus
>
> diff --git a/libstdc++-v3/libsupc++/eh_alloc.cc
> b/libstdc++-v3/libsupc++/eh_alloc.cc
> index c85b9aed40b..68f319869f9 100644
> --- a/libstdc++-v3/libsupc++/eh_alloc.cc
> +++ b/libstdc++-v3/libsupc++/eh_alloc.cc
> @@ -224,8 +224,8 @@ namespace
>           free_entry **fe;
>           for (fe = &first_free_entry;
>                (*fe)->next
> -              && (reinterpret_cast <char *> ((*fe)->next)
> -                  > reinterpret_cast <char *> (e) + sz);
> +              && (reinterpret_cast <char *> (e) + sz
> +                  > reinterpret_cast <char *> ((*fe)->next));
>                fe = &(*fe)->next)
>             ;
>           // If we can merge the next block into us do so and continue
>
> The change is OK with that adjustment.  I see you do not have write access
> so
> I'll test & push it for you.
>
> I'm curious how you noticed?
>
> Thanks,
> Richard.
>
  

Patch

diff --git a/libstdc++-v3/ChangeLog-2022 b/libstdc++-v3/ChangeLog-2022
new file mode 100644
index 00000000000..8057de58539
--- /dev/null
+++ b/libstdc++-v3/ChangeLog-2022
@@ -0,0 +1,4 @@ 
+2022-08-16  Keef Aragon  <keef.aragon@konscious.net>
+
+        * libstdc++-v3/libsupc++/eh_alloc.cc: inverse comparison in pool::free
+
diff --git a/libstdc++-v3/libsupc++/eh_alloc.cc b/libstdc++-v3/libsupc++/eh_alloc.cc
index c85b9aed40b..cad2750e3b9 100644
--- a/libstdc++-v3/libsupc++/eh_alloc.cc
+++ b/libstdc++-v3/libsupc++/eh_alloc.cc
@@ -225,7 +225,7 @@  namespace
 	  for (fe = &first_free_entry;
 	       (*fe)->next
 	       && (reinterpret_cast <char *> ((*fe)->next)
-		   > reinterpret_cast <char *> (e) + sz);
+		   < reinterpret_cast <char *> (e) + sz);
 	       fe = &(*fe)->next)
 	    ;
 	  // If we can merge the next block into us do so and continue