Nonblocking script execution

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

Nonblocking script execution

Alexander Gladysh
Hi, list!

What is the current "state of Lua nation" on non-blocking script execution?

http://lua-users.org/wiki/NonBlockingLuaExecution

What is considered to be the best way to prevent untrusted Lua code
from hanging up the system?

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alexander Gladysh
> What is the current "state of Lua nation" on non-blocking script execution?

> http://lua-users.org/wiki/NonBlockingLuaExecution

> What is considered to be the best way to prevent untrusted Lua code
> from hanging up the system?

I would narrow my question.

Say, I set instruction count hook. If it is triggered (and some
condition is met -- given time passed, for example), I need to kill
Lua state. How do I kill it most gracefully? I'd like all allocated
resources to be freed. (I think it would be perfect to be able to
imitate conventional runtime error raised from the hook point. Not
sure if this is achievable though...)

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Jeff Pohlmeyer
On Tue, Feb 24, 2009 at 7:04 AM, Alexander Gladysh <[hidden email]> wrote:

> Say, I set instruction count hook. If it is triggered (and some
> condition is met -- given time passed, for example), I need to kill
> Lua state. How do I kill it most gracefully? I'd like all allocated
> resources to be freed. (I think it would be perfect to be able to
> imitate conventional runtime error raised from the hook point. Not
> sure if this is achievable though...)

I have an application that does this,
from inside the debug hook callback:

if (timeout) {
  lua_pushstring(L, "Script timeout.");
  lua_error(L);
}

and it *seems* to work fine, but I'm no expert...

 - Jeff

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Eike Decker
Remember that you can overload functions to be working the way you
want them to work. For example, you can change coroutine.create /
coroutine.yield and debug.sethook to allow you to yield within a
script properly. For example, the debug hook could call a certain
yield function that propagates the yield over all running coroutines.
A special resume function could then continue the execution in the
coroutine that was stopped by the debug hook. The debug.sethook
function could also be extended that it does not touch your "watchdog"
function but is called from the watchdog.

However, this means high costs - the debugger has a serious impact on
the performance.

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Torsten K.
In reply to this post by Alexander Gladysh
Hi!

> > What is the current "state of Lua nation" on non-blocking script
> > execution?
> >
> > http://lua-users.org/wiki/NonBlockingLuaExecution
> >
> > What is considered to be the best way to prevent untrusted Lua code
> > from hanging up the system?

I do it this way in an irc-bot:

void script_Watchdog(lua_State *L, lua_Debug *Dummy)
{
	char buffer[MSGLEN];
	struct aLuaScriptInfo *LSI;
	int i;
	const char *msg;
	struct aBot *backup;

	LSI = script_LuaFindByState(L);
	i = LSI->Watchdog_MaxInstructions;
	snprintf(buffer, sizeof(buffer), "LUA script watchdog alert (%i instructions 
reached)!", i);
	luaL_where(L, 0);
	lua_pushstring(L, buffer);
	lua_concat(L, 2);
	if (LSI->Watchdog_PartylineMsg)
	{
		msg = lua_tostring(L, -1);
		backup = current;
		current = LSI->context;
		send_statmsg("%s", nullstr((char*)msg));
		current = backup;
	}
	lua_error(L);	/* never returns */
}

the send_statmsg() gives a informative report about script name, line number 
and file name where the error (this time: the Watchdog alert) occured.

When calling a script function i do:

if (LSI->Watchdog_MaxInstructions)
lua_sethook(LSI->L,script_Watchdog,LUA_MASKCOUNT,LSI->Watchdog_MaxInstructions);
...
i = lua_pcall(LSI->L, pnum, 1, 0);
...

The watchdog event gets captured like any other error (well, i dont check the 
time but only the execution counter).

ciao,

  Torsten


Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alexander Gladysh
In reply to this post by Jeff Pohlmeyer
On Tue, Feb 24, 2009 at 6:08 PM, Jeff Pohlmeyer
<[hidden email]> wrote:
> On Tue, Feb 24, 2009 at 7:04 AM, Alexander Gladysh <[hidden email]> wrote:

>> Say, I set instruction count hook. If it is triggered (and some
>> condition is met -- given time passed, for example), I need to kill
>> Lua state. How do I kill it most gracefully? I'd like all allocated
>> resources to be freed. (I think it would be perfect to be able to
>> imitate conventional runtime error raised from the hook point. Not
>> sure if this is achievable though...)

