Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion security/lua/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,61 @@ Inode (`inode`):
Dentry (`dentry`):
- `dentry:path()` -> path string

For a full list, see the method tables in `lua_kernel.c` and `lua_fs.c`.
Sock (`sock`):
- `sock:suites()` -> family, type, protocol strings
- `sock:proto()` -> raw `sk_protocol` number, needed for non-IP protocol
namespaces such as netlink's `NETLINK_GENERIC`

Skb (`skb`):
- `skb:sock()` -> owning sock, or `nil`. Always a full sock: a half-open
(`SYN_RECV`) connection resolves to its listening sock and a time-wait sock
yields `nil`, because those mini-sock forms lack the fields the `sock`
accessors read
- `skb:len()` -> length in bytes of the skb's data window
- `skb:read(off, len)` -> `len` bytes of the data window as a string
(`len` <= 256), or `nil` when `off` or `off + len` falls outside it

`skb:read()` returns raw bytes; decode multi-byte fields in Lua (e.g. with
`string.byte`), since the in-kernel Lua has no bit library. Reads are
bounds-checked and return `nil` instead of raising, so the policy decides the
verdict. Note that a Lua error inside a hook falls back to that hook's default
return value, which is "allow" for most hooks; wrap parsing in `pcall()` when
the policy must fail closed.

## The skb data window

`skb:len()` and `skb:read()` share one base, the skb's data window: the
`skb->len` bytes starting at `skb->data`. Offset 0 of `skb:read()` is the first
byte of that window, and a read reaching into the skb's paged fragments is
stitched together transparently.

That first byte is not the start of the packet. Each protocol layer consumes its
header by advancing `skb->data` past it, so where the window begins is a
property of the hook rather than of the packet: by the time any socket-level
hook runs on the IPv4 receive path the network header is already consumed, and
SCTP has consumed both its chunk header and the INIT header. An offset is
therefore only meaningful for the one hook it was computed for, and getting it
wrong is quiet: an out-of-range read yields `nil`, an in-range one yields
whatever field actually sits there, so a policy built on the wrong base can look
like it works.

Bases verified against this tree:

| hook | first byte of the window | hook called from |
| --- | --- | --- |
| `netlink_send` | the netlink message header (`struct nlmsghdr`) | `netlink_sendmsg()` |
| `socket_sock_rcv_skb` | the TCP or UDP header | `tcp_filter()`, `udp_queue_rcv_one_skb()` |
| `inet_conn_request` | the TCP header of the SYN | `tcp_v4_route_req()`, `cookie_v4_check()` |
| `inet_conn_established` | the TCP header of the SYN-ACK | `tcp_finish_connect()` |
| `socket_getpeersec_dgram` | the UDP header | `ip_cmsg_recv_security()` |
| `sctp_assoc_request` | the INIT chunk's parameters, for the INIT state | `sctp_sf_do_5_1B_init()` |
| `xfrm_decode_session` | not established; verify before relying on it | `__xfrm_decode_session()` |

Two further details about the hooks above:
- Under `netlink_send` the window spans everything one `sendmsg()` wrote, so a
batch arrives as consecutive length-delimited messages, not one message.
- `socket_sock_rcv_skb` runs before the socket filter can trim the skb, so the
window is the untrimmed length.

For a full list, see the method tables in `lua_kernel.c`, `lua_fs.c`, and
`lua_net.c`.
64 changes: 54 additions & 10 deletions security/lua/lua_net.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "debug.h"
#include <linux/printk.h>
#include <linux/security.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <net/inet_sock.h>
#include <uapi/linux/in.h>
Expand All @@ -21,6 +22,9 @@
#include "kvcache.h"
#include "lua_object.h"

/* Upper bound of a single skb:read(), sized to stay on the kernel stack. */
#define SKB_READ_MAX 256

static const char *family_tostring(sa_family_t sa_family)
{
const char *family = NULL;
Expand Down Expand Up @@ -206,9 +210,18 @@ SOCK_BOOL_DEF(udp)
SOCK_BOOL_DEF(stream_unix)
SOCK_BOOL_DEF(vsock)

static int net_sock_proto(lua_State *L)
{
struct sock *sk = tosock(L, 1);

lua_pushinteger(L, (lua_Integer)sk->sk_protocol);
return 1;
}

static const luaL_Reg sock_meth[] = {
{ "socket", net_sock_socket },
{ "suites", net_sock_suites },
{ "proto", net_sock_proto },
{ "listener", net_sock_listener },
{ "is_inet", net_sock_is_inet },
{ "is_tcp", net_sock_is_tcp },
Expand Down Expand Up @@ -256,16 +269,11 @@ static const luaL_Reg socket_meth[] = {

/********************************** sk_buff *********************************/

/*
* A request or time-wait sk lacks the fields the sock accessors read, and the
* garbage passes for a valid protocol number, so the policy fails open.
*/
static int net_skb_sock(lua_State *L)
{
struct sk_buff *skb = toskb(L, 1);
struct sock *sk = skb->sk;

sk ? *newsock(L) = sk : lua_pushnil(L);
return 1;
}

static int net_skb_full_sk(lua_State *L)
{
struct sk_buff *skb = toskb(L, 1);
struct sock *sk = skb_to_full_sk(skb);
Expand Down Expand Up @@ -316,12 +324,48 @@ static int net_skb_secmark(lua_State *L)
return 1;
}

static int net_skb_read_bounded(struct sk_buff *skb, lua_Integer offset,
void *dst, unsigned int size)
{
if (offset < 0 || offset > (lua_Integer)skb->len)
return -EINVAL;
if (size > skb->len - (u32)offset)
return -EINVAL;
if (skb_copy_bits(skb, (int)offset, dst, (int)size))
return -EFAULT;
return 0;
}

static int net_skb_len(lua_State *L)
{
struct sk_buff *skb = toskb(L, 1);

lua_pushinteger(L, (lua_Integer)skb->len);
return 1;
}

static int net_skb_read(lua_State *L)
{
struct sk_buff *skb = toskb(L, 1);
lua_Integer offset = luaL_checkinteger(L, 2);
lua_Integer length = luaL_checkinteger(L, 3);
char buffer[SKB_READ_MAX];

if (length < 0 || length > SKB_READ_MAX ||
net_skb_read_bounded(skb, offset, buffer, (unsigned int)length))
lua_pushnil(L);
else
lua_pushlstring(L, buffer, (size_t)length);
return 1;
}

static const luaL_Reg skb_meth[] = {
{ "sock", net_skb_sock },
{ "full_sk", net_skb_full_sk },
{ "protocol", net_skb_protocol },
{ "iif", net_skb_iif },
{ "secmark", net_skb_secmark },
{ "len", net_skb_len },
{ "read", net_skb_read },
{ NULL, NULL }
};

Expand Down