[PATCH bpf-next 6/6] selftests/bpf: add a test for BPF_CGROUP_FILE_OPEN

Alexei Starovoitov
2018-10-04T02:58:03Z

add bpf test for BPF_CGROUP_FILE_OPEN which attaches to a temporary cgroup and
- disallows further access to cgroup v2 file system for processes within this cgroup
- figures out mount_id of /etc, disallows access to this mnt_id,
checks that /etc/hosts and /etc/hostname are no longer readable,
re-allows access to /etc.
Note that /etc is likely mounted as part of /, so the test disallows access to / mount
- figures out dev/inode of /etc/hosts file and disallows access to this file

cgroup local storage is used to pass information between user space control program
and bpf program attached to BPF_CGROUP_FILE_OPEN hook

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/.gitignore | 1 +
tools/testing/selftests/bpf/Makefile | 6 +-
tools/testing/selftests/bpf/test_file_open.c | 154 ++++++++++++++++++
.../selftests/bpf/test_file_open_common.h | 13 ++
.../selftests/bpf/test_file_open_kern.c | 48 ++++++
5 files changed, 220 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/bpf/test_file_open.c
create mode 100644 tools/testing/selftests/bpf/test_file_open_common.h
create mode 100644 tools/testing/selftests/bpf/test_file_open_kern.c

diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index 8a60c9b9892d..a332b39bed68 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -25,3 +25,4 @@ test_cgroup_storage
test_select_reuseport
test_flow_dissector
flow_dissector_load
+test_file_open
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 1381ab81099c..89a0fd955c8e 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -24,7 +24,7 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
- test_netcnt
+ test_netcnt test_file_open

TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
@@ -36,7 +36,8 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
- test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_sk_lookup_kern.o
+ test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_sk_lookup_kern.o \
+ test_file_open_kern.o

# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
@@ -74,6 +75,7 @@ $(OUTPUT)/test_progs: trace_helpers.c
$(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c
$(OUTPUT)/test_cgroup_storage: cgroup_helpers.c
$(OUTPUT)/test_netcnt: cgroup_helpers.c
+$(OUTPUT)/test_file_open: cgroup_helpers.c

.PHONY: force

diff --git a/tools/testing/selftests/bpf/test_file_open.c b/tools/testing/selftests/bpf/test_file_open.c
new file mode 100644
index 000000000000..33716adafecf
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_file_open.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018 Facebook */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/kdev_t.h>
+
+#include <linux/bpf.h>
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+
+#include "cgroup_helpers.h"
+#include "bpf_rlimit.h"
+#include "test_file_open_common.h"
+
+#define CGROUP_PROG "./test_file_open_kern.o"
+
+#define TEST_CGROUP "/test-bpf-based-device-cgroup/"
+
+int main(int argc, char **argv)
+{
+ struct bpf_cgroup_storage_key key;
+ struct file_handle fhp[1] = {};
+ struct test_file_open_config cfg = {};
+ struct bpf_object *obj;
+ int error = EXIT_FAILURE;
+ int prog_fd, cgroup_fd;
+ struct bpf_map *map;
+ __u32 prog_cnt;
+ struct stat st;
+ int map_fd;
+
+ if (bpf_prog_load(CGROUP_PROG, BPF_PROG_TYPE_FILE_FILTER,
+ &obj, &prog_fd)) {
+ printf("Failed to load FILE_FILTER program\n");
+ goto out;
+ }
+
+ map = bpf_object__find_map_by_name(obj, "local_storage");
+ if (!map) {
+ printf("Failed to find cgroup local storage map");
+ goto err;
+ }
+ map_fd = bpf_map__fd(map);
+
+ if (setup_cgroup_environment()) {
+ printf("Failed to load FILE_FILTER program\n");
+ goto err;
+ }
+
+ /* Create a cgroup, get fd, and join it */
+ cgroup_fd = create_and_get_cgroup(TEST_CGROUP);
+ if (!cgroup_fd) {
+ printf("Failed to create test cgroup\n");
+ goto err;
+ }
+
+ if (join_cgroup(TEST_CGROUP)) {
+ printf("Failed to join cgroup\n");
+ goto err;
+ }
+
+ /* few sanity checks before bpf prog is attached */
+ assert(system("cat /mnt/cgroup-test-work-dir" TEST_CGROUP "cgroup.procs >& /dev/null") == 0);
+ assert(system("cat /etc/hosts >& /dev/null") == 0);
+
+ /* Attach bpf program */
+ if (bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_FILE_OPEN, 0)) {
+ perror("Failed to attach CGROUP_FILE_OPEN program");
+ goto err;
+ }
+
+ if (bpf_map_get_next_key(map_fd, NULL, &key)) {
+ printf("Failed to get key in cgroup storage\n");
+ goto err;
+ }
+
+ if (bpf_prog_query(cgroup_fd, BPF_CGROUP_FILE_OPEN, 0, NULL, NULL,
+ &prog_cnt)) {
+ perror("Failed to query attached programs");
+ goto err;
+ }
+ assert(prog_cnt == 1);
+
+ /* check that this process cannot make any further changes to cgroup */
+ assert(system("cat /mnt/cgroup-test-work-dir" TEST_CGROUP "cgroup.procs >& /dev/null") != 0);
+
+ /* figure out the mnt_id of /etc */
+ if (name_to_handle_at(-1, "/etc", fhp, &cfg.mnt_id, 0) != -1 ||
+ errno != EOVERFLOW) {
+ perror("name_to_handle_at failed");
+ goto err;
+ }
+
+ /* let bpf prog know /etc's mnt_id via cgroup local storage */
+ if (bpf_map_update_elem(map_fd, &key, &cfg, 0)) {
+ printf("Failed to update cgroup local storage\n");
+ goto err;
+ }
+
+ /* check that this process cannot read /etc any more */
+ assert(system("cat /etc/hosts >& /dev/null") != 0);
+ assert(system("cat /etc/hostname >& /dev/null") != 0);
+
+ /* set mnt_id back to zero */
+ cfg.mnt_id = 0;
+ if (bpf_map_update_elem(map_fd, &key, &cfg, 0)) {
+ printf("Failed to update cgroup local storage\n");
+ goto err;
+ }
+ /* access to /etc should work again */
+ assert(system("cat /etc/hosts >& /dev/null") == 0);
+
+ /* figure out inode of /etc/hosts */
+ if (stat("/etc/hosts", &st)) {
+ perror("stat failed");
+ goto err;
+ }
+ cfg.inode = st.st_ino;
+ cfg.dev_major = MAJOR(st.st_dev);
+ cfg.dev_minor = MINOR(st.st_dev);
+ if (bpf_map_update_elem(map_fd, &key, &cfg, 0)) {
+ printf("Failed to update cgroup local storage\n");
+ goto err;
+ }
+ /* check that this process cannot read /etc/hosts any more */
+ assert(system("cat /etc/hosts >& /dev/null") != 0);
+ /* but /etc/hostname is still ok */
+ assert(system("cat /etc/hostname >& /dev/null") == 0);
+
+ /*
+ * detach from cgroup. Otherwise our own bpf prog will prevent us
+ * from cleaning up the cgroup environment
+ */
+ if (bpf_prog_detach(cgroup_fd, BPF_CGROUP_FILE_OPEN)) {
+ perror("Failed to detach");
+ goto err;
+ }
+
+ error = 0;
+ printf("test_file_open:PASS\n");
+
+err:
+ cleanup_cgroup_environment();
+
+out:
+ return error;
+}
diff --git a/tools/testing/selftests/bpf/test_file_open_common.h b/tools/testing/selftests/bpf/test_file_open_common.h
new file mode 100644
index 000000000000..d07b1f0ba28b
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_file_open_common.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018 Facebook */
+#ifndef __TEST_FILE_OPEN_COMMON_H
+#define __TEST_FILE_OPEN_COMMON_H
+
+struct test_file_open_config {
+ int mnt_id;
+ int dev_major;
+ int dev_minor;
+ int inode;
+};
+
+#endif
diff --git a/tools/testing/selftests/bpf/test_file_open_kern.c b/tools/testing/selftests/bpf/test_file_open_kern.c
new file mode 100644
index 000000000000..ea4b7a576b38
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_file_open_kern.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018 Facebook */
+#include <linux/bpf.h>
+#include <linux/magic.h>
+#include "bpf_helpers.h"
+#include "test_file_open_common.h"
+
+struct bpf_map_def SEC("maps") local_storage = {
+ .type = BPF_MAP_TYPE_CGROUP_STORAGE,
+ .key_size = sizeof(struct bpf_cgroup_storage_key),
+ .value_size = sizeof(struct test_file_open_config),
+};
+
+SEC("cgroup/file_open")
+int bpf_file_filter(struct bpf_file_info *f)
+{
+ char fmt1[] = "magic 0x%x mnt %d inode %ld\n";
+ char fmt2[] = "dev 0x%x link %d file %s\n";
+ char fmt3[] = "mode %o flags %o /etc mnt_id %d\n";
+ char path[400];
+ struct test_file_open_config *cfg;
+
+ cfg = bpf_get_local_storage(&local_storage, 0);
+
+ /* debugging prints */
+ bpf_get_file_path(f, path, sizeof(path));
+ bpf_trace_printk(fmt1, sizeof(fmt1), f->fs_magic, f->mnt_id, f->inode);
+ bpf_trace_printk(fmt2, sizeof(fmt2), (f->dev_major << 8) | f->dev_minor,
+ f->nlink, path);
+ bpf_trace_printk(fmt3, sizeof(fmt3), f->mode, f->flags, cfg->mnt_id);
+
+ /* disallow access to cgroupv2 */
+ if (f->fs_magic == CGROUP2_SUPER_MAGIC)
+ return 0;
+
+ /* disallow access to given mount */
+ if (f->mnt_id == cfg->mnt_id)
+ return 0;
+
+ /* disallow access to a given file */
+ if (f->dev_major == cfg->dev_major &&
+ f->dev_minor == cfg->dev_minor &&
+ f->inode == cfg->inode)
+ return 0;
+ return 1;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.17.1