> I have an application that does this,
> from inside the debug hook callback:

> if (timeout) {
>  lua_pushstring(L, "Script timeout.");
>  lua_error(L);
> }

> and it *seems* to work fine, but I'm no expert...

I see this is a popular solution. Thank you everyone who answered my question!

Can any Lua internals expert confirm that this is safe? Can
instruction count debug hook get called in the middle of something
sensitive?

I would settle for positive answer covering "current implementation",
but it would be great to have a legal  way to terminate Lua state (or
trigger Lua error) from the hook.

Alexander.


Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

eugeny gladkih
>>>>> "AG" == Alexander Gladysh <[hidden email]> writes:

 >>> Say, I set instruction count hook. If it is triggered (and some
 >>> condition is met -- given time passed, for example), I need to kill
 >>> Lua state. How do I kill it most gracefully? I'd like all allocated
 >>> resources to be freed. (I think it would be perfect to be able to
 >>> imitate conventional runtime error raised from the hook point. Not
 >>> sure if this is achievable though...)

 >> I have an application that does this,
 >> from inside the debug hook callback:

 >> if (timeout) {
 >>  lua_pushstring(L, "Script timeout.");
 >>  lua_error(L);
 >> }

 >> and it *seems* to work fine, but I'm no expert...

 AG> I see this is a popular solution. Thank you everyone who answered my question!

 AG> Can any Lua internals expert confirm that this is safe? Can
 AG> instruction count debug hook get called in the middle of something
 AG> sensitive?

another intresting question is how much will it affect the performance?

-- 
Yours sincerely, Eugeny.
Doctor Web, Ltd. http://www.drweb.com
+79119997425


Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

hoelzro
I did a quick, nonreliable test, and it took about 14 times longer with a C hook. Also, another problem I thought of: what if the script makes an IO call?It'll still block...

-Rob

On Feb 24, 2009, at 14:54, eugeny gladkih <[hidden email]> wrote:

"AG" == Alexander Gladysh <[hidden email]> writes:

Say, I set instruction count hook. If it is triggered (and some
condition is met -- given time passed, for example), I need to kill
Lua state. How do I kill it most gracefully? I'd like all allocated
resources to be freed. (I think it would be perfect to be able to
imitate conventional runtime error raised from the hook point. Not
sure if this is achievable though...)

I have an application that does this,
from inside the debug hook callback:

if (timeout) {
 lua_pushstring(L, "Script timeout.");
 lua_error(L);
}

and it *seems* to work fine, but I'm no expert...

AG> I see this is a popular solution. Thank you everyone who answered my question!

AG> Can any Lua internals expert confirm that this is safe? Can
AG> instruction count debug hook get called in the middle of something
AG> sensitive?

another intresting question is how much will it affect the performance?

--
Yours sincerely, Eugeny.
Doctor Web, Ltd. http://www.drweb.com
+79119997425

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alexander Gladysh
On Wed, Feb 25, 2009 at 12:20 AM, Rob Hoelz <[hidden email]> wrote:
> I did a quick, nonreliable test, and it took about 14 times longer with a C
> hook.

Please share your test. My own quick unreliable test (attached) shows
slowdown closer to 1.05x, depending on instruction count, starting
from 100 instructions per hook. Maximum slowdown is 2.37 if hook is
called on every first instruction.

Note that in normal scenario instruction count in the hook is to be
greater than total count of instructions in the script.

Not setting hook
0m11.785s

1 instruction, hook called 700000008 times
0m27.900s

10  instructions, hook called 70000000 times
0m14.323s

100 instructions, hook called 7000000 times
0m12.328s

1000 instructions, hook called 700000 times
0m12.131s

10000 instructions, hook called 70000 times
0m12.181s

100000 instructions, hook called 7000 times
0m12.063s

1000000 instructions, hook called 700 times
0m12.273s

10000000 instructions, hook called 70 times
real	0m12.134s

100000000 instructions, hook called 7 times
0m12.282s

1000000000 instructions, hook called 0 times
0m12.213s

> Also, another problem I thought of: what if the script makes an IO
> call?It'll still block...

Just do not expose blocking IO to untrusted script.

Alexander.
#include <stdio.h>
#include <stdlib.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int g_counter = 0;

void handler(lua_State * L, lua_Debug * D)
{
  ++g_counter;
}

