Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

secwx weizhenvul

Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

###### Lua revision

version : Lua 5.4.0

###### Build platform

Ubuntu 18.10 LTS (Linux ubuntu 4.18.0-25-generic x86_64)

###### Build steps

```
add -fsanitize=address -g to CFLAGS
make
```

###### Test case

Memory_corruption_luaV_execute.lua
```
function main() 
 v4 = {13.37 }  
 v6 = {"function" ,1337 ,v4,assert,13.37 ,-2 ,v4,v4}  
 v12 = {1337 ,1337 ,1337 ,1337 ,1337 }  
 v13 = {a=1337 ,b=getmetatable,c="number" ,d=v12}  
 v14 = {a=getmetatable,c=v13,d=v13,e=getmetatable,length=getmetatable}  
local v16 = 1337  
function  v19(v20,v21,v22,v23)  
     v25 = {0 ,13.37 }  
    function  v27(v28,v29,v30,v31)  
        local v32 = -3852123798  
        if (v28) then 
             v33 = pcall 
            v32 = rawget 
         else  
            v32 = v14 
        end 
        local v35 = 13.37  
         v36 = v31(getmetatable)  
         v40 = string.sub("length" ,-1 ,-1 )  
         v41 = {13.37 ,13.37 }  
         v42 = v41[-3978136795]  
         v43 = 13.37  
         v44 = "dRDPeQXJ+A"  
         v45 = string 
         v46 = -846484919  
        local v49 = 0  
        while (v49 < 0 ) do 
             v50 = v49 + 1  
            v49 = v50 
        end 
        return v27 
    end 
     v53 = "config"  --/ /Tq/ygu   
     v54 = v25[-3978136795]  
    return v13 
end 
 v55 = string.dump(v19)  
 v56 = string.format(v55,v16,v6)  
 v58 = load(v56)  
 v59 = load(v58)  
-- Stderr:
end 
main()
```

###### Execution steps

```
$ ls 
Memory_corruption_luaV_execute.lua
$ ./Test_lua/lua/lua ./crashes/Memory_corruption_luaV_execute.lua
AddressSanitizer:DEADLYSIGNAL
=================================================================
==6373==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5602093d3815 bp 0x00000000024f sp 0x7fffcbf6d6e0 T0)
==6373==The signal is caused by a READ memory access.
==6373==Hint: address points to the zero page.
    #0 0x5602093d3814 in luaV_execute /home/test/Lua/Test_lua/lua/lvm.c:1784
    #1 0x5602093ac605 in luaD_callnoyield /home/test/Lua/Test_lua/lua/ldo.c:525
    #2 0x5602093a2d34 in lua_callk /home/test/Lua/Test_lua/lua/lapi.c:987
    #3 0x5602093ee57d in generic_reader /home/test/Lua/Test_lua/lua/lbaselib.c:352
    #4 0x5602093dfc5e in luaZ_fill /home/test/Lua/Test_lua/lua/lzio.c:28
    #5 0x5602093a9a17 in f_parser /home/test/Lua/Test_lua/lua/ldo.c:789
    #6 0x5602093a9c51 in luaD_rawrunprotected /home/test/Lua/Test_lua/lua/ldo.c:148
    #7 0x5602093ad1e0 in luaD_pcall /home/test/Lua/Test_lua/lua/ldo.c:749
    #8 0x5602093ad598 in luaD_protectedparser /home/test/Lua/Test_lua/lua/ldo.c:813
    #9 0x5602093a341c in lua_load /home/test/Lua/Test_lua/lua/lapi.c:1061
    #10 0x5602093eed4d in luaB_load /home/test/Lua/Test_lua/lua/lbaselib.c:379
    #11 0x5602093ac25c in luaD_call /home/test/Lua/Test_lua/lua/ldo.c:481
    #12 0x5602093d4824 in luaV_execute /home/test/Lua/Test_lua/lua/lvm.c:1614
    #13 0x5602093d4824 in luaV_execute /home/test/Lua/Test_lua/lua/lvm.c:1614
    #14 0x5602093ac605 in luaD_callnoyield /home/test/Lua/Test_lua/lua/ldo.c:525
    #15 0x5602093a9c51 in luaD_rawrunprotected /home/test/Lua/Test_lua/lua/ldo.c:148
    #16 0x5602093ad1e0 in luaD_pcall /home/test/Lua/Test_lua/lua/ldo.c:749
    #17 0x5602093a2f8f in lua_pcallk /home/test/Lua/Test_lua/lua/lapi.c:1031
    #18 0x56020939c42a in docall /home/test/Lua/Test_lua/lua/lua.c:139
    #19 0x56020939d79d in handle_script /home/test/Lua/Test_lua/lua/lua.c:228
    #20 0x56020939d79d in pmain /home/test/Lua/Test_lua/lua/lua.c:603
    #21 0x5602093ac25c in luaD_call /home/test/Lua/Test_lua/lua/ldo.c:481
    #22 0x5602093ac605 in luaD_callnoyield /home/test/Lua/Test_lua/lua/ldo.c:525
    #23 0x5602093a9c51 in luaD_rawrunprotected /home/test/Lua/Test_lua/lua/ldo.c:148
    #24 0x5602093ad1e0 in luaD_pcall /home/test/Lua/Test_lua/lua/ldo.c:749
    #25 0x5602093a2f8f in lua_pcallk /home/test/Lua/Test_lua/lua/lapi.c:1031
    #26 0x56020939bbda in main /home/test/Lua/Test_lua/lua/lua.c:629
    #27 0x7f13ec0c709a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2409a)
    #28 0x56020939c239 in _start (/home/test/Lua/Test_lua/lua/lua+0x18239)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/test/Lua/Test_lua/lua/lvm.c:1784 in luaV_execute
==6373==ABORTING
```

