bpf: Introduce bpf_sysctl_get_current_value helper
authorAndrey Ignatov <rdna@fb.com>
Fri, 1 Mar 2019 03:22:15 +0000 (19:22 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 12 Apr 2019 20:54:58 +0000 (13:54 -0700)
Add bpf_sysctl_get_current_value() helper to copy current sysctl value
into provided by BPF_PROG_TYPE_CGROUP_SYSCTL program buffer.

It provides same string as user space can see by reading corresponding
file in /proc/sys/, including new line, etc.

Documentation for the new helper is provided in bpf.h UAPI.

Since current value is kept in ctl_table->data in a parsed form,
ctl_table->proc_handler() with write=0 is called to read that data and
convert it to a string. Such a string can later be parsed by a program
using helpers that will be introduced separately.

Unfortunately it's not trivial to provide API to access parsed data due to
variety of data representations (string, intvec, uintvec, ulongvec,
custom structures, even NULL, etc). Instead it's assumed that user know
how to handle specific sysctl they're interested in and appropriate
helpers can be used.

Since ctl_table->proc_handler() expects __user buffer, conversion to
__user happens for kernel allocated one where the value is stored.

Signed-off-by: Andrey Ignatov <rdna@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
include/linux/filter.h
include/uapi/linux/bpf.h
kernel/bpf/cgroup.c

index a17732057880d47a91b35ca40cdce388616904fb..f254ff92819f4d53cc0cf44427c9f26b69f20250 100644 (file)
@@ -1182,6 +1182,8 @@ struct bpf_sock_ops_kern {
 struct bpf_sysctl_kern {
        struct ctl_table_header *head;
        struct ctl_table *table;
+       void *cur_val;
+       size_t cur_len;
        int write;
 };
 
index 9c8a2f3ccb9ba7a05ea943cfbf1b1f836c55b26c..063543afa359e2bf47c3f9090937f5cb603bd1b1 100644 (file)
@@ -2522,6 +2522,25 @@ union bpf_attr {
  *
  *             **-E2BIG** if the buffer wasn't big enough (*buf* will contain
  *             truncated name in this case).
+ *
+ * int bpf_sysctl_get_current_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len)
+ *     Description
+ *             Get current value of sysctl as it is presented in /proc/sys
+ *             (incl. newline, etc), and copy it as a string into provided
+ *             by program buffer *buf* of size *buf_len*.
+ *
+ *             The whole value is copied, no matter what file position user
+ *             space issued e.g. sys_read at.
+ *
+ *             The buffer is always NUL terminated, unless it's zero-sized.
+ *     Return
+ *             Number of character copied (not including the trailing NUL).
+ *
+ *             **-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *             truncated name in this case).
+ *
+ *             **-EINVAL** if current value was unavailable, e.g. because
+ *             sysctl is uninitialized and read returns -EIO for it.
  */
 #define __BPF_FUNC_MAPPER(FN)          \
        FN(unspec),                     \
@@ -2625,7 +2644,8 @@ union bpf_attr {
        FN(get_listener_sock),          \
        FN(skc_lookup_tcp),             \
        FN(tcp_check_syncookie),        \
-       FN(sysctl_get_name),
+       FN(sysctl_get_name),            \
+       FN(sysctl_get_current_value),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
index a6838704324499be4060112ed5182d7ad3127cad..c6b2cf29a54b115f48887bb11a953ace5052c330 100644 (file)
@@ -794,15 +794,37 @@ int __cgroup_bpf_run_filter_sysctl(struct ctl_table_header *head,
                .head = head,
                .table = table,
                .write = write,
+               .cur_val = NULL,
+               .cur_len = PAGE_SIZE,
        };
        struct cgroup *cgrp;
        int ret;
 
+       ctx.cur_val = kmalloc_track_caller(ctx.cur_len, GFP_KERNEL);
+       if (ctx.cur_val) {
+               mm_segment_t old_fs;
+               loff_t pos = 0;
+
+               old_fs = get_fs();
+               set_fs(KERNEL_DS);
+               if (table->proc_handler(table, 0, (void __user *)ctx.cur_val,
+                                       &ctx.cur_len, &pos)) {
+                       /* Let BPF program decide how to proceed. */
+                       ctx.cur_len = 0;
+               }
+               set_fs(old_fs);
+       } else {
+               /* Let BPF program decide how to proceed. */
+               ctx.cur_len = 0;
+       }
+
        rcu_read_lock();
        cgrp = task_dfl_cgroup(current);
        ret = BPF_PROG_RUN_ARRAY(cgrp->bpf.effective[type], &ctx, BPF_PROG_RUN);
        rcu_read_unlock();
 
+       kfree(ctx.cur_val);
+
        return ret == 1 ? 0 : -EPERM;
 }
 EXPORT_SYMBOL(__cgroup_bpf_run_filter_sysctl);
@@ -869,12 +891,55 @@ static const struct bpf_func_proto bpf_sysctl_get_name_proto = {
        .arg4_type      = ARG_ANYTHING,
 };
 
+static int copy_sysctl_value(char *dst, size_t dst_len, char *src,
+                            size_t src_len)
+{
+       if (!dst)
+               return -EINVAL;
+
+       if (!dst_len)
+               return -E2BIG;
+
+       if (!src || !src_len) {
+               memset(dst, 0, dst_len);
+               return -EINVAL;
+       }
+
+       memcpy(dst, src, min(dst_len, src_len));
+
+       if (dst_len > src_len) {
+               memset(dst + src_len, '\0', dst_len - src_len);
+               return src_len;
+       }
+
+       dst[dst_len - 1] = '\0';
+
+       return -E2BIG;
+}
+
+BPF_CALL_3(bpf_sysctl_get_current_value, struct bpf_sysctl_kern *, ctx,
+          char *, buf, size_t, buf_len)
+{
+       return copy_sysctl_value(buf, buf_len, ctx->cur_val, ctx->cur_len);
+}
+
+static const struct bpf_func_proto bpf_sysctl_get_current_value_proto = {
+       .func           = bpf_sysctl_get_current_value,
+       .gpl_only       = false,
+       .ret_type       = RET_INTEGER,
+       .arg1_type      = ARG_PTR_TO_CTX,
+       .arg2_type      = ARG_PTR_TO_UNINIT_MEM,
+       .arg3_type      = ARG_CONST_SIZE,
+};
+
 static const struct bpf_func_proto *
 sysctl_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 {
        switch (func_id) {
        case BPF_FUNC_sysctl_get_name:
                return &bpf_sysctl_get_name_proto;
+       case BPF_FUNC_sysctl_get_current_value:
+               return &bpf_sysctl_get_current_value_proto;
        default:
                return cgroup_base_func_proto(func_id, prog);
        }