Re: f=load("load(f)");f() bug found by AFL fuzzer

  • From: Coda Highland <chighland@xxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Mon, 2 Mar 2015 10:13:55 -0800

On Mon, Mar 2, 2015 at 10:01 AM, Coda Highland <chighland@xxxxxxxxx> wrote:
> On Mon, Mar 2, 2015 at 9:59 AM, Coda Highland <chighland@xxxxxxxxx> wrote:
>> On Mon, Mar 2, 2015 at 9:57 AM, Mike Pall <mike-1503@xxxxxxxxxx> wrote:
>>> Alexander Nasonov wrote:
>>>> But it's not easy to fool afl. These are the two programs it found:
>>>>
>>>> s="";s=load"adstrs=loadstring(s);wstring(s)ing(s);wstring(s)";s=loadstring(s);wstring(s);while
>>>>  s do s=s(s) end
>>>> s="";s=load"adstrins=loadstring(s);wstrg(s);wstring(s)";s=loadstring(s);wstring(s);while
>>>>  s do s=s(s) end
>>>>
>>>> both crash LuaJIT 2.0.
>>>
>>> This doesn't crash for me. Neither in 2.0.3 nor in 2.0 git, nor in
>>> 2.1 git. Neither when compiled as x86 nor as x64.
>>>
>>> It complains about "attempt to call global 'wstring' (a nil value)".
>>> But that's expected, since 'wstring' is not defined.
>>>
>>> --Mike
>>>
>>
>> It crashes for me in 2.0.3. I took some time to reduce the test case
>> to something a lot simpler.
>>
>> $ uname -a
>> Darwin mac-pro 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23
>> 16:26:45 PDT 2012; root:xnu-1699.32.7~1/RELEASE_I386 i386
>>
>> $ luajit
>> LuaJIT 2.0.3 -- Copyright (C) 2005-2014 Mike Pall. http://luajit.org/
>> JIT: ON CMOV SSE2 SSE3 fold cse dce fwd dse narrow loop abc sink fuse
>>> s = load"load(s)"
>>> load(s)
>> Segmentation fault: 11
>>
>> /s/ Adam
>
> For context:
>
> $ lua
> Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
>> s = load"load(s)"
> stdin:1: bad argument #1 to 'load' (function expected, got string)
> stack traceback:
>         [C]: in function 'load'
>         stdin:1: in main chunk
>         [C]: ?
>
> /s/ Adam

Fixed the 5.2-ism:

$ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> s = loadstring"load(s)"
> load(s)

Here, it just hangs instead of crashing. I'm guessing PUC-Rio Lua is
handling the stack differently, but it's still a DoS from untrusted
code.

And thinking about it this way reveals the issue: This isn't a bug, or
at least, it's not a bug you can do anything about without making Mr.
Turing very upset.

Let's walk through it:

The first line, s = loadstring"load(s)", is equivalent to:

function s()
  load(s)
end

(And note that it still crashes if you define it this way instead of
using loadstring.)

The second line, load(s), uses the function s as a reader that's
supposed to return incremental parts of the input file. However, s
just calls load(s)... which calls s... which calls load(s)...

You're just overflowing the stack with infinite recursion.

/s/ Adam

Other related posts: