aboutgitcodelistschat:MatrixIRC
path: root/cooker/match.c
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2024-08-13 18:50:33 +0200
committerStefano Brivio <sbrivio@redhat.com>2024-08-13 19:00:35 +0200
commit9bf3b1cc7a94357c250f77f16829c96cbae801fe (patch)
tree56cbc184974b18d33aa288dda7b12e5a77c38a94 /cooker/match.c
parentd699dac08778c597eefac1067a325059925e87e6 (diff)
downloadseitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.gz
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.bz2
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.lz
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.xz
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.zst
seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.zip
call, emit, match: Add support for vectorised operations, nfnetlinkHEADmaster
We want to add and delete rules with iptables(8), and manipulate set elements with nft(8). These are the first users we encounter sending multiple netlink messages in one sendmsg(). To support matching on those, we need to iterate over several messages, looking for a matching one, or a mismatching one (depending on quantifiers and match type), but we don't want to implement program loops because of security design reasons. We can't implement a generalised instruction that vectorises existing ones, either, because we need to support universal and existential quantifiers in fields that are repeated multiple times, once per each netlink message, with bitwise operations and non-exact matching types. Add vectorisation support to OP_CMP and OP_BITWISE instead, with a generic description for a vector (only sequences of netlink messages with length in nlmsghdr are supported at the moment) so that, depending on the quantifiers, we'll repeat those operations as many times as needed. This way, we don't risk any O(n^2) explosion, and we are bound by O(m * n) instead, with m compare/bitwise operations for a given expression, and n number of netlink messages. Add demos for nft and iptables using the new concepts. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Diffstat (limited to 'cooker/match.c')
-rw-r--r--cooker/match.c169
1 files changed, 145 insertions, 24 deletions
diff --git a/cooker/match.c b/cooker/match.c
index 1fd726f..f65ac5c 100644
--- a/cooker/match.c
+++ b/cooker/match.c
@@ -22,9 +22,70 @@
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
static struct num netlink_types[] = {
- { "newroute", RTM_NEWROUTE },
+ { "newroute", RTM_NEWROUTE },
+
+#define SUBSYS_NFCOMPAT(x) (NFNL_SUBSYS_NFT_COMPAT << 8 | (x))
+ { "nf_compat_get", (SUBSYS_NFCOMPAT(NFNL_MSG_COMPAT_GET))},
+
+#define SUBSYS_NFT(x) (NFNL_SUBSYS_NFTABLES << 8 | (x))
+ { "nf_get_any",
+ SUBSYS_NFT(NFT_MSG_GETTABLE | NFT_MSG_GETCHAIN | NFT_MSG_GETRULE |
+ NFT_MSG_GETSET | NFT_MSG_GETSETELEM | NFT_MSG_GETGEN |
+ NFT_MSG_GETFLOWTABLE | NFT_MSG_GETOBJ) |
+ SUBSYS_NFCOMPAT(NFNL_MSG_COMPAT_GET) },
+
+#undef SUBSYS_NFCOMPAT
+
+ { "nf_newtable", SUBSYS_NFT(NFT_MSG_NEWTABLE) },
+ { "nf_gettable", SUBSYS_NFT(NFT_MSG_GETTABLE) },
+ { "nf_deltable", SUBSYS_NFT(NFT_MSG_DELTABLE) },
+ { "nf_destroytable", SUBSYS_NFT(NFT_MSG_DESTROYTABLE) },
+ /* ignores ENOENT */
+
+ { "nf_newchain", SUBSYS_NFT(NFT_MSG_NEWCHAIN) },
+ { "nf_getchain", SUBSYS_NFT(NFT_MSG_GETCHAIN) },
+ { "nf_delchain", SUBSYS_NFT(NFT_MSG_DELCHAIN) },
+ { "nf_destroychain", SUBSYS_NFT(NFT_MSG_DESTROYCHAIN) },
+
+ { "nf_newrule", SUBSYS_NFT(NFT_MSG_NEWRULE) },
+ { "nf_getrule", SUBSYS_NFT(NFT_MSG_GETRULE) },
+ { "nf_getrule_reset", SUBSYS_NFT(NFT_MSG_GETRULE_RESET) },
+ { "nf_delrule", SUBSYS_NFT(NFT_MSG_DELRULE) },
+ { "nf_destroyrule", SUBSYS_NFT(NFT_MSG_DESTROYRULE) },
+
+ { "nf_newset", SUBSYS_NFT(NFT_MSG_NEWSET) },
+ { "nf_getset", SUBSYS_NFT(NFT_MSG_GETSET) },
+ { "nf_delset", SUBSYS_NFT(NFT_MSG_DELSET) },
+ { "nf_destroyset", SUBSYS_NFT(NFT_MSG_DESTROYSET) },
+
+ { "nf_newsetelem", SUBSYS_NFT(NFT_MSG_NEWSETELEM) },
+ { "nf_getsetelem", SUBSYS_NFT(NFT_MSG_GETSETELEM) },
+ { "nf_getsetelem_reset", SUBSYS_NFT(NFT_MSG_GETSETELEM_RESET) },
+ { "nf_delsetelem", SUBSYS_NFT(NFT_MSG_DELSETELEM) },
+ { "nf_destroysetelem", SUBSYS_NFT(NFT_MSG_DESTROYSETELEM) },
+
+ { "nf_newgen", SUBSYS_NFT(NFT_MSG_NEWGEN) },
+ { "nf_getgen", SUBSYS_NFT(NFT_MSG_GETGEN) },
+
+ { "nf_trace", SUBSYS_NFT(NFT_MSG_TRACE) },
+
+ { "nf_newobj", SUBSYS_NFT(NFT_MSG_NEWOBJ) },
+ { "nf_getobj", SUBSYS_NFT(NFT_MSG_GETOBJ) },
+ { "nf_getobj_reset", SUBSYS_NFT(NFT_MSG_GETOBJ_RESET) },
+ { "nf_delobj", SUBSYS_NFT(NFT_MSG_DELOBJ) },
+ { "nf_destroyobj", SUBSYS_NFT(NFT_MSG_DESTROYOBJ) },
+
+ { "nf_newflowtable", SUBSYS_NFT(NFT_MSG_NEWFLOWTABLE) },
+ { "nf_getflowtable", SUBSYS_NFT(NFT_MSG_GETFLOWTABLE) },
+ { "nf_delflowtable", SUBSYS_NFT(NFT_MSG_DELFLOWTABLE) },
+ { "nf_destroyflowtable", SUBSYS_NFT(NFT_MSG_DESTROYFLOWTABLE) },
+#undef SUBSYS_NFT
+
{ 0 },
};
@@ -109,6 +170,7 @@ static struct gluten_offset arg_load(struct gluten_ctx *g, struct arg *a)
*/
static union value parse_field(struct gluten_ctx *g,
struct gluten_offset offset,
+ struct vec_desc *vec,
enum op_cmp_type cmp, enum jump_type jump,
int index, struct field *f, JSON_Value *jvalue)
{
@@ -134,7 +196,7 @@ ______________________ _____________
*/
v.v_num = ((long long)0xfff << 44) | (0xfff << 8);
mask_offset = emit_data(g, U64, 0, &v);
- offset = emit_bitwise(g, U64, BITWISE_AND, NULL_OFFSET,
+ offset = emit_bitwise(g, U64, NULL, BITWISE_AND, NULL_OFFSET,
offset, mask_offset);
break;
case GNU_DEV_MINOR:
@@ -144,7 +206,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
*/
v.v_num = 0xff | ((long long)0xfff << 20);
mask_offset = emit_data(g, U64, 0, &v);
- offset = emit_bitwise(g, U64, BITWISE_AND, NULL_OFFSET,
+ offset = emit_bitwise(g, U64, NULL, BITWISE_AND, NULL_OFFSET,
offset, mask_offset);
break;
default:
@@ -155,9 +217,19 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
if (f->flags & IOV) {
struct gluten_offset iovlen = offset;
+ size_t alloc;
+
+ /* vectorised ops need a zero length descriptor at the end */
+ if (json_value_get_type(jvalue) == JSONObject &&
+ (tmp = json_value_get_object(jvalue)) &&
+ json_object_get_value(tmp, "netlink"))
+ alloc = f->size +
+ sizeof(((struct nlmsghdr *)NULL)->nlmsg_len);
+ else
+ alloc = f->size;
iovlen.offset += f->desc.d_iovlen;
- offset = emit_iovload(g, offset, iovlen, f->size);
+ offset = emit_iovload(g, offset, iovlen, alloc, f->size);
}
if (json_value_get_type(jvalue) == JSONObject &&
@@ -188,7 +260,16 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
}
jvalue = json_array_get_value(set, i);
- parse_field(g, offset, cmp, jump, index, f, jvalue);
+
+ /* FIXME: ugly. Otherwise nested parse_field() will
+ * increment twice.
+ */
+ offset.offset -= f->offset;
+
+ parse_field(g, offset, vec, cmp, jump, index, f,
+ jvalue);
+
+ offset.offset += f->offset;
}
return v; /* No SELECT based on sets... of course */
@@ -215,12 +296,12 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
&set, &cmp, &cmpterm);
set_offset = emit_data(g, f->type, 0, &set);
- masked = emit_bitwise(g, f->type, BITWISE_AND,
+ masked = emit_bitwise(g, f->type, vec, BITWISE_AND,
NULL_OFFSET,
offset, set_offset);
cmp_offset = emit_data(g, f->type, 0, &cmpterm);
- emit_cmp(g, cmp, masked, cmp_offset,
+ emit_cmp(g, cmp, vec, masked, cmp_offset,
gluten_size[f->type], jump);
emit_bpf_arg(index, f->type, cmpterm, set, cmp, g->mode);
@@ -241,9 +322,13 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
} else if (f->flags & MASK) {
mask.v_num = value_get_mask(f->desc.d_num);
mask_offset = emit_data(g, f->type, 0, &mask);
- data_offset = emit_bitwise(g, f->type, BITWISE_AND,
+
+ data_offset = emit_bitwise(g, f->type, vec, BITWISE_AND,
NULL_OFFSET, offset,
mask_offset);
+ if (vec)
+ vec->len_offset = 0;
+
v.v_num = value_get_num(f->desc.d_num, jvalue);
} else {
v.v_num = value_get_num(f->desc.d_num, jvalue);
@@ -251,7 +336,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
const_offset = emit_data(g, f->type, 0, &v);
- emit_cmp(g, cmp, data_offset, const_offset,
+ emit_cmp(g, cmp, vec, data_offset, const_offset,
gluten_size[f->type], jump);
emit_bpf_arg(index, f->type, v, mask, cmp, g->mode);
@@ -266,7 +351,7 @@ ______________________ _____________
v.v_num = (v.v_num & 0xfff) << 8 | (v.v_num & ~0xfff) << 32;
const_offset = emit_data(g, U64, 0, &v);
- emit_cmp_field(g, cmp, f, offset, const_offset, jump);
+ emit_cmp_field(g, cmp, vec, f, offset, const_offset, jump);
filter_needs_deref(); /* No shifts in BPF */
break;
@@ -278,7 +363,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
v.v_num = value_get_num(f->desc.d_num, jvalue);
v.v_num = (v.v_num & 0xff) | (v.v_num & ~0xfff) << 12;
const_offset = emit_data(g, U64, 0, &v);
- emit_cmp_field(g, cmp, f, offset, const_offset, jump);
+ emit_cmp_field(g, cmp, vec, f, offset, const_offset, jump);
filter_needs_deref(); /* No shifts in BPF */
break;
@@ -292,17 +377,43 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
sel = jvalue;
}
- v = parse_field(g, offset, cmp, jump, index, f_inner, sel);
+ v = parse_field(g, offset, vec, cmp, jump, index, f_inner, sel);
f = select_field(g, index, f->desc.d_select, v);
- if (f)
- parse_field(g, offset, cmp, jump, index, f, jvalue);
+ if (f) {
+ parse_field(g, offset, vec, cmp, jump, index, f,
+ jvalue);
+ }
break;
case STRING:
if (json_value_get_type(jvalue) == JSONObject &&
(tmp = json_value_get_object(jvalue))) {
if ((jvalue = json_object_get_value(tmp, "netlink"))) {
- parse_field(g, offset, cmp, jump, index,
+ struct vec_desc v_nl, *vecptr;
+
+ /* FIXME: even send()/sendto() might need
+ * vectorised operations
+ */
+ if (f->flags & IOV) {
+ v_nl.start = offset;
+ v_nl.len_offset = offsetof(struct nlmsghdr,
+ nlmsg_len);
+ vecptr = &v_nl;
+ } else {
+ vecptr = NULL;
+ }
+
+ /* TODO: mark as CMPVEC, with:
+ * - offset of length (offsetof nlmsghdr)
+ * - vector pointer: that's already offset?
+ * - offset of comparison in vector (0 atm)
+ * - second term of comparison (from jvalue)
+ * - length (still needed?)
+ *
+ * plus BITWISEVEC? is it even needed?
+ */
+
+ parse_field(g, offset, vecptr, cmp, jump, index,
&netlink_header, jvalue);
} else {
die(" unrecognised blob type");
@@ -317,8 +428,8 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
die(" string %s too long for field", v.v_str);
const_offset = emit_data(g, STRING, strlen(v.v_str) + 1, &v);
- emit_cmp(g, CMP_NE, offset, const_offset, strlen(v.v_str) + 1,
- JUMP_NEXT_BLOCK);
+ emit_cmp(g, CMP_NE, vec, offset, const_offset,
+ strlen(v.v_str) + 1, JUMP_NEXT_BLOCK);
break;
case FDPATH:
case FDMOUNT:
@@ -328,8 +439,8 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
const_offset = emit_data(g, STRING, size, &v);
seccomp_offset = emit_seccomp_data(index);
emit_resolvefd(g, f->type, seccomp_offset, offset, size);
- emit_cmp(g, CMP_NE, offset, const_offset, size,
- JUMP_NEXT_BLOCK);
+ emit_cmp(g, CMP_NE, vec, offset, const_offset,
+ size, JUMP_NEXT_BLOCK);
break;
case STRUCT:
for (f_inner = f->desc.d_struct; f_inner->name; f_inner++) {
@@ -340,7 +451,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
if (!field_value)
continue;
- parse_field(g, offset, cmp, jump, index, f_inner,
+ parse_field(g, offset, vec, cmp, jump, index, f_inner,
field_value);
}
break;
@@ -365,7 +476,8 @@ static void parse_arg(struct gluten_ctx *g, JSON_Value *jvalue, struct arg *a)
offset = arg_load(g, a);
- parse_field(g, offset, CMP_NE, JUMP_NEXT_BLOCK, a->pos, &a->f, jvalue);
+ parse_field(g, offset, NULL, CMP_NE, JUMP_NEXT_BLOCK, a->pos, &a->f,
+ jvalue);
}
/**
@@ -407,16 +519,25 @@ static void parse_match(struct gluten_ctx *g, JSON_Object *obj,
void handle_matches(struct gluten_ctx *g, JSON_Value *value)
{
JSON_Array *matches = json_value_get_array(value);
- unsigned i;
+ unsigned i, count;
- for (i = 0; i < json_array_get_count(matches); i++) {
+ if (matches)
+ count = json_array_get_count(matches);
+ else
+ count = 1;
+
+ for (i = 0; i < count; i++) {
JSON_Object *match, *args;
struct call **set, *call;
const char *name;
+ if (matches)
+ match = json_array_get_object(matches, i);
+ else
+ match = json_value_get_object(value);
+
g->mr = g->ip;
- match = json_array_get_object(matches, i);
name = json_object_get_name(match, 0);
args = json_object_get_object(match, name);
debug(" Parsing match %i: %s", i, name);