luasocket sandbox

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

luasocket sandbox

Leo Leo
Hi,

I want to write a software that execute lua networking code. My goal was to
sandbox Luasocket to put restrictions on what hosts are allowed or not,
maximum amout of bandwith, number of sockets opened, ... Initially, I thinked
that I could easily do that in pure lua using closure. But after a
promising start, wrapping socket objects into an array, I discover a major
problem, the socket.select() function. This function cannot take my array
and need 'pure' socket to use them. So, now, I have implemented that the
listen() and connect() method return a pure socket rather than an array.
That break the Luasocket protocol and permit only to put restriction on
hosts but no other ones like bandwith management.

Do you think there is a good (and secure) solution in Lua or I must do some
change in C ? If I must change C, where and how do you advise me to change
to be as clean as possible ? Some of you as already a socket sandbox ready
? :-)

Thank you, reguards.

Leo

PS: Here is my socket sandbox code:

do
        local s_ori = socket

        socket = {}

        -- socket.tcp() is originally a 'master' object, transformed in 'server'
        -- object by a call to listen() or transformed in a 'client' object by a call
        -- to connect(). At this point select() need an array of 'client' or
        -- 'server' objects. So after a call to listen() or connect(), we must
        -- return a new object because we can't change our object wrapper (array) in
        -- the final object. This way break lightly the luasocket syntax. Anyway,
        -- this solution permit only host filtering and not socket and bandwith
        -- limitations. One more problem is that you can normally use a 'master'
        -- object in select but not in this solution, because 'master' object is
        -- always wrapped in a table.

        socket.tcp = function()
                local stcp = s_ori.tcp()
                local sock = {}

                -- We must break the protocol here, because socket.select() need pure
                -- socket{client|master} objects and no tables.
                sock.connect = function(self, host, ...)
                        if host ~= "this.denied.host" then
                                local s, msg = stcp.connect(stcp, host, unpack(arg))
                                if s ~= nil then
                                        return stcp
                                else
                                        return s, msg
                                end
                        else
                                return nil, "sandbox"
                        end
                end

                -- We must break the protocol here, because socket.select() need pure
                -- socket{client|master} objects and no tables.
                sock.listen = function(self, ...)
                        local s = stcp.listen(stcp, unpack(arg))
                        if s == nil then
                                return nil
                        else
                                return stcp
                        end
                end
                sock.bind = function(self, ...)
                        return stcp.bind(stcp, unpack(arg))
                end

                sock.close = function(self, ...)
                        return stcp.close(stcp, unpack(arg))
                end
                sock.getsockname = function(self, ...)
                        return stcp.getsockname(stcp, unpack(arg))
                end
                sock.getstats = function(self, ...)
                        return stcp.getstats(stcp, unpack(arg))
                end
                sock.setstats = function(self, ...)
                        return stcp.setstats(stcp, unpack(arg))
                end
                sock.settimeout = function(self, ...)
                        return stcp.settimeout(stcp, unpack(arg))
                end

                -- metatable (didn't work ..., not important implemented above)
--[[
                local sock_meta = {}
                sock_meta['__index'] = function (table, func)
                        return stcp[func]
                end
                sock_meta['__newindex'] = function (table, func) end
                setmetatable(sock, sock_meta)
]]--

                return sock
        end

        socket.udp = function()
                local sudp = s_ori.udp()
                local sock = {}
                sock.close = function(self, ...)
                        return sudp.close(sudp, unpack(arg))
                end
                sock.getpeername = function(self, ...)
                        return sudp.getpeername(sudp, unpack(arg))
                end
                sock.getsockname = function(self, ...)
                        return sudp.getsockname(sudp, unpack(arg))
                end
                sock.receive = function(self, ...)
                        return sudp.receive(sudp, unpack(arg))
                end
                sock.receivefrom = function(self, ...)
                        return sudp.receivefrom(sudp, unpack(arg))
                end
                sock.send = function(self, ...)
                        return sudp.send(sudp, unpack(arg))
                end
                sock.sendto = function(self, ...)
                        return sudp.sendto(sudp, unpack(arg))
                end
                sock.setpeername = function(self, ...)
                        return sudp.setpeername(sudp, unpack(arg))
                end
                sock.setsockname = function(self, ...)
                        return sudp.setsockname(sudp, unpack(arg))
                end
                sock.setoption = function(self, ...)
                        return sudp.setoption(sudp, unpack(arg))
                end
                sock.settimeout = function(self, ...)
                        return sudp.settimeout(sudp, unpack(arg))
                end

                return sock
        end

        socket.bind = function(...)
                return s_ori.bind(unpack(arg))
        end
        socket.connect = function(...)
                return s_ori.connect(unpack(arg))
        end
        socket._DEBUG = s_ori._DEBUG
        socket.newtry = function(...)
                return s_ori.newtry(unpack(arg))
        end
        socket.protect = function(...)
                return s_ori.protect(unpack(arg))
        end
        socket.select = function(...)
                return s_ori.select(unpack(arg))
        end
        socket.sink = function(...)
                return s_ori.sink(unpack(arg))
        end
        socket.skip = function(...)
                return s_ori.skip(unpack(arg))
        end
        socket.sleep = function(...)
                return s_ori.sleep(unpack(arg))
        end
        socket.source = function(...)
                return s_ori.source(unpack(arg))
        end
        socket.gettime = function(...)
                return s_ori.gettime(unpack(arg))
        end
        socket.try = function(...)
                return s_ori.try(unpack(arg))
        end
        socket._VERSION = s_ori._VERSION

        socket.dns = {}

        socket.dns.gethostname = function(...)
                return s_ori.dns.gethostname(unpack(arg))
        end
        socket.dns.tohostname = function(...)
                return s_ori.dns.tohostname(unpack(arg))
        end
        socket.dns.toip = function(...)
                return s_ori.dns.toip(unpack(arg))
        end
end

Reply | Threaded
Open this post in threaded view
|

Re: luasocket sandbox

Leo Leo
In fact I have solved my problem (in Lua). I store the tcp socket in an upvalue table with his index only stored in the sandbox table for the socket. Then I have redefined socket.select() to take the wrapper index, take the real socket in the upvalue, call select() and re-wrapping the results in the sandbox. That way, my tcp socket are always in their sandbox table and the Lua socket protocol don't need to be changed.


2006/2/7, Leo Leo <[hidden email]>:
Hi,

I want to write a software that execute lua networking code. My goal was to
sandbox Luasocket to put restrictions on what hosts are allowed or not,
maximum amout of bandwith, number of sockets opened, ... Initially, I thinked
that I could easily do that in pure lua using closure. But after a
promising start, wrapping socket objects into an array, I discover a major
problem, the socket.select() function. This function cannot take my array
and need 'pure' socket to use them. So, now, I have implemented that the
listen() and connect() method return a pure socket rather than an array.
That break the Luasocket protocol and permit only to put restriction on
hosts but no other ones like bandwith management.

Do you think there is a good (and secure) solution in Lua or I must do some
change in C ? If I must change C, where and how do you advise me to change
to be as clean as possible ? Some of you as already a socket sandbox ready
? :-)

