Re: Segfault from nil table access when using ffi.gc

  • From: Peter Cawley <corsix@xxxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Fri, 11 Nov 2016 22:02:25 +0000

`ffi.load('./libtest.so')` returns a C library namespace object. The
following passage from http://luajit.org/ext_ffi_semantics.html#clib
is relevant:

C library namespaces themselves are garbage collected objects. If the last 
reference to the namespace object is gone, the garbage collector will 
eventually release the shared library reference and remove all memory 
associated with the namespace. Since this may trigger the removal of the 
shared library from the memory of the running process, it's generally not 
safe to use function cdata objects obtained from a library if the namespace 
object may be unreferenced.

I suspect the following sequence of events is occurring:
1. Your local variable called `test` is garbage collected, thus
unloading `libtest.so`.
2. Your local variable called `cells` is garbage collected, thus
calling `Abc_destruct`.
3. The code for `Abc_destruct` is no longer present in memory, thus
resulting in a segfault.

On Fri, Nov 11, 2016 at 9:35 PM, Chris Osborne <chris.osborne@xxxxxxx> wrote:
Hi,

I seem to be encountering a bug in luajit, tested on version 2.0.4 on both
Linux x64 (Mint 17.3) and OS X 10.10 (from the apt-get default
package/homebrew release). If I have a pointer active that is subject the
ffi.gc using my own finalizer (rather than just malloc/free) I get a
segfault if I access a nil element of a table rather than a nice traceback
telling me off. I've included a minimum working example below:

test.cpp:

#include <cstdlib>

extern "C"
{
typedef struct
{
    int len;
    double* a;
} Abc;
}


extern "C"
Abc* Abc_new(int length)
{
    Abc* a = (Abc*)malloc(sizeof(Abc));
    a->a = (double*)malloc(length * sizeof(double));
    a->len = length;
    return a;
}

extern "C"
void Abc_destruct(Abc* a)
{
    free(a->a);
    free(a);
}
--------------------------------------------------------------------------------------
compile with `c++ -fpic -shared -o libtest.so test.cpp`
--------------------------------------------------------------------------------------
seg.lua:

local blah = nil
local ffi = require('ffi')
ffi.cdef[[
typedef struct
{
    int len;
    double* a;
} Abc;

Abc* Abc_new(int length);
void Abc_destruct(Abc* a);
]]

local test = ffi.load('./libtest.so')
local cells = ffi.gc(test.Abc_new(100), test.Abc_destruct)
blah:this_will_segfault()
-------------------------------------------------------------------------------------

If I call blah:this_will_segfault() before the ffi.gc call, it gives me the
traceback that I'd expect, but putting it there gives a segfault. Valgrind
seems to link this segfault to lua_gc:

==22498== Memcheck, a memory error detector
==22498== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==22498== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==22498== Command: luajit seg.lua
==22498==
==22498== Jump to the invalid address stated on the next line
==22498==    at 0x5D26754: ???
==22498==    by 0x43BE45: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x43C51F: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x454585: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x40E779: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x42B877: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x42B916: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x448E37: lua_gc (in /usr/bin/luajit-2.0.4)
==22498==    by 0x403EE8: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x404CAD: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x454585: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x448B79: lua_cpcall (in /usr/bin/luajit-2.0.4)
==22498==  Address 0x5d26754 is not stack'd, malloc'd or (recently) free'd
==22498==
==22498==
==22498== Process terminating with default action of signal 11 (SIGSEGV)
==22498==  Access not within mapped region at address 0x5D26754
==22498==    at 0x5D26754: ???
==22498==    by 0x43BE45: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x43C51F: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x454585: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x40E779: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x42B877: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x42B916: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x448E37: lua_gc (in /usr/bin/luajit-2.0.4)
==22498==    by 0x403EE8: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x404CAD: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x454585: ??? (in /usr/bin/luajit-2.0.4)
==22498==    by 0x448B79: lua_cpcall (in /usr/bin/luajit-2.0.4)
==22498==  If you believe this happened as a result of a stack
==22498==  overflow in your program's main thread (unlikely but
==22498==  possible), you can try to increase the size of the
==22498==  main thread stack using the --main-stacksize= flag.
==22498==  The main thread stack size used in this run was 8388608.
==22498==
==22498== HEAP SUMMARY:
==22498==     in use at exit: 816 bytes in 2 blocks
==22498==   total heap usage: 9 allocs, 7 frees, 6,927 bytes allocated
==22498==
==22498== LEAK SUMMARY:
==22498==    definitely lost: 0 bytes in 0 blocks
==22498==    indirectly lost: 0 bytes in 0 blocks
==22498==      possibly lost: 0 bytes in 0 blocks
==22498==    still reachable: 816 bytes in 2 blocks
==22498==         suppressed: 0 bytes in 0 blocks
==22498== Rerun with --leak-check=full to see details of leaked memory
==22498==
==22498== For counts of detected and suppressed errors, rerun with: -v
==22498== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault


Any advice as to whether I'm doing something wrong, or if this is a bug in
luajit (known or otherwise) would be welcome -- it makes silly typo errors
take a lot longer to track down!

Many thanks!


Other related posts: