From 001ced17982fffb4132be81d5a59dfe749becc60 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 3 Feb 2025 14:08:29 +0100 Subject: socket: properly support SO_ATTACH_FILTER sockopt The SO_ATTACH_FILTER socket option requires special handling as it is not passing a self-contained structure to the kernel but a pointer to user memory holding the actual BPF bytecode. In order to properly support this, first rework the ucode value to C struct conversion callback machinery to pass an indirect struct base pointer, allowing conversion callbacks to realloc the struct memory as needed. Finally introduce custom uv to C conversion for the BPF data which accepts either a raw bytecode string, an array of opcode arrays or a flat array of opcode values which are converted into a C array of struct sock_filter records appended to the reallocated struct memory. Attaching a BPF program equivalent to the tcpdump expression `vlan 20 && ether proto 0x1234` would then look like this: sock.setopt(SOL_SOCKET, SO_ATTACH_FILTER, { filter: [ [ 0x28, 0, 0, 0x0000000c ], [ 0x15, 2, 0, 0x00008100 ], [ 0x15, 1, 0, 0x000088a8 ], [ 0x15, 0, 6, 0x00009100 ], [ 0x28, 0, 0, 0x0000000e ], [ 0x54, 0, 0, 0x00000fff ], [ 0x15, 0, 3, 0x00000014 ], [ 0x28, 0, 0, 0x00000010 ], [ 0x15, 0, 1, 0x00001234 ], [ 0x6, 0, 0, 0x00040000 ], [ 0x6, 0, 0, 0x00000000 ], ] }); Signed-off-by: Jo-Philipp Wich --- lib/socket.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 7 deletions(-) (limited to 'lib/socket.c') diff --git a/lib/socket.c b/lib/socket.c index c91898a..f4b10de 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -78,6 +78,7 @@ #if defined(__linux__) # include +# include # ifndef SO_TIMESTAMP_OLD # define SO_TIMESTAMP_OLD SO_TIMESTAMP @@ -969,6 +970,90 @@ static struct_t st_timeval = { }; #if defined(__linux__) +static bool +filter_to_c(void *st, uc_value_t *uv) +{ + struct sock_fprog **fpp = st; + struct sock_fprog *fp = *fpp; + size_t i, len; + + if (ucv_type(uv) == UC_STRING) { + size_t len = ucv_string_length(uv); + + if (len == 0 || (len % sizeof(struct sock_filter)) != 0) + err_return(EINVAL, "Filter program length not a multiple of %zu", + sizeof(struct sock_filter)); + + fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog) + len); + fp->filter = memcpy((char *)fp + sizeof(struct sock_fprog), ucv_string_get(uv), len); + + if (fp->len == 0) + fp->len = len / sizeof(struct sock_filter); + } + else if (ucv_type(uv) == UC_ARRAY) { + /* Opcode array of array. Each sub-array is a 4 element tuple */ + if (ucv_type(ucv_array_get(uv, 0)) == UC_ARRAY) { + len = ucv_array_length(uv); + + fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog) + + (len * sizeof(struct sock_filter))); + + fp->filter = (struct sock_filter *)((char *)fp + sizeof(struct sock_fprog)); + + for (i = 0; i < len; i++) { + uc_value_t *op = ucv_array_get(uv, i); + + if (ucv_type(op) != UC_ARRAY) + continue; + + fp->filter[i].code = ucv_to_unsigned(ucv_array_get(op, 0)); + fp->filter[i].jt = ucv_to_unsigned(ucv_array_get(op, 1)); + fp->filter[i].jf = ucv_to_unsigned(ucv_array_get(op, 2)); + fp->filter[i].k = ucv_to_unsigned(ucv_array_get(op, 3)); + } + } + + /* Flat opcode array, must be a multiple of 4 */ + else { + len = ucv_array_length(uv); + + if (len % 4) + err_return(EINVAL, "Opcode array length not a multiple of 4"); + + len /= 4; + + fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog) + + (len * sizeof(struct sock_filter))); + + fp->filter = (struct sock_filter *)((char *)fp + sizeof(struct sock_fprog)); + + for (i = 0; i < len; i++) { + fp->filter[i].code = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 0)); + fp->filter[i].jt = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 1)); + fp->filter[i].jf = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 2)); + fp->filter[i].k = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 3)); + } + } + + if (fp->len == 0) + fp->len = i; + } + else { + err_return(EINVAL, "Expecting either BPF bytecode string or array of opcodes"); + } + + return true; +} + +static struct_t st_sock_fprog = { + .size = sizeof(struct sock_fprog), + .members = (member_t []){ + STRUCT_MEMBER_NP(sock_fprog, len, DT_UNSIGNED), + STRUCT_MEMBER_CB(filter, filter_to_c, NULL), + { 0 } + } +}; + static struct_t st_ucred = { .size = sizeof(struct ucred), .members = (member_t []){ @@ -1051,7 +1136,7 @@ in6_ifindex_to_uv(void *st) static bool in6_ifindex_to_c(void *st, uc_value_t *uv) { - struct ipv6_mreq *mr = st; + struct ipv6_mreq *mr = *(struct ipv6_mreq **)st; if (ucv_type(uv) == UC_STRING) { mr->ipv6mr_interface = if_nametoindex(ucv_string_get(uv)); @@ -1189,7 +1274,9 @@ rcv_wscale_to_uv(void *st) static bool snd_wscale_to_c(void *st, uc_value_t *uv) { - ((struct tcp_info *)st)->tcpi_snd_wscale = ucv_to_unsigned(uv); + struct tcp_info *ti = *(struct tcp_info **)st; + + ti->tcpi_snd_wscale = ucv_to_unsigned(uv); if (errno) err_return(errno, "Unable to convert field snd_wscale to unsigned"); @@ -1200,7 +1287,9 @@ snd_wscale_to_c(void *st, uc_value_t *uv) static bool rcv_wscale_to_c(void *st, uc_value_t *uv) { - ((struct tcp_info *)st)->tcpi_rcv_wscale = ucv_to_unsigned(uv); + struct tcp_info *ti = *(struct tcp_info **)st; + + ti->tcpi_rcv_wscale = ucv_to_unsigned(uv); if (errno) err_return(errno, "Unable to convert field rcv_wscale to unsigned"); @@ -1314,7 +1403,7 @@ mr_ifindex_to_uv(void *st) static bool mr_ifindex_to_c(void *st, uc_value_t *uv) { - struct packet_mreq *mr = st; + struct packet_mreq *mr = *(struct packet_mreq **)st; if (ucv_type(uv) == UC_STRING) { mr->mr_ifindex = if_nametoindex(ucv_string_get(uv)); @@ -1344,7 +1433,7 @@ mr_address_to_uv(void *st) static bool mr_address_to_c(void *st, uc_value_t *uv) { - struct packet_mreq *mr = st; + struct packet_mreq *mr = *(struct packet_mreq **)st; size_t len; if (!uv_to_hwaddr(uv, mr->mr_address, &len)) @@ -1507,7 +1596,7 @@ static sockopt_t sockopts[] = { { SOL_SOCKET, SO_TIMESTAMP, SV_BOOL }, { SOL_SOCKET, SO_TYPE, SV_INT }, #if defined(__linux__) - { SOL_SOCKET, SO_ATTACH_FILTER, SV_STRING }, + { SOL_SOCKET, SO_ATTACH_FILTER, &st_sock_fprog }, { SOL_SOCKET, SO_ATTACH_BPF, SV_INT }, { SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SV_STRING }, { SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, SV_INT }, @@ -1792,7 +1881,7 @@ uv_to_struct(uc_value_t *uv, struct_t *spec) break; case DT_CALLBACK: - if (m->u1.to_c && !m->u1.to_c(st, fv)) { + if (m->u1.to_c && !m->u1.to_c(&st, fv)) { free(st); return NULL; } -- cgit v1.2.3