int main(int argc, char ** argv)
{
  int count = 0;
  int result = 0;
  lua_State * L = lua_open();
  luaL_openlibs(L);
  
  if (argc >= 2)
  {
    count = atoi(argv[1]);
  }
  
  if (count <= 0)
  {
    printf("not setting hook\n");
  }
  else
  {
    printf("setting hook on %d instructions\n", count);
    lua_sethook(L, handler, LUA_MASKCOUNT, count);
  }
  
  printf("running, please wait...\n");
  if (
      luaL_dostring(
          L, 
          "local j = 0\n"
          "local f = function() j = j + 1 end\n"
          "for i = 1, 1e8 do f() end\n"
        ) != 0
    )
  {
    printf("Lua error: %s\n", lua_tostring(L, -1));
    result = 1;
  }
  else
  {
    printf("done, hook called %d times\n", g_counter);
  }
  
  lua_close(L);

  return result;
}
Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alex Davies
In reply to this post by hoelzro
I did a quick, nonreliable test, and it took about 14 times longer with a C hook. Also, another problem I thought of: what if the script makes an IO call?It'll still block...

It is a problem - your whole C library needs to either have a guaranteed worse case time (even Lua's standard library doesn't offer this, string/io/table libraries can take a long time performing their various functions) or check the debug hooks themselves.

- Alex

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alexander Gladysh
On Wed, Feb 25, 2009 at 2:39 AM, Alex Davies <[hidden email]> wrote:
>> I did a quick, nonreliable test, and it took about 14 times longer  with a
>> C hook.  Also, another problem I thought of: what if the script  makes an IO
>> call?It'll still block...
>
> It is a problem - your whole C library needs to either have a guaranteed
> worse case time (even Lua's standard library doesn't offer this,
> string/io/table libraries can take a long time performing their various
> functions) or check the debug hooks themselves.

Yes, indeed.

("xyzzy"):rep(1e7):gsub("(z+)", "Z")

This one takes 4 seconds on my box, and has just 9 instructions.

While one can take away IO from untrusted code, string library is
usually a requirement...

Alexander.


Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

David Given
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Alexander Gladysh wrote:
[...]
> ("xyzzy"):rep(1e7):gsub("(z+)", "Z")
> 
> This one takes 4 seconds on my box, and has just 9 instructions.
> 
> While one can take away IO from untrusted code, string library is
> usually a requirement...

Of course, this *does* use an unreasonable amount of memory, which in
such an environment is probably going to managed (apart from anything
else, Lua makes this very easy).

It's an important point, though. I wonder what the *most* malicious code
it's possible to write in a instruction-and-memory-limited Lua VM,
assuming no IO, of course? Could you, for example, persuade gsub to
continuously insert an empty string, or something similar?

- --
ââââ ïïïïïïïïïïïïïï âââââ http://www.cowlark.com âââââ
â
â "People who think they know everything really annoy those of us who
â know we don't." --- Bjarne Stroustrup
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFJpI/2f9E0noFvlzgRArGMAKDe8VmLplNvM39pwwd56rhvdowOVgCfQhmg
876+UbAQX37aFM6g9hIjdMM=
=rPZ3
-----END PGP SIGNATURE-----


Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Asko Kauppi
In reply to this post by Alexander Gladysh

Alexander Gladysh kirjoitti 25.2.2009 kello 1:59:

On Wed, Feb 25, 2009 at 2:39 AM, Alex Davies <[hidden email]> wrote:
I did a quick, nonreliable test, and it took about 14 times longer with a C hook. Also, another problem I thought of: what if the script makes an IO
call?It'll still block...

It is a problem - your whole C library needs to either have a guaranteed
worse case time (even Lua's standard library doesn't offer this,
string/io/table libraries can take a long time performing their various
functions) or check the debug hooks themselves.

Yes, indeed.

("xyzzy"):rep(1e7):gsub("(z+)", "Z")

This one takes 4 seconds on my box, and has just 9 instructions.

While one can take away IO from untrusted code, string library is
usually a requirement...

Alexander.

Quite smart to think of this kind of issues; I've never realized them.

The only solution I'm aware of that currently offers cancellation of Lua states from the outside is Lanes. It will _really_ kill runaway cases, from the OS threading level, and then close their Lua states.

-asko



Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Asko Kauppi
In reply to this post by David Given

Maybe there should be yet another kind of hook, which string and gsub libraries etc. would trigger from their internal loops.

Or maybe the execution time limit should be completely detached from the debug API.

It's fairly easy to make a portable "this function gives the execution time" setting for Lua compilation. But what and when should be done about that is another issue. The same mechanism could actually be used as a "performance counter" of sorts in Lua.

