summaryrefslogtreecommitdiffstats
path: root/src/core/bpf
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/bpf')
-rw-r--r--src/core/bpf/meson.build111
-rw-r--r--src/core/bpf/restrict_fs/meson.build24
-rw-r--r--src/core/bpf/restrict_fs/restrict-fs-skel.h14
-rw-r--r--src/core/bpf/restrict_fs/restrict-fs.bpf.c82
-rw-r--r--src/core/bpf/restrict_ifaces/meson.build24
-rw-r--r--src/core/bpf/restrict_ifaces/restrict-ifaces-skel.h14
-rw-r--r--src/core/bpf/restrict_ifaces/restrict-ifaces.bpf.c52
-rw-r--r--src/core/bpf/socket_bind/meson.build24
-rw-r--r--src/core/bpf/socket_bind/socket-bind-api.bpf.h51
-rw-r--r--src/core/bpf/socket_bind/socket-bind-skel.h14
-rw-r--r--src/core/bpf/socket_bind/socket-bind.bpf.c111
11 files changed, 521 insertions, 0 deletions
diff --git a/src/core/bpf/meson.build b/src/core/bpf/meson.build
new file mode 100644
index 0000000..f654016
--- /dev/null
+++ b/src/core/bpf/meson.build
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+if conf.get('BPF_FRAMEWORK') != 1
+ subdir_done()
+endif
+
+bpf_clang_flags = [
+ '-std=gnu11',
+ '-Wno-compare-distinct-pointer-types',
+ '-O2',
+ '-target',
+ 'bpf',
+ '-g',
+ '-c',
+]
+
+bpf_gcc_flags = [
+ '-std=gnu11',
+ '-O2',
+ '-mkernel=5.2',
+ '-mcpu=v3',
+ '-mco-re',
+ '-gbtf',
+ '-c',
+]
+
+# Generate defines that are appropriate to tell the compiler what architecture
+# we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
+# This dictionary contains the exceptions where this doesn't work.
+#
+# C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
+# and src/basic/missing_syscall_def.h.
+cpu_arch_defines = {
+ 'ppc' : ['-D__powerpc__'],
+ 'ppc64' : ['-D__powerpc64__', '-D_CALL_ELF=2'],
+ 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32'],
+ 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64'],
+ 'x86' : ['-D__i386__'],
+
+ # For arm, assume hardware fp is available.
+ 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP'],
+}
+
+bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
+ ['-D__@0@__'.format(host_machine.cpu_family())])
+if bpf_compiler == 'gcc'
+ bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
+endif
+
+libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir')
+
+bpf_o_unstripped_cmd = []
+if bpf_compiler == 'clang'
+ bpf_o_unstripped_cmd += [
+ clang,
+ bpf_clang_flags,
+ bpf_arch_flags,
+ ]
+elif bpf_compiler == 'gcc'
+ bpf_o_unstripped_cmd += [
+ bpf_gcc,
+ bpf_gcc_flags,
+ bpf_arch_flags,
+ ]
+endif
+
+bpf_o_unstripped_cmd += ['-I.']
+
+if not meson.is_cross_build() and bpf_compiler == 'clang'
+ target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
+ if target_triplet_cmd.returncode() == 0
+ target_triplet = target_triplet_cmd.stdout().strip()
+ bpf_o_unstripped_cmd += [
+ '-isystem',
+ '/usr/include/@0@'.format(target_triplet)
+ ]
+ endif
+endif
+
+bpf_o_unstripped_cmd += [
+ '-idirafter',
+ libbpf_include_dir,
+ '@INPUT@',
+ '-o',
+ '@OUTPUT@'
+]
+
+if bpftool_strip
+ bpf_o_cmd = [
+ bpftool,
+ 'gen',
+ 'object',
+ '@OUTPUT@',
+ '@INPUT@'
+ ]
+elif bpf_compiler == 'clang'
+ bpf_o_cmd = [
+ llvm_strip,
+ '-g',
+ '@INPUT@',
+ '-o',
+ '@OUTPUT@'
+ ]
+endif
+
+skel_h_cmd = [
+ bpftool,
+ 'gen',
+ 'skeleton',
+ '@INPUT@'
+]
diff --git a/src/core/bpf/restrict_fs/meson.build b/src/core/bpf/restrict_fs/meson.build
new file mode 100644
index 0000000..69cde02
--- /dev/null
+++ b/src/core/bpf/restrict_fs/meson.build
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if conf.get('BPF_FRAMEWORK') != 1
+ subdir_done()
+endif
+
+restrict_fs_bpf_o_unstripped = custom_target(
+ 'restrict-fs.bpf.unstripped.o',
+ input : 'restrict-fs.bpf.c',
+ output : 'restrict-fs.bpf.unstripped.o',
+ command : bpf_o_unstripped_cmd)
+
+restrict_fs_bpf_o = custom_target(
+ 'restrict-fs.bpf.o',
+ input : restrict_fs_bpf_o_unstripped,
+ output : 'restrict-fs.bpf.o',
+ command : bpf_o_cmd)
+
+restrict_fs_skel_h = custom_target(
+ 'restrict-fs.skel.h',
+ input : restrict_fs_bpf_o,
+ output : 'restrict-fs.skel.h',
+ command : skel_h_cmd,
+ capture : true)
diff --git a/src/core/bpf/restrict_fs/restrict-fs-skel.h b/src/core/bpf/restrict_fs/restrict-fs-skel.h
new file mode 100644
index 0000000..412cf62
--- /dev/null
+++ b/src/core/bpf/restrict_fs/restrict-fs-skel.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+/* libbpf is used via dlopen(), so rename symbols */
+#define bpf_object__open_skeleton sym_bpf_object__open_skeleton
+#define bpf_object__load_skeleton sym_bpf_object__load_skeleton
+#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton
+
+#include "bpf/restrict_fs/restrict-fs.skel.h"
diff --git a/src/core/bpf/restrict_fs/restrict-fs.bpf.c b/src/core/bpf/restrict_fs/restrict-fs.bpf.c
new file mode 100644
index 0000000..eb5ed3e
--- /dev/null
+++ b/src/core/bpf/restrict_fs/restrict-fs.bpf.c
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+#include <linux/types.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct super_block {
+ unsigned long int s_magic;
+} __attribute__((preserve_access_index));
+
+struct inode {
+ struct super_block *i_sb;
+} __attribute__((preserve_access_index));
+
+struct file {
+ struct inode *f_inode;
+} __attribute__((preserve_access_index));
+
+/*
+ * max_entries is set from user space with the bpf_map__set_max_entries helper.
+ * */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
+ __type(key, uint64_t); /* cgroup ID */
+ __type(value, uint32_t); /* fs magic set */
+} cgroup_hash SEC(".maps");
+
+SEC("lsm/file_open")
+int BPF_PROG(restrict_filesystems, struct file *file, int ret)
+{
+ unsigned long raw_magic_number;
+ uint64_t cgroup_id;
+ uint32_t *value, *magic_map, magic_number, zero = 0, *is_allow;
+
+ /* ret is the return value from the previous BPF program or 0 if it's
+ * the first hook */
+ if (ret != 0)
+ return ret;
+
+ BPF_CORE_READ_INTO(&raw_magic_number, file, f_inode, i_sb, s_magic);
+ /* super_block.s_magic is unsigned long, but magic_map keys are
+ * uint32_t. Using s_magic as-is would fail on big-endian systems,
+ * which have 64-bit unsigned long. So cast it. */
+ magic_number = (uint32_t)raw_magic_number;
+
+ cgroup_id = bpf_get_current_cgroup_id();
+
+ magic_map = bpf_map_lookup_elem(&cgroup_hash, &cgroup_id);
+ if (!magic_map)
+ return 0;
+
+ is_allow = bpf_map_lookup_elem(magic_map, &zero);
+ if (!is_allow)
+ /* Malformed map, it doesn't include whether it's an allow list
+ * or a deny list. Allow. */
+ return 0;
+
+ if (*is_allow) {
+ /* Allow-list: Allow access only if magic_number present in inner map */
+ if (!bpf_map_lookup_elem(magic_map, &magic_number))
+ return -EPERM;
+ } else {
+ /* Deny-list: Allow access only if magic_number is not present in inner map */
+ if (bpf_map_lookup_elem(magic_map, &magic_number))
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static const char _license[] SEC("license") = "GPL";
diff --git a/src/core/bpf/restrict_ifaces/meson.build b/src/core/bpf/restrict_ifaces/meson.build
new file mode 100644
index 0000000..5f36178
--- /dev/null
+++ b/src/core/bpf/restrict_ifaces/meson.build
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if conf.get('BPF_FRAMEWORK') != 1
+ subdir_done()
+endif
+
+restrict_ifaces_bpf_o_unstripped = custom_target(
+ 'restrict-ifaces.bpf.unstripped.o',
+ input : 'restrict-ifaces.bpf.c',
+ output : 'restrict-ifaces.bpf.unstripped.o',
+ command : bpf_o_unstripped_cmd)
+
+restrict_ifaces_bpf_o = custom_target(
+ 'restrict-ifaces.bpf.o',
+ input : restrict_ifaces_bpf_o_unstripped,
+ output : 'restrict-ifaces.bpf.o',
+ command : bpf_o_cmd)
+
+restrict_ifaces_skel_h = custom_target(
+ 'restrict-ifaces.skel.h',
+ input : restrict_ifaces_bpf_o,
+ output : 'restrict-ifaces.skel.h',
+ command : skel_h_cmd,
+ capture : true)
diff --git a/src/core/bpf/restrict_ifaces/restrict-ifaces-skel.h b/src/core/bpf/restrict_ifaces/restrict-ifaces-skel.h
new file mode 100644
index 0000000..f937490
--- /dev/null
+++ b/src/core/bpf/restrict_ifaces/restrict-ifaces-skel.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+/* libbpf is used via dlopen(), so rename symbols */
+#define bpf_object__open_skeleton sym_bpf_object__open_skeleton
+#define bpf_object__load_skeleton sym_bpf_object__load_skeleton
+#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton
+
+#include "bpf/restrict_ifaces/restrict-ifaces.skel.h"
diff --git a/src/core/bpf/restrict_ifaces/restrict-ifaces.bpf.c b/src/core/bpf/restrict_ifaces/restrict-ifaces.bpf.c
new file mode 100644
index 0000000..32cde5c
--- /dev/null
+++ b/src/core/bpf/restrict_ifaces/restrict-ifaces.bpf.c
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* <linux/bpf.h> must precede <bpf/bpf_helpers.h> due to integer types
+ * in bpf helpers signatures.
+ */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+const volatile __u8 is_allow_list = 0;
+
+/* Map containing the network interfaces indexes.
+ * The interpretation of the map depends on the value of is_allow_list.
+ */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, __u32);
+ __type(value, __u8);
+} sd_restrictif SEC(".maps");
+
+#define DROP 0
+#define PASS 1
+
+static __always_inline int restrict_network_interfaces_impl(const struct __sk_buff *sk) {
+ __u32 zero = 0, ifindex;
+ __u8 *lookup_result;
+
+ ifindex = sk->ifindex;
+ lookup_result = bpf_map_lookup_elem(&sd_restrictif, &ifindex);
+ if (is_allow_list) {
+ /* allow-list: let the packet pass if iface in the list */
+ if (lookup_result)
+ return PASS;
+ } else {
+ /* deny-list: let the packet pass if iface *not* in the list */
+ if (!lookup_result)
+ return PASS;
+ }
+
+ return DROP;
+}
+
+SEC("cgroup_skb/egress")
+int sd_restrictif_e(const struct __sk_buff *sk) {
+ return restrict_network_interfaces_impl(sk);
+}
+
+SEC("cgroup_skb/ingress")
+int sd_restrictif_i(const struct __sk_buff *sk) {
+ return restrict_network_interfaces_impl(sk);
+}
+
+static const char _license[] SEC("license") = "LGPL-2.1-or-later";
diff --git a/src/core/bpf/socket_bind/meson.build b/src/core/bpf/socket_bind/meson.build
new file mode 100644
index 0000000..05a2b9d
--- /dev/null
+++ b/src/core/bpf/socket_bind/meson.build
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if conf.get('BPF_FRAMEWORK') != 1
+ subdir_done()
+endif
+
+socket_bind_bpf_o_unstripped = custom_target(
+ 'socket-bind.bpf.unstripped.o',
+ input : 'socket-bind.bpf.c',
+ output : 'socket-bind.bpf.unstripped.o',
+ command : bpf_o_unstripped_cmd)
+
+socket_bind_bpf_o = custom_target(
+ 'socket-bind.bpf.o',
+ input : socket_bind_bpf_o_unstripped,
+ output : 'socket-bind.bpf.o',
+ command : bpf_o_cmd)
+
+socket_bind_skel_h = custom_target(
+ 'socket-bind.skel.h',
+ input : socket_bind_bpf_o,
+ output : 'socket-bind.skel.h',
+ command : skel_h_cmd,
+ capture : true)
diff --git a/src/core/bpf/socket_bind/socket-bind-api.bpf.h b/src/core/bpf/socket_bind/socket-bind-api.bpf.h
new file mode 100644
index 0000000..277b9bb
--- /dev/null
+++ b/src/core/bpf/socket_bind/socket-bind-api.bpf.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+#include <linux/types.h>
+
+/*
+ * Bind rule is matched with socket fields accessible to cgroup/bind{4,6} hook
+ * through bpf_sock_addr struct.
+ * 'address_family' is expected to be one of AF_UNSPEC, AF_INET or AF_INET6.
+ * Matching by family is bypassed for rules with AF_UNSPEC set, which makes the
+ * rest of a rule applicable for both IPv4 and IPv6 addresses.
+ * If matching by family is either successful or bypassed, a rule and a socket
+ * are matched by ip protocol.
+ * If 'protocol' is 0, matching is bypassed.
+ * 'nr_ports' and 'port_min' fields specify a set of ports to match a user port
+ * with.
+ * If 'nr_ports' is 0, matching by port is bypassed, making that rule applicable
+ * for all possible ports, e.g. [1, 65535] range. Thus a rule with
+ * 'address_family', 'protocol' and 'nr_ports' equal to AF_UNSPEC, 0 and 0
+ * correspondingly forms 'allow any' or 'deny any' cases.
+ * For positive 'nr_ports', a user_port lying in a range from 'port_min' to'
+ * 'port_min' + 'nr_ports' exclusively is considered to be a match. 'nr_ports'
+ * equalling to 1 forms a rule for a single port.
+ * Ports are in host order.
+ *
+ * Examples:
+ * AF_UNSPEC, 1, 0, 7777: match IPv4 and IPv6 addresses with 7777 user port;
+ *
+ * AF_INET, 1023, 0, 1: match IPv4 addresses with user port in [1, 1023]
+ * range inclusively;
+ *
+ * AF_INET6, 0, 0, 0: match IPv6 addresses;
+ *
+ * AF_UNSPEC, 0, 0, 0: match IPv4 and IPv6 addresses;
+ *
+ * AF_INET6, IPPROTO_TCP, 0, 0: match IPv6/TCP addresses.
+ */
+
+struct socket_bind_rule {
+ __u32 address_family;
+ __u32 protocol;
+ __u16 nr_ports;
+ __u16 port_min;
+};
+
+#define SOCKET_BIND_MAX_RULES 128
diff --git a/src/core/bpf/socket_bind/socket-bind-skel.h b/src/core/bpf/socket_bind/socket-bind-skel.h
new file mode 100644
index 0000000..e0d1626
--- /dev/null
+++ b/src/core/bpf/socket_bind/socket-bind-skel.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+/* libbpf is used via dlopen(), so rename symbols */
+#define bpf_object__open_skeleton sym_bpf_object__open_skeleton
+#define bpf_object__load_skeleton sym_bpf_object__load_skeleton
+#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton
+
+#include "bpf/socket_bind/socket-bind.skel.h"
diff --git a/src/core/bpf/socket_bind/socket-bind.bpf.c b/src/core/bpf/socket_bind/socket-bind.bpf.c
new file mode 100644
index 0000000..b7972a8
--- /dev/null
+++ b/src/core/bpf/socket_bind/socket-bind.bpf.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* The SPDX header above is actually correct in claiming this was
+ * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
+ * compatible with GPL we will claim this to be GPL however, which should be
+ * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
+ */
+
+#include "socket-bind-api.bpf.h"
+/* <linux/types.h> must precede <bpf/bpf_helpers.h> due to
+ * <bpf/bpf_helpers.h> does not depend from type header by design.
+ */
+#include <linux/types.h>
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+#include <linux/bpf.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+
+/*
+ * max_entries is set from user space with bpf_map__set_max_entries helper.
+ */
+struct socket_bind_map_t {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, __u32);
+ __type(value, struct socket_bind_rule);
+};
+
+enum socket_bind_action {
+ SOCKET_BIND_DENY = 0,
+ SOCKET_BIND_ALLOW = 1,
+};
+
+struct socket_bind_map_t sd_bind_allow SEC(".maps");
+struct socket_bind_map_t sd_bind_deny SEC(".maps");
+
+static __always_inline bool match_af(
+ __u8 address_family, const struct socket_bind_rule *r) {
+ return r->address_family == AF_UNSPEC || address_family == r->address_family;
+}
+
+static __always_inline bool match_protocol(
+ __u32 protocol, const struct socket_bind_rule *r) {
+ return r->protocol == 0 || r->protocol == protocol;
+}
+
+static __always_inline bool match_user_port(
+ __u16 port, const struct socket_bind_rule *r) {
+ return r->nr_ports == 0 ||
+ (port >= r->port_min && port < r->port_min + (__u32) r->nr_ports);
+}
+
+static __always_inline bool match(
+ __u8 address_family,
+ __u32 protocol,
+ __u16 port,
+ const struct socket_bind_rule *r) {
+ return match_af(address_family, r) &&
+ match_protocol(protocol, r) &&
+ match_user_port(port, r);
+}
+
+static __always_inline bool match_rules(
+ struct bpf_sock_addr *ctx,
+ struct socket_bind_map_t *rules) {
+ volatile __u32 user_port = ctx->user_port;
+ __u16 port = (__u16)bpf_ntohs(user_port);
+
+ for (__u32 i = 0; i < SOCKET_BIND_MAX_RULES; ++i) {
+ const __u32 key = i;
+ const struct socket_bind_rule *rule = bpf_map_lookup_elem(rules, &key);
+
+ /* Lookup returns NULL if iterator is advanced past the last
+ * element put in the map. */
+ if (!rule)
+ break;
+
+ if (match(ctx->user_family, ctx->protocol, port, rule))
+ return true;
+ }
+
+ return false;
+}
+
+static __always_inline int bind_socket(struct bpf_sock_addr *ctx) {
+ if (match_rules(ctx, &sd_bind_allow))
+ return SOCKET_BIND_ALLOW;
+
+ if (match_rules(ctx, &sd_bind_deny))
+ return SOCKET_BIND_DENY;
+
+ return SOCKET_BIND_ALLOW;
+}
+
+SEC("cgroup/bind4")
+int sd_bind4(struct bpf_sock_addr *ctx) {
+ if (ctx->user_family != AF_INET || ctx->family != AF_INET)
+ return SOCKET_BIND_ALLOW;
+
+ return bind_socket(ctx);
+}
+
+SEC("cgroup/bind6")
+int sd_bind6(struct bpf_sock_addr *ctx) {
+ if (ctx->user_family != AF_INET6 || ctx->family != AF_INET6)
+ return SOCKET_BIND_ALLOW;
+
+ return bind_socket(ctx);
+}
+
+char _license[] SEC("license") = "GPL";