Thank you, reguards.

Leo

PS: Here is my socket sandbox code:

do
        local s_ori = socket

        socket = {}

        -- socket.tcp() is originally a 'master' object, transformed in 'server'
        -- object by a call to listen() or transformed in a 'client' object by a call
        -- to connect(). At this point select() need an array of 'client' or
        -- 'server' objects. So after a call to listen() or connect(), we must
        -- return a new object because we can't change our object wrapper (array) in
        -- the final object. This way break lightly the luasocket syntax. Anyway,
        -- this solution permit only host filtering and not socket and bandwith
        -- limitations. One more problem is that you can normally use a 'master'
        -- object in select but not in this solution, because 'master' object is
        -- always wrapped in a table.

        socket.tcp = function()
                local stcp = s_ori.tcp()
                local sock = {}

                -- We must break the protocol here, because socket.select() need pure
                -- socket{client|master} objects and no tables.
                sock.connect = function(self, host, ...)
                        if host ~= "this.denied.host" then
                                local s, msg = stcp.connect(stcp, host, unpack(arg))
                                if s ~= nil then
                                        return stcp
                                else
                                        return s, msg
                                end
                        else
                                return nil, "sandbox"
                        end
                end

                -- We must break the protocol here, because socket.select() need pure
                -- socket{client|master} objects and no tables.
                sock.listen = function(self, ...)
                        local s = stcp.listen(stcp, unpack(arg))
                        if s == nil then
                                return nil
                        else
                                return stcp
                        end
                end
                sock.bind = function(self, ...)
                        return stcp.bind(stcp, unpack(arg))
                end

                sock.close = function(self, ...)
                        return stcp.close(stcp, unpack(arg))
                end
                sock.getsockname = function(self, ...)
                        return stcp.getsockname(stcp, unpack(arg))
                end
                sock.getstats = function(self, ...)
                        return stcp.getstats(stcp, unpack(arg))
                end
                sock.setstats = function(self, ...)
                        return stcp.setstats(stcp, unpack(arg))
                end
                sock.settimeout = function(self, ...)
                        return stcp.settimeout(stcp, unpack(arg))
                end

                -- metatable (didn't work ..., not important implemented above)
--[[
                local sock_meta = {}
                sock_meta['__index'] = function (table, func)
                        return stcp[func]
                end
                sock_meta['__newindex'] = function (table, func) end
                setmetatable(sock, sock_meta)
]]--

                return sock
        end

        socket.udp = function()
                local sudp = s_ori.udp()
                local sock = {}
                sock.close = function(self, ...)
                        return sudp.close(sudp, unpack(arg))
                end
                sock.getpeername = function(self, ...)
                        return sudp.getpeername(sudp, unpack(arg))
                end
                sock.getsockname = function(self, ...)
                        return sudp.getsockname(sudp, unpack(arg))
                end
                sock.receive = function(self, ...)
                        return sudp.receive(sudp, unpack(arg))
                end
                sock.receivefrom = function(self, ...)
                        return sudp.receivefrom(sudp, unpack(arg))
                end
                sock.send = function(self, ...)
                        return sudp.send(sudp, unpack(arg))
                end
                sock.sendto = function(self, ...)
                        return sudp.sendto(sudp, unpack(arg))
                end
                sock.setpeername = function(self, ...)
                        return sudp.setpeername(sudp, unpack(arg))
                end
                sock.setsockname = function(self, ...)
                        return sudp.setsockname(sudp, unpack(arg))
                end
                sock.setoption = function(self, ...)
                        return sudp.setoption(sudp, unpack(arg))
                end
                sock.settimeout = function(self, ...)
                        return sudp.settimeout(sudp, unpack(arg))
                end

                return sock
        end

        socket.bind = function(...)
                return s_ori.bind(unpack(arg))
        end
        socket.connect = function(...)
                return s_ori.connect(unpack(arg))
        end
        socket._DEBUG = s_ori._DEBUG
        socket.newtry = function(...)
                return s_ori.newtry(unpack(arg))
        end
        socket.protect = function(...)
                return s_ori.protect(unpack(arg))
        end
        socket.select = function(...)
                return s_ori.select(unpack(arg))
        end
        socket.sink = function(...)
                return s_ori.sink(unpack(arg))
        end
        socket.skip = function(...)
                return s_ori.skip(unpack(arg))
        end
        socket.sleep = function(...)
                return s_ori.sleep(unpack(arg))
        end
        socket.source = function(...)
                return s_ori.source(unpack(arg))
        end
        socket.gettime = function(...)
                return s_ori.gettime(unpack(arg))
        end
        socket.try = function(...)
                return s_ori.try(unpack(arg))
        end
        socket._VERSION = s_ori._VERSION

        socket.dns = {}

        socket.dns.gethostname = function(...)
                return s_ori.dns.gethostname(unpack(arg))
        end
        socket.dns.tohostname = function(...)
                return s_ori.dns.tohostname(unpack(arg))
        end
        socket.dns.toip = function(...)
                return s_ori.dns.toip(unpack(arg))
        end
end


Reply | Threaded
Open this post in threaded view
|

Re: luasocket sandbox

Diego Nehab-3
Hi,

This is not documented, but you can pass anything that has a getfd()
method and an empty() method to socket.select. You CAN wrap your socket
object inside a table.

[]s,
Diego.
Reply | Threaded
Open this post in threaded view
|

Re: luasocket sandbox

Leo Leo
Thanks, that will simplify my code.

Leo

2006/2/8, Diego Nehab <[hidden email]>:
Hi,

This is not documented, but you can pass anything that has a getfd()
method and an empty() method to socket.select. You CAN wrap your socket
object inside a table.

[]s,
Diego.

Reply | Threaded
Open this post in threaded view
|

Re: luasocket sandbox

Adrian Sietsma
In reply to this post by Diego Nehab-3
Diego Nehab wrote:
> Hi,
>
> This is not documented, but you can pass anything that has a getfd()
> method and an empty() method to socket.select. You CAN wrap your socket
> object inside a table.
>
> []s,
> Diego.
>

empty() or dirty() ?

ps now i've seen that, i'm off to tidy a lot of code !

Adrian
Reply | Threaded
Open this post in threaded view
|

Re: luasocket sandbox

Diego Nehab-3
Hi,

It's dirty(). :)

[]s,
Diego.