-asko


David Given kirjoitti 25.2.2009 kello 2:25:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Alexander Gladysh wrote:
[...]
("xyzzy"):rep(1e7):gsub("(z+)", "Z")

This one takes 4 seconds on my box, and has just 9 instructions.

While one can take away IO from untrusted code, string library is
usually a requirement...

Of course, this *does* use an unreasonable amount of memory, which in
such an environment is probably going to managed (apart from anything
else, Lua makes this very easy).

It's an important point, though. I wonder what the *most* malicious code
it's possible to write in a instruction-and-memory-limited Lua VM,
assuming no IO, of course? Could you, for example, persuade gsub to
continuously insert an empty string, or something similar?

- --
ââââ ïïïïïïïïïïïïïï âââââ http://www.cowlark.com âââââ
â
â "People who think they know everything really annoy those of us who
â know we don't." --- Bjarne Stroustrup
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFJpI/2f9E0noFvlzgRArGMAKDe8VmLplNvM39pwwd56rhvdowOVgCfQhmg
876+UbAQX37aFM6g9hIjdMM=
=rPZ3
-----END PGP SIGNATURE-----



Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Alexander Gladysh
On Wed, Feb 25, 2009 at 11:13 AM, Asko Kauppi <[hidden email]> wrote:
> Maybe there should be yet another kind of hook, which string and gsub
> libraries etc. would trigger from their internal loops.

But you always can supply your own string.gsub after all...

> Or maybe the execution time limit should be completely detached from the
> debug API.

> It's fairly easy to make a portable "this function gives the execution time"
> setting for Lua compilation. But what and when should be done about that is
> another issue. The same mechanism could actually be used as a "performance
> counter" of sorts in Lua.

I think tracking *time* properly and portably would be too hard task
for Lua (also it should be a considerable performance hit).

...OTOH, it may make sense to have centralized abstract "operation
counter" with C API to increment it and to set hook on it a-la
instruction counter. Standard Lua libraries would increment them at
will. Instruction counter would be incremented in parallel with it.
Any sandbox-friendly 3rd party modules would increment the counter as
well on long operation loops. It would allow to register long
iterative operations for a sandbox in a centralized way.

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Nonblocking script execution

Patrick Donnelly
On Wed, Feb 25, 2009 at 3:52 PM, Alexander Gladysh <[hidden email]> wrote:
> ...OTOH, it may make sense to have centralized abstract "operation
> counter" with C API to increment it and to set hook on it a-la
> instruction counter. Standard Lua libraries would increment them at
> will. Instruction counter would be incremented in parallel with it.
> Any sandbox-friendly 3rd party modules would increment the counter as
> well on long operation loops. It would allow to register long
> iterative operations for a sandbox in a centralized way.

I've also thought it would be useful to have a sort of generic C API
call to act as an instruction count increment which would decrement
L->hookcount and possibly run the debug hook in the same way executing
the next bytecode instruction does.

-- 
-Patrick Donnelly

"One of the lessons of history is that nothing is often a good thing
to do and always a clever thing to say."

Reply | Threaded
Open this post in threaded view
|

Coroutines and lua_close()

kathrin_69@gmx.de
In reply to this post by Asko Kauppi
Hi,

if I've this code:

lua_State* pParent = luaL_newstate();
lua_State* pChild = lua_newthread(parent);
lua_close(pChild);

It seems like the last command also destroys the parent VM (all access to it after the call to lua_close results in access to non allocated memory).

So given the code above: Is there any difference between calling lua_close() on pChild or on pParent? I know that I've not to call lua_close(pChild); to cleanup anything, because coroutines are subject to GC. I'm just asking to get better understanding of inner mechanics.

Thank you and regards
joerg



Reply | Threaded
Open this post in threaded view
|

Re: Coroutines and lua_close()

Robert G. Jakabosky
On Tuesday 17, [hidden email] wrote:
> So given the code above: Is there any difference between calling
> lua_close() on pChild or on pParent?
> I know that I've not to call lua_close(pChild); to cleanup anything,
> because coroutines are subject to GC. I'm just asking to get better
> understanding of inner mechanics.

No, calling lua_close() on either the child or the parent thread will free the 
mainthread and all child threads.

First line from lua_close():
L = G(L)->mainthread;  /* only the main thread can be closed */

So it always switches to the mainthread.

-- 
Robert G. Jakabosky