Heap use after free in luaD_call

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Heap use after free in luaD_call

Yongheng Chen

Hi,

 

We found a heap use after free bug in lua.

 

POC:

 

function errfunc()(function() print(xpcall(

    wrap,

    coroutine.wrap(function(a, b) function errfunc()

                       a = {} end function test(do_yield)

                           pcall(function() if do_yield then coroutine.yield()

                                     end end) 'fail' end coro = function()

                               print(xpcall(test, errfunc))

                                   xpcall(test, errfunc, true) end coro() end)))

                       end) "" 'fail' end(function()

                                              print(xpcall(test, errfunc))

                                                  end)()

 

When built with address sanitizer, run `lua poc.lua` and we get a crash. Tested on Ubuntu, lua git hash e1d8770f12542d34a3e32b825c95b93f8a341ee1

 

Best,
Yongheng

 

 

Sent from Mail for Windows 10

 

Reply | Threaded
Open this post in threaded view
|

Re: Heap use after free in luaD_call

Andrew Gierth
>>>>> "Yongheng" == Yongheng Chen <[hidden email]> writes:

 Yongheng> Hi,
 Yongheng> We found a heap use after free bug in lua.

So this one seems to be something corrupting the gray lists, if I
understand the logic correctly.

What's happening is that a value (in this case a C closure) is being
pushed on the stack and then immediately freed because the current
thread stack is not being marked (which ought to be happening in the
atomic() call, but a breakpoint on traversethread() was not hit). I
think this can only happen if the current thread is marked gray (it is)
but not actually linked into any gray list...

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: Heap use after free in luaD_call

Andrew Gierth
>>>>> "Andrew" == Andrew Gierth <[hidden email]> writes:

 Andrew> So this one seems to be something corrupting the gray lists, if
 Andrew> I understand the logic correctly.

 Andrew> What's happening is that a value (in this case a C closure) is
 Andrew> being pushed on the stack and then immediately freed because
 Andrew> the current thread stack is not being marked (which ought to be
 Andrew> happening in the atomic() call, but a breakpoint on
 Andrew> traversethread() was not hit). I think this can only happen if
 Andrew> the current thread is marked gray (it is) but not actually
 Andrew> linked into any gray list...

Getting somewhere. An object (table) is ending up on the grayagain list
(after the atomic phase of a generational step) while still new; it's
still in that list when sweepgen sees it, which clears it to white
without removing it from the list. A subsequent barrier call then sees
it as white and turns it gray, adding it to the (empty) gray list, at
which point everything _else_ that was on grayagain just got dropped on
the floor, and this includes the main thread.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: Heap use after free in luaD_call

Roberto Ierusalimschy
> Getting somewhere. An object (table) is ending up on the grayagain list
> (after the atomic phase of a generational step) while still new; it's
> still in that list when sweepgen sees it, which clears it to white
> without removing it from the list. A subsequent barrier call then sees
> it as white and turns it gray, adding it to the (empty) gray list, at
> which point everything _else_ that was on grayagain just got dropped on
> the floor, and this includes the main thread.

Another small step:

Up to the barrier, all was going on as expected.

The idea is that, after sweepgen clears it to white without removing
it from the gray list, a call to correctgraylist will remove it from
the gray list. (sweepgen cannot remove it because we need to traverse
the gray list to remove an element.)

However, still inside sweepgen, if a thread is collected, it has to
close its upvalues. This moves the value from the stack being
collected to the upvalue, and that needs a barrier (the one you
mentioned!). So, this barrier sees the object still in an inconsistent
state, between having its color cleared by sweepgen and it being
removed from the gray list by correctgraylist. Then, caos follows,
as you described.

-- Roberto