From bdbec30a849807fb5e6841a38cfe0d168e5962b9 Mon Sep 17 00:00:00 2001 From: Stefano Brivio Date: Thu, 21 Dec 2023 12:06:05 +0100 Subject: seitan: Add netlink, sendto()/sendmsg(), iovec handling, demo with routes A bit rough at the moment, but it does the trick. Bonus: setsockopt() (with magic values only, not used in any demo yet). Signed-off-by: Stefano Brivio --- cooker/call.c | 61 +++++++++++-- cooker/calls/net.c | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cooker/cooker.h | 6 +- cooker/emit.c | 38 +++++++- cooker/emit.h | 4 + cooker/gluten.c | 16 +++- cooker/gluten.h | 4 +- cooker/match.c | 56 +++++++++++- cooker/parse.c | 6 +- 9 files changed, 434 insertions(+), 16 deletions(-) (limited to 'cooker') diff --git a/cooker/call.c b/cooker/call.c index bb53829..dd37fe9 100644 --- a/cooker/call.c +++ b/cooker/call.c @@ -72,7 +72,7 @@ static union value parse_metadata(struct gluten_ctx *g, struct field *f, if (tag_offset.type == OFFSET_NULL) die(" tag not found"); - if ((*base_offset)->type == OFFSET_NULL) { + if ((*base_offset)->type == OFFSET_NULL || (f->flags & WBUF)) { **base_offset = tag_offset; } else if (f->flags & MASK || add) { emit_bitwise(g, f->type, BITWISE_OR, offset, offset, @@ -133,6 +133,10 @@ Examples of arguments: - STRING: abcd write abcd to ro_data (using size), and pointer to it parse_arg() passes ro_data offset +- IOV: write (one!) struct iovec with iov_base pointer to tag + parse_arg() passes null offset + parse_field() gives back data offset + - STRUCT: 1, 2 write struct to ro_data, and pointer to it parse_arg() passes ro_data offset @@ -169,12 +173,38 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args, (tmp1 = json_value_get_object(jvalue)) && is_metadata_obj(tmp1)) { v = parse_metadata(g, f, &base_offset, offset, tmp1, dry_run, add); - if (v.v_num == 0) + if (!(f->flags & IOV) && v.v_num == 0) + return v; + } + + if (f->flags & IOV) { + struct gluten_offset iov; + struct iovec *iovp; + intptr_t *msg_iov; + + if (dry_run) return v; + + /* iov_base: v */ + /* iov_len: at the moment from field */ + msg_iov = (intptr_t *)gluten_ptr(&g->g, offset); + + iov = gluten_rw_alloc(g, sizeof(struct iovec)); + iovp = (struct iovec *)gluten_ptr(&g->g, iov); + iovp->iov_base = (void *)(intptr_t)base_offset->offset; + gluten_relocation_add(g, iov); + + iovp->iov_len = f->size; + + *msg_iov = iov.offset; + gluten_relocation_add(g, offset); + + return v; } if (!jvalue && !(f->flags & SIZE)) return v; + switch (f->type) { case USHORT: case INT: @@ -249,12 +279,28 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args, tmp1 = json_value_get_object(jvalue); f_value = json_object_get_value(tmp1, f_inner->name); - debug(" parse struct internal value:%s", + debug(" parse struct, field %s, internal value:%s", + f_inner->name, json_serialize_to_string(f_value)); - if (!f_value) + if (!f_value && !(f_inner->flags & SIZE)) continue; - parse_field(g, args, &struct_start, index, f_inner, - f_value, false, add); + + if (f_inner->size && f_inner->type == SELECT) { + struct gluten_offset ptr_offset; + intptr_t *link; + + ptr_offset = gluten_rw_alloc(g, f_inner->size); + parse_field(g, args, &ptr_offset, index, f_inner, + f_value, false, add); + + link = (intptr_t *)gluten_ptr(&g->g, offset); + *link = ptr_offset.offset; + gluten_relocation_add(g, offset); + } else { + parse_field(g, args, &struct_start, index, f_inner, + f_value, false, add); + } + if (base_offset->type == OFFSET_NULL) *base_offset = struct_start; } @@ -278,6 +324,9 @@ bool arg_needs_temp(struct field *f, int pos, JSON_Value *jvalue, if (f->flags & COPY_ON_CALL) return true; + if (f->flags & IOV) + return true; + if (f->flags & SIZE) return false; diff --git a/cooker/calls/net.c b/cooker/calls/net.c index a348037..94b13cd 100644 --- a/cooker/calls/net.c +++ b/cooker/calls/net.c @@ -79,6 +79,9 @@ static struct num protocols[] = { { "mpls", IPPROTO_MPLS }, { "raw", IPPROTO_RAW }, { "mptcp", IPPROTO_MPTCP }, + + { "nl_route", NETLINK_ROUTE }, + { 0 }, }; @@ -243,9 +246,265 @@ static struct arg connect_args[] = { { 0 } }; +static struct num send_flags[] = { + { "confirm", MSG_CONFIRM }, + { "dontroute", MSG_DONTROUTE }, + { "dontwait", MSG_DONTWAIT }, + { "eor", MSG_EOR }, + { "more", MSG_MORE }, + { "nosignal", MSG_NOSIGNAL }, + { "oob", MSG_OOB }, + { "fastopen", MSG_FASTOPEN }, + { 0 }, +}; + +static struct arg send_args[] = { + { 0, + { + "fd", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 1, + { + "buf", STRING, WBUF, + 0, + BUFSIZ, + { 0 }, + }, + }, + { 2, + { + "len", LONG, SIZE, + 0, + 0, + { .d_size = (intptr_t)&send_args[1] }, + }, + }, + { 3, + { + "flags", INT, FLAGS, + 0, + 0, + { .d_num = send_flags } + } + }, + { 0 } +}; + +static struct arg sendto_args[] = { + { 0, + { + "fd", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 1, + { + "buf", STRING, WBUF, + 0, + BUFSIZ, + { 0 }, + }, + }, + { 2, + { + "len", LONG, SIZE, + 0, + 0, + { .d_size = (intptr_t)&sendto_args[1] }, + }, + }, + { 3, + { + "flags", INT, FLAGS, + 0, + 0, + { .d_num = send_flags } + } + }, + { 4, + { + "addr", SELECT, 0, + 0, + sizeof(struct sockaddr_storage), + { .d_select = &connect_addr_select }, + }, + }, + { 5, + { + "addrlen", LONG, SIZE, + 0, + 0, + { .d_size = (intptr_t)&connect_addr_select }, + }, + }, + { 0 } +}; + +static struct select sendmsg_name_select = { + &connect_family, { .d_num = connect_addr_select_family } +}; + +static struct field sendmsg_msghdr[] = { + { + "name", SELECT, 0, + offsetof(struct msghdr, msg_name), + sizeof(struct sockaddr_storage), + { .d_select = &sendmsg_name_select }, + }, + { + "namelen", LONG, SIZE, + offsetof(struct msghdr, msg_namelen), + 0, + { .d_size = (intptr_t)&sendmsg_name_select }, + }, + { + "iov", STRING, WBUF | IOV, + offsetof(struct msghdr, msg_iov), + BUFSIZ, + { .d_iovlen = offsetof(struct msghdr, msg_iovlen) - + offsetof(struct msghdr, msg_iov) }, + }, + { + "iovlen", LONG, 0, + offsetof(struct msghdr, msg_iovlen), + 0, + { 0 }, + }, + { + "control", STRING, 0, + offsetof(struct msghdr, msg_control), + BUFSIZ, + { 0 }, + }, + { + "controllen", LONG, SIZE, + offsetof(struct msghdr, msg_controllen), + 0, + { 0 }, + }, + { + "flags", INT, 0, + offsetof(struct msghdr, msg_flags), + 0, + { 0 }, + }, + { 0 } +}; + +static struct arg sendmsg_args[] = { + { 0, + { + "fd", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 1, + { + "msg", STRUCT, 0, + 0, + sizeof(struct msghdr), + { .d_struct = sendmsg_msghdr }, + }, + }, + { 2, + { + "flags", INT, FLAGS, + 0, + 0, + { .d_num = send_flags } + } + }, + { 0 } +}; + +static struct arg recvmsg_args[] = { + { 0, + { + "fd", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 1, + { + "msg", STRUCT, 0, + 0, + sizeof(struct msghdr), + { .d_struct = sendmsg_msghdr }, + }, + }, + { 2, + { + "flags", INT, FLAGS, + 0, + 0, + { .d_num = send_flags } + } + }, + { 0 } +}; + +static struct arg setsockopt_args[] = { + { 0, + { + "fd", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 1, + { + "level", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 2, + { + "optname", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 3, + { + "optval", INT, 0, + 0, + BUFSIZ, + { 0 }, + }, + }, + { 4, + { + "optlen", INT, 0, + 0, + 0, + { 0 }, + }, + }, + { 0 } +}; + struct call syscalls_net[] = { { __NR_connect, "connect", connect_args }, { __NR_bind, "bind", connect_args }, { __NR_socket, "socket", socket_args }, + /* { __NR_send, "send", send_args }, */ + { __NR_sendto, "sendto", sendto_args }, + { __NR_sendmsg, "sendmsg", sendmsg_args }, + { __NR_recvmsg, "recvmsg", recvmsg_args }, + { __NR_setsockopt, "setsockopt", setsockopt_args }, { 0 }, }; diff --git a/cooker/cooker.h b/cooker/cooker.h index 9217c40..05a1f1b 100644 --- a/cooker/cooker.h +++ b/cooker/cooker.h @@ -35,13 +35,15 @@ struct size; * @d_num: Pointer to a list of numbers and their labels * @d_struct: Pointer to a struct description * @d_select: Pointer to description of a selector - * @d_arg_size: Position of argument whose pointed length is described + * @d_size: Position of argument whose pointed length is described + * @d_iovlen: Relative offset from pointed iovec field to corresponding iovlen */ union desc { struct num *d_num; struct field *d_struct; struct select *d_select; intptr_t d_size; + ptrdiff_t d_iovlen; }; /** @@ -110,6 +112,8 @@ enum flags { RBUF = BIT(5), /* Copy value from original call, ignore on return */ WBUF = BIT(6), + + IOV = BIT(7), }; #define TYPE_COUNT (TYPE_END - 1) diff --git a/cooker/emit.c b/cooker/emit.c index efe70eb..7899fa5 100644 --- a/cooker/emit.c +++ b/cooker/emit.c @@ -119,7 +119,7 @@ void emit_call(struct gluten_ctx *g, struct context_desc *cdesc, long nr, memcpy(desc->args, offset, sizeof(struct gluten_offset) * count); desc->args[count] = ret_offset; - debug(" %i: OP_CALL: %s, arguments:", g->ip.offset, syscall_name_str[nr]); + debug(" %i: OP_CALL: %s, arguments:", g->ip.offset, syscall_name(nr)); for (i = 0; i < count; i++) { debug("\t%i: %s %s%i", i, gluten_offset_name[offset[i].type], is_ptr[i] ? "*" : "", offset[i].offset); @@ -179,6 +179,42 @@ void emit_store(struct gluten_ctx *g, struct gluten_offset dst, die("Too many instructions"); } +/** + * emit_iovload() - Emit OP_IOVLOAD instruction: IO vector from loaded pointers + * @g: gluten context + * @iov: Pointer to msg_iov, already stored in gluten + * @iovlen: Pointer to msg_iovlen, already stored in gluten + * @dst: gluten destination to copy dereferenced data + * @len: Maximum length of data to copy altogether + */ +struct gluten_offset emit_iovload(struct gluten_ctx *g, + struct gluten_offset iov, + struct gluten_offset iovlen, + size_t len) +{ + struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); + struct op_iovload *load = &op->op.iovload; + struct gluten_offset dst; + + dst = gluten_rw_alloc(g, len); + + op->type = OP_IOVLOAD; + + load->iov = iov; + load->iovlen = iovlen; + + load->dst = dst; + load->size = len; + + debug(" %i: OP_IOVLOAD: #%i < (#%i) as iovec (size: %lu)", + g->ip.offset, dst.offset, iov.offset, len); + + if (++g->ip.offset > INST_MAX) + die("Too many instructions"); + + return dst; +} + /** * emit_resolved() - Emit OP_RESOLVEFD instruction: resolve file descriptor with path * @g: gluten context diff --git a/cooker/emit.h b/cooker/emit.h index 835aae7..5557187 100644 --- a/cooker/emit.h +++ b/cooker/emit.h @@ -13,6 +13,10 @@ void emit_call(struct gluten_ctx *g, struct context_desc *cdesc, long nr, struct gluten_offset offset[6], struct gluten_offset ret_offset); void emit_load(struct gluten_ctx *g, struct gluten_offset dst, int index, size_t len); +struct gluten_offset emit_iovload(struct gluten_ctx *g, + struct gluten_offset iov, + struct gluten_offset iovlen, + size_t len); void emit_store(struct gluten_ctx *g, struct gluten_offset dst, struct gluten_offset src, struct gluten_offset count); struct gluten_offset emit_seccomp_data(int index); diff --git a/cooker/gluten.c b/cooker/gluten.c index 4d3aea5..69c9384 100644 --- a/cooker/gluten.c +++ b/cooker/gluten.c @@ -146,6 +146,7 @@ void gluten_add_attr(struct gluten_ctx *g, enum attr_type type, intptr_t id, union value gluten_get_attr(struct gluten_ctx *g, enum attr_type type, intptr_t id) { + union value missing = { 0 }; int i; for (i = 0; i < ATTRS_MAX && g->attrs[i].type; i++) { @@ -153,8 +154,8 @@ union value gluten_get_attr(struct gluten_ctx *g, enum attr_type type, return g->attrs[i].v; } - die(" attribute '%p' not found", id); - return g->attrs[0].v; /* Pro forma, not actually happening */ + debug(" attribute '%p' not found", id); + return missing; } /** @@ -163,8 +164,8 @@ union value gluten_get_attr(struct gluten_ctx *g, enum attr_type type, */ void gluten_init(struct gluten_ctx *g) { - g->ip.type = g->lr.type = g->mr.type = OFFSET_INSTRUCTION; g->ip.offset = g->lr.offset = g->mr.offset = 0; + g->ip.type = g->lr.type = g->mr.type = OFFSET_INSTRUCTION; g->dp.type = OFFSET_DATA; g->cp.type = OFFSET_RO_DATA; } @@ -185,3 +186,12 @@ void gluten_write(struct gluten_ctx *g, const char *path) close(fd); } + +void gluten_relocation_add(struct gluten_ctx *g, struct gluten_offset offset) +{ + g->g.header.relocation[g->rel_count++] = offset; + debug(" Added relocation for %s: %u", gluten_offset_name[offset.type], + offset.offset); + if (g->rel_count >= 256) + die("Too many relocations"); +} diff --git a/cooker/gluten.h b/cooker/gluten.h index 4659583..4a3b3f0 100644 --- a/cooker/gluten.h +++ b/cooker/gluten.h @@ -44,6 +44,8 @@ struct gluten_ctx { struct arg *selected_arg[6]; enum scmp_mode mode; + + size_t rel_count; }; /** @@ -70,8 +72,8 @@ void gluten_add_attr(struct gluten_ctx *g, enum attr_type type, intptr_t id, union value v); union value gluten_get_attr(struct gluten_ctx *g, enum attr_type type, intptr_t id); +void gluten_relocation_add(struct gluten_ctx *g, struct gluten_offset offset); void gluten_init(struct gluten_ctx *g); -void gluten_block_init(struct gluten_ctx *g); void gluten_write(struct gluten_ctx *g, const char *path); extern size_t gluten_size[TYPE_COUNT]; diff --git a/cooker/match.c b/cooker/match.c index 36ac9df..c56d9e5 100644 --- a/cooker/match.c +++ b/cooker/match.c @@ -19,6 +19,40 @@ #include "calls/net.h" #include "seccomp_profile.h" + +#include +#include + +static struct num netlink_types[] = { + { "newroute", RTM_NEWROUTE }, + { 0 }, +}; + +static struct num netlink_flags[] = { + { "request", NLM_F_REQUEST }, + { "create", NLM_F_CREATE }, + { 0 }, +}; + +static struct field netlink_header_fields[] = { + { + "type", USHORT, 0, + offsetof(struct nlmsghdr, nlmsg_type), + 0, { .d_num = netlink_types } + }, + { + "flags", USHORT, FLAGS, + offsetof(struct nlmsghdr, nlmsg_flags), + 0, { .d_num = netlink_flags } + }, + { 0 }, +}; + +static struct field netlink_header = { + "netlink", STRUCT, 0, 0, 0, + { .d_struct = netlink_header_fields } +}; + /** * arg_load() - Allocate and build bytecode for one syscall argument * @g: gluten context @@ -117,6 +151,15 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx break; } + offset.offset += f->offset; + + if (f->flags & IOV) { + struct gluten_offset iovlen = offset; + + iovlen.offset += f->desc.d_iovlen; + offset = emit_iovload(g, offset, iovlen, f->size); + } + if (json_value_get_type(jvalue) == JSONObject && (tmp = json_value_get_object(jvalue)) && (tag_name = json_object_get_string(tmp, "set"))) { @@ -155,8 +198,6 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx if (!jvalue || (f->flags & RBUF)) return v; - offset.offset += f->offset; - switch (f->type) { case USHORT: case INT: @@ -258,6 +299,17 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx parse_field(g, offset, 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, + &netlink_header, jvalue); + } else { + die(" unrecognised blob type"); + } + break; + } + if ((v.v_str = json_value_get_string(jvalue)) == NULL) die(" failed parsing field for value:%s", json_serialize_to_string_pretty(jvalue)); diff --git a/cooker/parse.c b/cooker/parse.c index 5d5cab3..8d0c20d 100644 --- a/cooker/parse.c +++ b/cooker/parse.c @@ -201,14 +201,14 @@ struct rule_parser { static union value value_get_set(struct num *desc, JSON_Array *set) { + union value n = { 0 }; struct num *tmp; - union value n; unsigned i; for (i = 0; i < json_array_get_count(set); i++) { for (tmp = desc; tmp->name; tmp++) { if (!strcmp(tmp->name, json_array_get_string(set, i))) { - n.v_num |= desc->value; + n.v_num |= tmp->value; break; } } @@ -364,6 +364,8 @@ static void parse_block(struct gluten_ctx *g, JSON_Object *block) unsigned i; memset(g->selected_arg, 0, sizeof(g->selected_arg)); + memset(g->match_dst, 0, sizeof(g->match_dst)); + memset(g->call_src, 0, sizeof(g->call_src)); memset(g->tags, 0, sizeof(g->tags)); g->lr = g->ip; -- cgit v1.2.3