###### Backtrace

```
$ gdb -q -args ./Test_lua/lua/lua ./crashes/Memory_corruption_luaV_execute.lua
Reading symbols from ./Test_lua/lua/lua...done.
(gdb) r
Starting program: /home/test/Lua/Test_lua/lua/lua ./crashes/Memory_corruption_luaV_execute.lua
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x00005555555a3815 in luaV_execute (L=L@entry=0x61b000000088, ci=<optimized out>) at lvm.c:1783
1783            Proto *p = cl->p->p[GETARG_Bx(i)];
(gdb) x/5i $rip
=> 0x5555555a3815 <luaV_execute+6709>:  mov    (%rdi),%r14
   0x5555555a3818 <luaV_execute+6712>:  shr    $0x3,%rax
   0x5555555a381c <luaV_execute+6716>:  cmpb   $0x0,0x7fff8000(%rax)
   0x5555555a3823 <luaV_execute+6723>:  jne    0x5555555aedfc <luaV_execute+53276>
   0x5555555a3829 <luaV_execute+6729>:  lea    0x8(%r11),%rdi
(gdb) p/x $rdi
$1 = 0x0
```
Reply | Threaded
Open this post in threaded view
|

Re: Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Viacheslav Usov
On Mon, Aug 31, 2020 at 5:25 AM secwx weizhenvul <[hidden email]> wrote:

>  v55 = string.dump(v19)
>  v56 = string.format(v55,v16,v6)
>  v58 = load(v56)

This dumps the byte code of v19,  then potentially mutilates the dump,
then tries to load the potentially malformed byte code. Which, per the
documentation, "can crash the interpreter".

Don't do that.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Luiz Henrique de Figueiredo
> >  v55 = string.dump(v19)
> >  v56 = string.format(v55,v16,v6)
> >  v58 = load(v56)
>
> This dumps the byte code of v19,  then potentially mutilates the dump,
> then tries to load the potentially malformed byte code. Which, per the
> documentation, "can crash the interpreter".

Loading malformed bytecode should not crash Lua.
Running maliciously crafted bytecode can crash the interpreter.
Reply | Threaded
Open this post in threaded view
|

Re: Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Viacheslav Usov
On Mon, Aug 31, 2020 at 12:28 PM Luiz Henrique de Figueiredo
<[hidden email]> wrote:

> Loading malformed bytecode should not crash Lua.
> Running maliciously crafted bytecode can crash the interpreter.

You are certainly free to make the bytecode loader more robust :)

That said, currently we see "Maliciously crafted binary chunks can
crash the interpreter" in the section on load(), and it is not obvious
at all that this does not apply to load() itself.

Not that the distinctions between "malformed" and "maliciously
crafted" and between "loading" and "running" would make a big
difference to Lua users.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Andrew Gierth
In reply to this post by Luiz Henrique de Figueiredo
>>>>> "Luiz" == Luiz Henrique de Figueiredo <[hidden email]> writes:

 >> >  v55 = string.dump(v19)
 >> >  v56 = string.format(v55,v16,v6)
 >> >  v58 = load(v56)
 >>
 >> This dumps the byte code of v19,  then potentially mutilates the dump,
 >> then tries to load the potentially malformed byte code. Which, per the
 >> documentation, "can crash the interpreter".

 Luiz> Loading malformed bytecode should not crash Lua.
 Luiz> Running maliciously crafted bytecode can crash the interpreter.

The code in question does in fact run the malformed bytecode:

 v55 = string.dump(v19)
 v56 = string.format(v55,v16,v6)
 v58 = load(v56)

 -- at this point, v58 is a function value with invalid bytecode;

 v59 = load(v58)

 -- load(x) where x is a function assumes that x is a reader function,
 -- to be called in order to obtain chunks of input. So v58 is called
 -- from inside load(), via generic_reader -> lua_callk -> luaV_execute
 -- which then crashes due to the malformed code.

Regardless, the answer is "don't mutilate the bytecode". Sandboxes that
need to prevent the user crashing the interpreter obviously have to
disable the binary load option (along with preventing access to the
debug library, etc.)

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

Re: Memory corruption in in luaV_execute (lua/Test_lua/lua/lvm.c:1784)

Philippe Verdy-2
I don't se the rationale: the bytecode loader should not crash. Otherwise it offers a way for hackers to corrupt the store used by the compiler to keep the binary code that was running and installed, in order to crash a server with running applications, and force it to experiment pseudo-random behavior that can be eventually studied, predicted and then forge an attack that will be successful.

For CVE, it would be a candidate as a sever bug to be listed and should be corrected. This may eventually make the byteloader a bit longer, but Lua apps don"t pass lot of time loading binary code. This is critical for servers even if they try to limit accesses to the bytecode store on the filesystem (but let's remember that the byteloader itself does not use a specific system user account when it will be used internally by internal APIs of the applications where many sessions and remote users run "their" code or session within the same system account (creating a system account for each of them to isolate them is very costly, it is assumed to be not needed if Lua is its own virtualized machine providing this isolation.

Comparatively, the Java VMs all perform validation tasks to load its bytecode. As well there's a good usage for transporting the bytecode over the network in order to delegate tasks that will run or will deployed on other hosts, and which is compiled just once, on a more powerful machine. These remote hosts may be very small machines with limited resources, just trusting the server over which they are connected and instructing them what to do and within a virtualized security realm parameterizd by the server (these remote hosts won't really know what they are running for the benefit of this server.

A Bytecode verifier in the bytecode loader is then an essential component for a solid Lua implementation.

Anwya I don't understand the interest of the sentence "Loading malformed bytecode should not crash Lua. Running maliciously crafted bytecode can crash the interpreter." First the loader should not crash, but if it does n,to crash and let the code run, running it to cause a crash is even more harmful and can make serious security damages. If to avoid the crash of the interpreter caused by malicious crafted code depends on the assumption that the byte code loader should have returned an error (without crashing), then it is the byteloader (not crashing) that must reject this invalid code and properly return the error expected by the bytecode runner.

The good rule for security is still "detect error early as much as you can, don't let then pass the way". This does not mean that downstream processing units don't need to perform other security checks, but most of the time these checks depends on the fact that some of their input was already validated at some level.

So any invalid/malformed bytecode should not be loadable at all. There's no way to justify it (even if the loader does not crash itself along with the whole VM running it, as effectively it should not); if this is not implemented, what can be done is to first try loading the bytecode and test it in an isolated temporary VM that can crash, isolated with no filesystem access, no network access and some limited memory quotas and CPU usage, and ignore the errors that may be returned such as permission denied or simple normal termination; detect this crash, declare the bytecode as not loadable in the normal VM.

I think that it should be possible to do that  on any Jua VM that runs on top of a system where it is easy to instanciante a new VM instance and even isolate it on a strict environemtn with no file access (or access to an empty directory and limted storage, possibly with a virtual filesystem residing only in memory). At least this should work on servers, this won't run on remote small devices with limited OS running everything in the same process and the same thread with a single core, but at least the server has the control and can check the byte code that it will submit to remotes (which then just have to check if the code was not damaged, e.g. by using string digital signatures on the transmitted bytecode they will receive to authenticate that it was effectively checked by the appropriate trusted source and not damaged in the middle of the transmission)


Le lun. 31 août 2020 à 16:15, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Luiz" == Luiz Henrique de Figueiredo <[hidden email]> writes:

 >> >  v55 = string.dump(v19)
 >> >  v56 = string.format(v55,v16,v6)
 >> >  v58 = load(v56)
 >>
 >> This dumps the byte code of v19,  then potentially mutilates the dump,
 >> then tries to load the potentially malformed byte code. Which, per the
 >> documentation, "can crash the interpreter".

 Luiz> Loading malformed bytecode should not crash Lua.
 Luiz> Running maliciously crafted bytecode can crash the interpreter.

The code in question does in fact run the malformed bytecode:

 v55 = string.dump(v19)
 v56 = string.format(v55,v16,v6)
 v58 = load(v56)

 -- at this point, v58 is a function value with invalid bytecode;

 v59 = load(v58)

 -- load(x) where x is a function assumes that x is a reader function,
 -- to be called in order to obtain chunks of input. So v58 is called
 -- from inside load(), via generic_reader -> lua_callk -> luaV_execute
 -- which then crashes due to the malformed code.

Regardless, the answer is "don't mutilate the bytecode". Sandboxes that
need to prevent the user crashing the interpreter obviously have to
disable the binary load option (along with preventing access to the
debug library, etc.)

--
Andrew.