bpf: Sysctl hook
Subsystems (10)
v2 cover bpf: Sysctl hook
v1->v2:
- add fs/proc/proc_sysctl.c mainteners to Cc:.
The patch set introduces new BPF hook for sysctl.
It adds new program type BPF_PROG_TYPE_CGROUP_SYSCTL and attach type BPF_CGROUP_SYSCTL.
BPF_CGROUP_SYSCTL hook is placed before calling to sysctl's proc_handler so that accesses (read/write) to sysctl can be controlled for specific cgroup and either allowed or denied, or traced.
The hook has access to sysctl name, current sysctl value and (on write only) to new sysctl value via corresponding helpers. New sysctl value can be overridden by program. Both name and values (current/new) are represented as strings same way they're visible in /proc/sys/. It is up to program to parse these strings.
To help with parsing the most common kind of sysctl value, vector of integers, two new helpers are provided: bpf_strtol and bpf_strtoul with semantic similar to user space strtol(3) and strtoul(3).
The hook also provides bpf_sysctl context with two fields:
* @write indicates whether sysctl is being read (= 0) or written (= 1);
* @file_pos is sysctl file position to read from or write to, can be overridden.
The hook allows to make better isolation for containerized applications that are run as root so that one container can't change a sysctl and affect all other containers on a host, make changes to allowed sysctl in a safer way and simplify sysctl tracing for cgroups.
Patch 1 is preliminary refactoring.
Patch 2 adds new program and attach types.
Patches 3-5 implement helpers to access sysctl name and value. Patch 6 adds file_pos field to bpf_sysctl context.
Patch 7 updates UAPI in tools.
Patches 8-9 add support for the new hook to libbpf and corresponding test. Patches 10-14 add selftests for the new hook.
Patch 15 adds support for new arg types to verifier: pointer to integer. Patch 16 adds bpf_strto{l,ul} helpers to parse integers from sysctl value. Patch 17 updates UAPI in tools.
Patch 18 updates bpf_helpers.h.
Patch 19 adds selftests for pointer to integer in verifier. Patches 20-21 add selftests for bpf_strto{l,ul}, including integration
C based test for sysctl value parsing.
Andrey Ignatov (21):
bpf: Add base proto function for cgroup-bpf programs
bpf: Sysctl hook
bpf: Introduce bpf_sysctl_get_name helper
bpf: Introduce bpf_sysctl_get_current_value helper
bpf: Introduce bpf_sysctl_{get,set}_new_value helpers
bpf: Add file_pos field to bpf_sysctl ctx
bpf: Sync bpf.h to tools/
libbpf: Support sysctl hook
selftests/bpf: Test sysctl section name selftests/bpf: Test BPF_CGROUP_SYSCTL selftests/bpf: Test bpf_sysctl_get_name helper selftests/bpf: Test sysctl_get_current_value helper selftests/bpf: Test bpf_sysctl_{get,set}_new_value helpers selftests/bpf: Test file_pos field in bpf_sysctl ctx
bpf: Introduce ARG_PTR_TO_{INT,LONG} arg types
bpf: Introduce bpf_strtol and bpf_strtoul helpers
bpf: Sync bpf.h to tools/
selftests/bpf: Add sysctl and strtoX helpers to bpf_helpers.h selftests/bpf: Test ARG_PTR_TO_LONG arg type selftests/bpf: Test bpf_strtol and bpf_strtoul helpers selftests/bpf: C based test for sysctl and strtoX
fs/proc/proc_sysctl.c | 25 +- include/linux/bpf-cgroup.h | 21 + include/linux/bpf.h | 4 + include/linux/bpf_types.h | 1 + include/linux/filter.h | 16 + include/uapi/linux/bpf.h | 139 +- kernel/bpf/cgroup.c | 364 +++- kernel/bpf/helpers.c | 131 ++ kernel/bpf/syscall.c | 7 + kernel/bpf/verifier.c | 30 + tools/include/uapi/linux/bpf.h | 139 +- tools/lib/bpf/libbpf.c | 3 + tools/lib/bpf/libbpf_probes.c | 1 + tools/testing/selftests/bpf/Makefile | 3 +- tools/testing/selftests/bpf/bpf_helpers.h | 19 + .../selftests/bpf/progs/test_sysctl_prog.c | 89 + .../selftests/bpf/test_section_names.c | 5 + tools/testing/selftests/bpf/test_sysctl.c | 1567 +++++++++++++++++ .../testing/selftests/bpf/verifier/int_ptr.c | 160 ++ 19 files changed, 2716 insertions(+), 8 deletions(-) create mode 100644 tools/testing/selftests/bpf/progs/test_sysctl_prog.c create mode 100644 tools/testing/selftests/bpf/test_sysctl.c create mode 100644 tools/testing/selftests/bpf/verifier/int_ptr.c
Signature · 1 line
2.17.1
PATCH v2 1/21 bpf: Add base proto function for cgroup-bpf programs
Currently kernel/bpf/cgroup.c contains only one program type and one proto function cgroup_dev_func_proto(). It'd be useful to have base proto function that can be reused for new cgroup-bpf program types coming soon.
Introduce cgroup_base_func_proto().
--- kernel/bpf/cgroup.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
kernel/bpf/cgroup.c +7 -1
@@ -701,7 +701,7 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,EXPORT_SYMBOL (__cgroup_bpf_check_dev_permission);static const struct bpf_func_proto * -cgroup_dev_func_proto (enum bpf_func_id func_id,const struct bpf_prog *prog) +cgroup_base_func_proto (enum bpf_func_id func_id,const struct bpf_prog *prog) {switch (func_id) {case BPF_FUNC_map_lookup_elem:
@@ -725,6 +725,12 @@ cgroup_dev_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) } } +static const struct bpf_func_proto * +cgroup_dev_func_proto (enum bpf_func_id func_id,const struct bpf_prog *prog) +{ +return cgroup_base_func_proto (func_id, prog); +} + static bool cgroup_dev_is_valid_access(int off, int size, enum bpf_access_type type, const struct bpf_prog *prog,
Signature · 1 line
2.17.1
PATCH v2 2/21 bpf: Sysctl hook
In reply to Andrey IgnatovContainerized applications may run as root and it may create problems for whole host. Specifically such applications may change a sysctl and affect applications in other containers.
Furthermore in existing infrastructure it may not be possible to just completely disable writing to sysctl, instead such a process should be gradual with ability to log what sysctl are being changed by a container, investigate, limit the set of writable sysctl to currently used ones (so that new ones can not be changed) and eventually reduce this set to zero.
The patch introduces new program type BPF_PROG_TYPE_CGROUP_SYSCTL and attach type BPF_CGROUP_SYSCTL to solve these problems on cgroup basis.
New program type has access to following minimal context:
struct bpf_sysctl {
__u32 write;
};Where @write indicates whether sysctl is being read (= 0) or written (= 1).
Helpers to access sysctl name and value will be introduced separately.
BPF_CGROUP_SYSCTL attach point is added to sysctl code right before passing control to ctl_table->proc_handler so that BPF program can either allow or deny access to sysctl.
--- fs/proc/proc_sysctl.c | 5 +++ include/linux/bpf-cgroup.h | 18 ++++++++ include/linux/bpf_types.h | 1 + include/linux/filter.h | 8 ++++ include/uapi/linux/bpf.h | 9 ++++ kernel/bpf/cgroup.c | 92 ++++++++++++++++++++++++++++++++++++++ kernel/bpf/syscall.c | 7 +++ kernel/bpf/verifier.c | 1 + 8 files changed, 141 insertions(+)
fs/proc/proc_sysctl.c +5 -0
@@ -13,6 +13,7 @@#include <linux/namei.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/bpf-cgroup.h> #include "internal.h" static const struct dentry_operations proc_sys_dentry_operations;
@@ -588,6 +589,10 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,if (!table->proc_handler) goto out; + error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write); +if (error) + goto out; +/* careful: calling conventions are nasty here */ res = count; error = table->proc_handler (table, write, buf, &res, ppos);
include/linux/bpf-cgroup.h +18 -0
@@ -17,6 +17,8 @@ struct bpf_map;struct bpf_prog ;struct bpf_sock_ops_kern ;struct bpf_cgroup_storage ; +struct ctl_table ; +struct ctl_table_header ;#ifdef CONFIG_CGROUP_BPF
@@ -109,6 +111,10 @@ int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,int __cgroup_bpf_check_dev_permission (short dev_type,u32 major,u32 minor,short access,enum bpf_attach_type type); +int __cgroup_bpf_run_filter_sysctl (struct ctl_table_header *head, +struct ctl_table *table,int write, +enum bpf_attach_type type); +static inline enum bpf_cgroup_storage_type cgroup_storage_type (struct bpf_map *map) {
@@ -253,6 +259,17 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key, \ __ret; \ }) + + +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) \ +({ \ + int __ret = 0; \ + if (cgroup_bpf_enabled) \ + __ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \ + BPF_CGROUP_SYSCTL); \ + __ret; \ +}) +int cgroup_bpf_prog_attach (const union bpf_attr *attr,enum bpf_prog_type ptype,struct bpf_prog *prog); int cgroup_bpf_prog_detach(const union bpf_attr *attr,
@@ -321,6 +338,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK (sk, uaddr, t_ctx) ({ 0; })#define BPF_CGROUP_RUN_PROG_SOCK_OPS (sock_ops) ({ 0; })#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP (type,major,minor,access) ({ 0; }) +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) ({ 0; })#define for_each_cgroup_storage_type (stype) for (; false; )
include/linux/bpf_types.h +1 -0
@@ -28,6 +28,7 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_RAW_TRACEPOINT, raw_tracepoint)#endif #ifdef CONFIG_CGROUP_BPF BPF_PROG_TYPE(BPF_PROG_TYPE_CGROUP_DEVICE, cg_dev) +BPF_PROG_TYPE(BPF_PROG_TYPE_CGROUP_SYSCTL, cg_sysctl)#endif #ifdef CONFIG_BPF_LIRC_MODE2 BPF_PROG_TYPE (BPF_PROG_TYPE_LIRC_MODE2 , lirc_mode2)
include/linux/filter.h +8 -0
@@ -33,6 +33,8 @@ struct bpf_prog_aux;struct xdp_rxq_info ;struct xdp_buff ;struct sock_reuseport ; +struct ctl_table ; +struct ctl_table_header ;/* ArgX, context and stack frame pointer register positions. Note, * Arg1, Arg2, Arg3, etc are used as argument mappings of function
@@ -1177,4 +1179,10 @@ struct bpf_sock_ops_kern { */ }; +struct bpf_sysctl_kern { +struct ctl_table_header *head; +struct ctl_table *table; +int write; +}; +#endif /* __LINUX_FILTER_H__ */
include/uapi/linux/bpf.h +9 -0
@@ -166,6 +166,7 @@ enum bpf_prog_type {BPF_PROG_TYPE_LIRC_MODE2 ,BPF_PROG_TYPE_SK_REUSEPORT ,BPF_PROG_TYPE_FLOW_DISSECTOR , + BPF_PROG_TYPE_CGROUP_SYSCTL, }; enum bpf_attach_type {
@@ -187,6 +188,7 @@ enum bpf_attach_type {BPF_CGROUP_UDP6_SENDMSG ,BPF_LIRC_MODE2 ,BPF_FLOW_DISSECTOR , +BPF_CGROUP_SYSCTL , __MAX_BPF_ATTACH_TYPE };
@@ -3275,4 +3277,11 @@ struct bpf_line_info {struct bpf_spin_lock {__u32 val; }; + +struct bpf_sysctl { +__u32 write;/* Sysctl is being read (= 0) or written (= 1). +* Allows 1,2,4-byte read, but no write. +*/ +}; +#endif /* _UAPI__LINUX_BPF_H__ */
kernel/bpf/cgroup.c +92 -0
102 lines not on this page · raw message
kernel/bpf/syscall.c +7 -0
@@ -1819,6 +1819,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)case BPF_FLOW_DISSECTOR : ptype =BPF_PROG_TYPE_FLOW_DISSECTOR ;break ; +case BPF_CGROUP_SYSCTL : + ptype =BPF_PROG_TYPE_CGROUP_SYSCTL ; +break ;default :return -EINVAL ; }
@@ -1897,6 +1900,9 @@ static int bpf_prog_detach(const union bpf_attr *attr)return lirc_prog_detach (attr);case BPF_FLOW_DISSECTOR :return skb_flow_dissector_bpf_prog_detach (attr); +case BPF_CGROUP_SYSCTL : + ptype =BPF_PROG_TYPE_CGROUP_SYSCTL ; +break ;default :return -EINVAL ; }
@@ -1930,6 +1936,7 @@ static int bpf_prog_query(const union bpf_attr *attr,case BPF_CGROUP_UDP6_SENDMSG :case BPF_CGROUP_SOCK_OPS :case BPF_CGROUP_DEVICE : +case BPF_CGROUP_SYSCTL :break ;case BPF_LIRC_MODE2 :return lirc_prog_query (attr, uattr);
kernel/bpf/verifier.c +1 -0
@@ -5127,6 +5127,7 @@ static int check_return_code(struct bpf_verifier_env *env)case BPF_PROG_TYPE_CGROUP_SOCK_ADDR :case BPF_PROG_TYPE_SOCK_OPS :case BPF_PROG_TYPE_CGROUP_DEVICE : +case BPF_PROG_TYPE_CGROUP_SYSCTL :break ;default :return 0 ;
Signature · 1 line
2.17.1
PATCH v2 3/21 bpf: Introduce bpf_sysctl_get_name helper
In reply to Andrey IgnatovAdd bpf_sysctl_get_name() helper to copy sysctl name (/proc/sys/ entry) into provided by BPF_PROG_TYPE_CGROUP_SYSCTL program buffer.
By default full name (w/o /proc/sys/) is copied, e.g. "net/ipv4/tcp_mem".
If BPF_F_SYSCTL_BASE_NAME flag is set, only base name will be copied, e.g. "tcp_mem".
Documentation for the new helper is provided in bpf.h UAPI.
--- include/uapi/linux/bpf.h | 22 ++++++++++++- kernel/bpf/cgroup.c | 70 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-)
include/uapi/linux/bpf.h +21 -1
@@ -2479,6 +2479,22 @@ union bpf_attr {* Return * 0 if iph and th are a valid SYN cookie ACK, or a negative error * otherwise. +* +* int bpf_sysctl_get_name(struct bpf_sysctl *ctx, char *buf, size_t buf_len, u64 flags) +* Description +* Get name of sysctl in /proc/sys/ and copy it into provided by +* program buffer *buf* of size *buf_len*. +* +* The buffer is always NUL terminated, unless it's zero-sized. +* +* If *flags* is zero, full name (e.g. "net/ipv4/tcp_mem") is +* copied. Use **BPF_F_SYSCTL_BASE_NAME** flag to copy base name +* only (e.g. "tcp_mem"). +* 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). */ #define __BPF_FUNC_MAPPER (FN ) \ FN(unspec), \
@@ -2581,7 +2597,8 @@ union bpf_attr {FN (skb_ecn_set_ce), \FN (get_listener_sock), \FN (skc_lookup_tcp), \ -FN (tcp_check_syncookie), +FN (tcp_check_syncookie), \ +FN (sysctl_get_name),/* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call
@@ -2648,6 +2665,9 @@ enum bpf_func_id {#define BPF_F_ADJ_ROOM_ENCAP_L4_GRE (1ULL << 3)#define BPF_F_ADJ_ROOM_ENCAP_L4_UDP (1ULL << 4) +/* BPF_FUNC_sysctl_get_name flags. */ +#define BPF_F_SYSCTL_BASE_NAME (1ULL << 0) +/* Mode for BPF_FUNC_skb_adjust_room helper. */ enum bpf_adj_room_mode {BPF_ADJ_ROOM_NET ,
kernel/bpf/cgroup.c +69 -1
85 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v2 4/21 bpf: Introduce bpf_sysctl_get_current_value helper
In reply to Andrey IgnatovAdd 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.
--- include/linux/filter.h | 2 ++ include/uapi/linux/bpf.h | 22 +++++++++++++- kernel/bpf/cgroup.c | 65 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-)
include/linux/filter.h +2 -0
@@ -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; };
include/uapi/linux/bpf.h +21 -1
@@ -2495,6 +2495,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), \
@@ -2598,7 +2617,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
kernel/bpf/cgroup.c +65 -0
92 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v2 5/21 bpf: Introduce bpf_sysctl_{get,set}_new_value helpers
In reply to Andrey IgnatovAdd helpers to work with new value being written to sysctl by user space.
bpf_sysctl_get_new_value() copies value being written to sysctl into provided buffer.
bpf_sysctl_set_new_value() overrides new value being written by user space with a one from provided buffer. Buffer should contain string representation of the value, similar to what can be seen in /proc/sys/.
Both helpers can be used only on sysctl write.
File position matters and can be managed by an interface that will be introduced separately. E.g. if user space calls sys_write to a file in /proc/sys/ at file position = X, where X > 0, then the value set by bpf_sysctl_set_new_value() will be written starting from X. If program wants to override whole value with specified buffer, file position has to be set to zero.
Documentation for the new helpers is provided in bpf.h UAPI.
--- fs/proc/proc_sysctl.c | 22 ++++++++--- include/linux/bpf-cgroup.h | 8 ++-- include/linux/filter.h | 3 ++ include/uapi/linux/bpf.h | 38 +++++++++++++++++- kernel/bpf/cgroup.c | 81 +++++++++++++++++++++++++++++++++++++- 5 files changed, 142 insertions(+), 10 deletions(-)
fs/proc/proc_sysctl.c +17 -5
@@ -570,8 +570,8 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,struct inode *inode =file_inode (filp);struct ctl_table_header *head =grab_header (inode);struct ctl_table *table =PROC_I (inode)->sysctl_entry; +void *new_buf =NULL ;ssize_t error; -size_t res;if (IS_ERR (head))return PTR_ERR (head);
@@ -589,15 +589,27 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,if (!table->proc_handler) goto out; - error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write); + error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, &count, + &new_buf);if (error) goto out;/* careful: calling conventions are nasty here */ - res = count; - error = table->proc_handler (table, write, buf, &res, ppos); +if (new_buf) { +mm_segment_t old_fs; + + old_fs =get_fs (); +set_fs (KERNEL_DS ); + error = table->proc_handler (table, write, (void __user *)new_buf, + &count, ppos); +set_fs (old_fs); +kfree (new_buf); + }else { + error = table->proc_handler (table, write, buf, &count, ppos); + } +if (!error) - error = res; + error = count; out:sysctl_head_finish (head);
include/linux/bpf-cgroup.h +5 -3
@@ -113,7 +113,8 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,int __cgroup_bpf_run_filter_sysctl (struct ctl_table_header *head,struct ctl_table *table,int write, -enum bpf_attach_type type); +void __user *buf,size_t *pcount, +void **new_buf,enum bpf_attach_type type);static inline enum bpf_cgroup_storage_type cgroup_storage_type (struct bpf_map *map)
@@ -261,11 +262,12 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key, }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) \ +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, count, nbuf) \ ({ \ int __ret = 0; \ if (cgroup_bpf_enabled) \ __ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \ + buf, count, nbuf, \ BPF_CGROUP_SYSCTL); \ __ret; \ })
@@ -338,7 +340,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK (sk, uaddr, t_ctx) ({ 0; })#define BPF_CGROUP_RUN_PROG_SOCK_OPS (sock_ops) ({ 0; })#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP (type,major,minor,access) ({ 0; }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) ({ 0; }) +#define BPF_CGROUP_RUN_PROG_SYSCTL (head,table,write,buf,count,nbuf) ({ 0; })#define for_each_cgroup_storage_type (stype) for (; false; )
include/linux/filter.h +3 -0
@@ -1184,6 +1184,9 @@ struct bpf_sysctl_kern {struct ctl_table *table;void *cur_val;size_t cur_len; +void *new_val; +size_t new_len; +int new_updated;int write; };
include/uapi/linux/bpf.h +37 -1
@@ -2514,6 +2514,40 @@ union bpf_attr {* * **-EINVAL** if current value was unavailable, e.g. because * sysctl is uninitialized and read returns -EIO for it. +* +* int bpf_sysctl_get_new_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len) +* Description +* Get new value being written by user space to sysctl (before +* the actual write happens) and copy it as a string into +* provided by program buffer *buf* of size *buf_len*. +* +* User space may write new value at file position > 0. +* +* 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 sysctl is being read. +* +* int bpf_sysctl_set_new_value(struct bpf_sysctl *ctx, const char *buf, size_t buf_len) +* Description +* Override new value being written by user space to sysctl with +* value provided by program in buffer *buf* of size *buf_len*. +* +* *buf* should contain a string in same form as provided by user +* space on sysctl write. +* +* User space may write new value at file position > 0. To override +* the whole sysctl value file position should be set to zero. +* Return +* 0 on success. +* +* **-E2BIG** if the *buf_len* is too big. +* +* **-EINVAL** if sysctl is being read. */ #define __BPF_FUNC_MAPPER (FN ) \ FN(unspec), \
@@ -2618,7 +2652,9 @@ union bpf_attr {FN (skc_lookup_tcp), \FN (tcp_check_syncookie), \FN (sysctl_get_name), \ -FN (sysctl_get_current_value), +FN (sysctl_get_current_value), \ +FN (sysctl_get_new_value), \ +FN (sysctl_set_new_value),/* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call
kernel/bpf/cgroup.c +80 -1
123 lines not on this page · raw message
Signature · 1 line
2.17.1
Daniel Borkmann
maintainerRe: [PATCH v2 bpf-next 05/21] bpf: Introduce bpf_sysctl_{get,set}_new_value helpers
Quoted 55 lines · Andrey Ignatov
Add helpers to work with new value being written to sysctl by user space.
bpf_sysctl_get_new_value()copies value being written to sysctl into provided buffer.
bpf_sysctl_set_new_value()overrides new value being written by user space with a one from provided buffer. Buffer should contain string representation of the value, similar to what can be seen in/proc/sys/.Both helpers can be used only on sysctl write.
File position matters and can be managed by an interface that will be introduced separately. E.g. if user space calls
sys_writeto a file in/proc/sys/at file position = X, where X > 0, then the value set bybpf_sysctl_set_new_value()will be written starting from X. If program wants to override whole value with specified buffer, file position has to be set to zero.Documentation for the new helpers is provided in
bpf.hUAPI.Signed-off-by Andrey Ignatov <[email protected]>--- fs/proc/proc_sysctl.c | 22 ++++++++--- include/linux/bpf-cgroup.h | 8 ++-- include/linux/filter.h | 3 ++ include/uapi/linux/bpf.h | 38 +++++++++++++++++- kernel/bpf/cgroup.c | 81 +++++++++++++++++++++++++++++++++++++- 5 files changed, 142 insertions(+), 10 deletions(-)fs/proc/proc_sysctl.c +3
-2@@ -570,8 +570,8 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,struct inode *inode =file_inode (filp);struct ctl_table_header *head =grab_header (inode);struct ctl_table *table =PROC_I (inode)->sysctl_entry; +void *new_buf =NULL ;ssize_t error; -size_t res;if (IS_ERR (head))return PTR_ERR (head);@@ -589,15 +589,27 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,if (!table->proc_handler) goto out; - error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write); + error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, &count, + &new_buf);if (error) goto out;/* careful: calling conventions are nasty here */
fs/proc/proc_sysctl.c static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, · line 605
Quoted 10 lines
fs/proc/proc_sysctl.c +8
-2- res = count; - error = table->proc_handler (table, write, buf, &res, ppos); +if (new_buf) { +mm_segment_t old_fs; + + old_fs =get_fs (); +set_fs (KERNEL_DS ); + error = table->proc_handler (table, write, (void __user *)new_buf, + &count, ppos); +set_fs (old_fs);
From quick glance on the set, the above stood out. Afaik, there is an ongoing effort by Al and other fs/core folks (as visible in the git log) to get rid of set_fs() calls in the tree with the goal of eliminating this interface /entirely/ (more context on 'why' here: https://lwn.net/Articles/722267/). Is there a better way to achieve the above w/o needing it?
Quoted 48 lines
fs/proc/proc_sysctl.c +6
-1+kfree (new_buf); + }else { + error = table->proc_handler (table, write, buf, &count, ppos); + } +if (!error) - error = res; + error = count; out:sysctl_head_finish (head);include/linux/bpf-cgroup.h +5
-3@@ -113,7 +113,8 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,int __cgroup_bpf_run_filter_sysctl (struct ctl_table_header *head,struct ctl_table *table,int write, -enum bpf_attach_type type); +void __user *buf,size_t *pcount, +void **new_buf,enum bpf_attach_type type);static inline enum bpf_cgroup_storage_type cgroup_storage_type (struct bpf_map *map)@@ -261,11 +262,12 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key, }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) \ +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, count, nbuf) \ ({ \ int __ret = 0; \ if (cgroup_bpf_enabled) \ __ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \ + buf, count, nbuf, \ BPF_CGROUP_SYSCTL); \ __ret; \ })@@ -338,7 +340,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK (sk, uaddr, t_ctx) ({ 0; })#define BPF_CGROUP_RUN_PROG_SOCK_OPS (sock_ops) ({ 0; })#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP (type,major,minor,access) ({ 0; }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) ({ 0; }) +#define BPF_CGROUP_RUN_PROG_SYSCTL (head,table,write,buf,count,nbuf) ({ 0; })#define for_each_cgroup_storage_type (stype) for (; false; )
Re: [PATCH v2 bpf-next 05/21] bpf: Introduce bpf_sysctl_{get,set}_new_value helpers
Daniel Borkmann <[email protected]> [Thu, 2019-04-04 07:38 -0700]:
Quoted 64 lines · Andrey Ignatov
Quoted 63 lines
Add helpers to work with new value being written to sysctl by user space.
bpf_sysctl_get_new_value()copies value being written to sysctl into provided buffer.
bpf_sysctl_set_new_value()overrides new value being written by user space with a one from provided buffer. Buffer should contain string representation of the value, similar to what can be seen in/proc/sys/.Both helpers can be used only on sysctl write.
File position matters and can be managed by an interface that will be introduced separately. E.g. if user space calls
sys_writeto a file in/proc/sys/at file position = X, where X > 0, then the value set bybpf_sysctl_set_new_value()will be written starting from X. If program wants to override whole value with specified buffer, file position has to be set to zero.Documentation for the new helpers is provided in
bpf.hUAPI.Signed-off-by Andrey Ignatov <[email protected]>--- fs/proc/proc_sysctl.c | 22 ++++++++--- include/linux/bpf-cgroup.h | 8 ++-- include/linux/filter.h | 3 ++ include/uapi/linux/bpf.h | 38 +++++++++++++++++- kernel/bpf/cgroup.c | 81 +++++++++++++++++++++++++++++++++++++- 5 files changed, 142 insertions(+), 10 deletions(-)fs/proc/proc_sysctl.c +9
-4@@ -570,8 +570,8 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,struct inode *inode =file_inode (filp);struct ctl_table_header *head =grab_header (inode);struct ctl_table *table =PROC_I (inode)->sysctl_entry; +void *new_buf =NULL ;ssize_t error; -size_t res;if (IS_ERR (head))return PTR_ERR (head);@@ -589,15 +589,27 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,if (!table->proc_handler) goto out; - error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write); + error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, &count, + &new_buf);if (error) goto out;/* careful: calling conventions are nasty here */ - res = count; - error = table->proc_handler (table, write, buf, &res, ppos); +if (new_buf) { +mm_segment_t old_fs; + + old_fs =get_fs (); +set_fs (KERNEL_DS ); + error = table->proc_handler(table, write, (void __user *)new_buf,
Quoted 8 lines
fs/proc/proc_sysctl.c static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, · line 605
Quoted 2 lines
fs/proc/proc_sysctl.c +2
-0+ &count, ppos); +set_fs (old_fs);From quick glance on the set, the above stood out. Afaik, there is an ongoing
effort by Al and other fs/core folks (as visible in the git log) to get rid ofset_fs()calls in the tree with the goal of eliminating this interface /entirely/
(more context on 'why' here: https://urldefense.proofpoint.com/v2/url?u=https-3A__lwn.net_Articles_722267_&d=DwICaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=3jAokpHyGuCuJ834j-tttQ&m=fmn6jd1czDvp5a6GeSw0zLMxU3VRcgm1ohqwAPOKf38&s=AfbvJ91arUzm328cKzcHXeeb104boAx8NJjsoIU6Lbk&e=). Is there a better way to achieve the above w/o needing it?
That's a good question. I've spent quite a lot of time looking for a better way and the only one I'm aware of so far is to change proc_handler signature, so that it accepts kernel 'buffer', and copying between user and kernel happens outside of proc_handler.
But it would require changing all proc_handler implementations as well so that they accept kernel 'buffer' and don't copy data from/to user by themselves and there are just too many sysctl proc_handler implementations:
% git grep -E '\.proc_handler\s+=\s+' | \ sed -Ee 's/^.*\.proc_handler\s+=\s+//' | sort -u | wc -l 179 % git grep -lE '\.proc_handler\s+=\s+' | wc -l 103
, i.e. it's huge refactoring that can be really hard to upstream.
Also I looked at the LWN article you mentioned and found this branch that cleans up set_fs use cases:
http://git.infradead.org/users/hch/vfs.git/shortlog/refs/heads/setfs-elimination
I see it uses either similar approach, i.e. introduce separate (or use available) function that accepts kernel buffer, then copy data from user and pass it to such a function. Another approach, I see in the branch, is to use iovec iterators (that's mentioned in the LWN article as well), but that again would require changing all proc_handler implementations.
That's being said I don't know a better way to do this w/o huge refactoring.
Quoted 48 lines
Quoted 48 lines
fs/proc/proc_sysctl.c +6
-1+kfree (new_buf); + }else { + error = table->proc_handler (table, write, buf, &count, ppos); + } +if (!error) - error = res; + error = count; out:sysctl_head_finish (head);include/linux/bpf-cgroup.h +5
-3@@ -113,7 +113,8 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,int __cgroup_bpf_run_filter_sysctl (struct ctl_table_header *head,struct ctl_table *table,int write, -enum bpf_attach_type type); +void __user *buf,size_t *pcount, +void **new_buf,enum bpf_attach_type type);static inline enum bpf_cgroup_storage_type cgroup_storage_type (struct bpf_map *map)@@ -261,11 +262,12 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key, }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) \ +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, count, nbuf) \ ({ \ int __ret = 0; \ if (cgroup_bpf_enabled) \ __ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \ + buf, count, nbuf, \ BPF_CGROUP_SYSCTL); \ __ret; \ })@@ -338,7 +340,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK (sk, uaddr, t_ctx) ({ 0; })#define BPF_CGROUP_RUN_PROG_SOCK_OPS (sock_ops) ({ 0; })#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP (type,major,minor,access) ({ 0; }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write) ({ 0; }) +#define BPF_CGROUP_RUN_PROG_SYSCTL (head,table,write,buf,count,nbuf) ({ 0; })#define for_each_cgroup_storage_type (stype) for (; false; )
Signature · 1 line
Andrey Ignatov
PATCH v2 6/21 bpf: Add file_pos field to bpf_sysctl ctx
In reply to Andrey IgnatovAdd file_pos field to bpf_sysctl context to read and write sysctl file position at which sysctl is being accessed (read or written).
The field can be used to e.g. override whole sysctl value on write to sysctl even when sys_write is called by user space with file_pos > 0. Or BPF program may reject such accesses.
--- fs/proc/proc_sysctl.c | 2 +- include/linux/bpf-cgroup.h | 9 ++++--- include/linux/filter.h | 3 +++ include/uapi/linux/bpf.h | 3 +++ kernel/bpf/cgroup.c | 54 +++++++++++++++++++++++++++++++++++--- 5 files changed, 63 insertions(+), 8 deletions(-)
fs/proc/proc_sysctl.c +1 -1
@@ -590,7 +590,7 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, goto out; error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, &count, - &new_buf); + ppos, &new_buf);if (error) goto out;
include/linux/bpf-cgroup.h +5 -4
@@ -114,7 +114,8 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,int __cgroup_bpf_run_filter_sysctl (struct ctl_table_header *head,struct ctl_table *table,int write,void __user *buf,size_t *pcount, -void **new_buf,enum bpf_attach_type type); +loff_t *ppos,void **new_buf, +enum bpf_attach_type type);static inline enum bpf_cgroup_storage_type cgroup_storage_type (struct bpf_map *map)
@@ -262,12 +263,12 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key, }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, count, nbuf) \ +#define BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, buf, count, pos, nbuf) \ ({ \ int __ret = 0; \ if (cgroup_bpf_enabled) \ __ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \ - buf, count, nbuf, \ + buf, count, pos, nbuf, \ BPF_CGROUP_SYSCTL); \ __ret; \ })
@@ -340,7 +341,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK (sk, uaddr, t_ctx) ({ 0; })#define BPF_CGROUP_RUN_PROG_SOCK_OPS (sock_ops) ({ 0; })#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP (type,major,minor,access) ({ 0; }) -#define BPF_CGROUP_RUN_PROG_SYSCTL (head,table,write,buf,count,nbuf) ({ 0; }) +#define BPF_CGROUP_RUN_PROG_SYSCTL (head,table,write,buf,count,pos,nbuf) ({ 0; })#define for_each_cgroup_storage_type (stype) for (; false; )
include/linux/filter.h +3 -0
@@ -1188,6 +1188,9 @@ struct bpf_sysctl_kern {size_t new_len;int new_updated;int write; +loff_t *ppos; +/* Temporary "register" for indirect stores to ppos. */ + u64 tmp_reg; };#endif /* __LINUX_FILTER_H__ */
include/uapi/linux/bpf.h +3 -0
@@ -3358,6 +3358,9 @@ struct bpf_sysctl {__u32 write;/* Sysctl is being read (= 0) or written (= 1). * Allows 1,2,4-byte read, but no write. */ +__u32 file_pos;/* Sysctl file position to read from, write to. +* Allows 1,2,4-byte read an 4-byte write. + */ };#endif /* _UAPI__LINUX_BPF_H__ */
kernel/bpf/cgroup.c +51 -3
89 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v2 7/21 bpf: Sync bpf.h to tools/
In reply to Andrey IgnatovSync BPF_PROG_TYPE_CGROUP_SYSCTL related bpf UAPI changes to tools/.
--- tools/include/uapi/linux/bpf.h | 90 +++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-)
tools/include/uapi/linux/bpf.h +89 -1
@@ -166,6 +166,7 @@ enum bpf_prog_type {BPF_PROG_TYPE_LIRC_MODE2 ,BPF_PROG_TYPE_SK_REUSEPORT ,BPF_PROG_TYPE_FLOW_DISSECTOR , + BPF_PROG_TYPE_CGROUP_SYSCTL, }; enum bpf_attach_type {
@@ -187,6 +188,7 @@ enum bpf_attach_type {BPF_CGROUP_UDP6_SENDMSG ,BPF_LIRC_MODE2 ,BPF_FLOW_DISSECTOR , +BPF_CGROUP_SYSCTL , __MAX_BPF_ATTACH_TYPE };
@@ -2477,6 +2479,75 @@ union bpf_attr {* Return * 0 if iph and th are a valid SYN cookie ACK, or a negative error * otherwise. +* +* int bpf_sysctl_get_name(struct bpf_sysctl *ctx, char *buf, size_t buf_len, u64 flags) +* Description +* Get name of sysctl in /proc/sys/ and copy it into provided by +* program buffer *buf* of size *buf_len*. +* +* The buffer is always NUL terminated, unless it's zero-sized. +* +* If *flags* is zero, full name (e.g. "net/ipv4/tcp_mem") is +* copied. Use **BPF_F_SYSCTL_BASE_NAME** flag to copy base name +* only (e.g. "tcp_mem"). +* 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). +* +* 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. +* +* int bpf_sysctl_get_new_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len) +* Description +* Get new value being written by user space to sysctl (before +* the actual write happens) and copy it as a string into +* provided by program buffer *buf* of size *buf_len*. +* +* User space may write new value at file position > 0. +* +* 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 sysctl is being read. +* +* int bpf_sysctl_set_new_value(struct bpf_sysctl *ctx, const char *buf, size_t buf_len) +* Description +* Override new value being written by user space to sysctl with +* value provided by program in buffer *buf* of size *buf_len*. +* +* *buf* should contain a string in same form as provided by user +* space on sysctl write. +* +* User space may write new value at file position > 0. To override +* the whole sysctl value file position should be set to zero. +* Return +* 0 on success. +* +* **-E2BIG** if the *buf_len* is too big. +* +* **-EINVAL** if sysctl is being read. */ #define __BPF_FUNC_MAPPER (FN ) \ FN(unspec), \
@@ -2579,7 +2650,11 @@ union bpf_attr {FN (skb_ecn_set_ce), \FN (get_listener_sock), \FN (skc_lookup_tcp), \ -FN (tcp_check_syncookie), +FN (tcp_check_syncookie), \ +FN (sysctl_get_name), \ +FN (sysctl_get_current_value), \ +FN (sysctl_get_new_value), \ +FN (sysctl_set_new_value),/* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call
@@ -2646,6 +2721,9 @@ enum bpf_func_id {#define BPF_F_ADJ_ROOM_ENCAP_L4_GRE (1ULL << 3)#define BPF_F_ADJ_ROOM_ENCAP_L4_UDP (1ULL << 4) +/* BPF_FUNC_sysctl_get_name flags. */ +#define BPF_F_SYSCTL_BASE_NAME (1ULL << 0) +/* Mode for BPF_FUNC_skb_adjust_room helper. */ enum bpf_adj_room_mode {BPF_ADJ_ROOM_NET ,
@@ -3275,4 +3353,14 @@ struct bpf_line_info {struct bpf_spin_lock {__u32 val; }; + +struct bpf_sysctl { +__u32 write;/* Sysctl is being read (= 0) or written (= 1). +* Allows 1,2,4-byte read, but no write. +*/ +__u32 file_pos;/* Sysctl file position to read from, write to. +* Allows 1,2,4-byte read an 4-byte write. +*/ +}; +#endif /* _UAPI__LINUX_BPF_H__ */
Signature · 1 line
2.17.1
PATCH v2 8/21 libbpf: Support sysctl hook
In reply to Andrey IgnatovSupport BPF_PROG_TYPE_CGROUP_SYSCTL program in libbpf: identifying program and attach types by section name, probe.
--- tools/lib/bpf/libbpf.c | 3 +++ tools/lib/bpf/libbpf_probes.c | 1 + 2 files changed, 4 insertions(+)
tools/lib/bpf/libbpf.c +3 -0
@@ -1689,6 +1689,7 @@ static bool bpf_prog_type__needs_kver(enum bpf_prog_type type)case BPF_PROG_TYPE_TRACEPOINT :case BPF_PROG_TYPE_RAW_TRACEPOINT :case BPF_PROG_TYPE_PERF_EVENT : +case BPF_PROG_TYPE_CGROUP_SYSCTL :return false;case BPF_PROG_TYPE_KPROBE :default :
@@ -2626,6 +2627,8 @@ static const struct { BPF_CGROUP_UDP4_SENDMSG),BPF_EAPROG_SEC ("cgroup/sendmsg6" ,BPF_PROG_TYPE_CGROUP_SOCK_ADDR ,BPF_CGROUP_UDP6_SENDMSG ), +BPF_EAPROG_SEC ("cgroup/sysctl" ,BPF_PROG_TYPE_CGROUP_SYSCTL , + BPF_CGROUP_SYSCTL), };#undef BPF_PROG_SEC_IMPL
tools/lib/bpf/libbpf_probes.c +1 -0
@@ -97,6 +97,7 @@ probe_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns,case BPF_PROG_TYPE_LIRC_MODE2 :case BPF_PROG_TYPE_SK_REUSEPORT :case BPF_PROG_TYPE_FLOW_DISSECTOR : +case BPF_PROG_TYPE_CGROUP_SYSCTL :default :break ; }
Signature · 1 line
2.17.1
PATCH v2 9/21 selftests/bpf: Test sysctl section name
In reply to Andrey IgnatovAdd unit test to verify that program and attach types are properly identified for "cgroup/sysctl" section name.
--- tools/testing/selftests/bpf/test_section_names.c | 5 +++++ 1 file changed, 5 insertions(+)
tools/testing/selftests/bpf/test_section_names.c +5 -0
@@ -119,6 +119,11 @@ static struct sec_name_test tests[] = { {0, BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG}, {0, BPF_CGROUP_UDP6_SENDMSG}, }, + { +"cgroup/sysctl" , + {0, BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL}, + {0, BPF_CGROUP_SYSCTL}, + }, };static int test_prog_type_by_name (const struct sec_name_test *test)
Signature · 1 line
2.17.1
PATCH v2 10/21 selftests/bpf: Test BPF_CGROUP_SYSCTL
In reply to Andrey IgnatovAdd unit test for BPF_PROG_TYPE_CGROUP_SYSCTL program type.
Test that program can allow/deny access. Test both valid and invalid accesses to ctx->write.
Example of output:
# ./test_sysctl Test case: sysctl wrong attach_type .. [PASS] Test case: sysctl:read allow all .. [PASS] Test case: sysctl:read deny all .. [PASS] Test case: ctx:write sysctl:read read ok .. [PASS] Test case: ctx:write sysctl:write read ok .. [PASS] Test case: ctx:write sysctl:read write reject .. [PASS] Summary: 6 PASSED, 0 FAILED
--- tools/testing/selftests/bpf/Makefile | 3 +- tools/testing/selftests/bpf/test_sysctl.c | 291 ++++++++++++++++++++++ 2 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/bpf/test_sysctl.c
tools/testing/selftests/bpf/Makefile +2 -1
@@ -23,7 +23,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_tcpnotify_user test_sock_fields + test_netcnt test_tcpnotify_user test_sock_fields test_sysctl BPF_OBJ_FILES = $(patsubst %.c,%.o, $(notdir $(wildcard progs/*.c))) TEST_GEN_FILES = $(BPF_OBJ_FILES)
@@ -93,6 +93,7 @@ $(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c $(OUTPUT)/test_cgroup_storage: cgroup_helpers.c $(OUTPUT)/test_netcnt: cgroup_helpers.c $(OUTPUT)/test_sock_fields: cgroup_helpers.c +$(OUTPUT)/test_sysctl: cgroup_helpers.c .PHONY: force
tools/testing/selftests/bpf/test_sysctl.c new file +291 -0
291 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v2 11/21 selftests/bpf: Test bpf_sysctl_get_name helper
In reply to Andrey IgnatovTest w/ and w/o BPF_F_SYSCTL_BASE_NAME, buffers with enough space and too small buffers to get E2BIG and truncated result, etc.
# ./test_sysctl ... Test case: sysctl_get_name sysctl_value:base ok .. [PASS] Test case: sysctl_get_name sysctl_value:base E2BIG truncated .. [PASS] Test case: sysctl_get_name sysctl:full ok .. [PASS] Test case: sysctl_get_name sysctl:full E2BIG truncated .. [PASS] Test case: sysctl_get_name sysctl:full E2BIG truncated small .. [PASS] Summary: 11 PASSED, 0 FAILED
--- tools/testing/selftests/bpf/test_sysctl.c | 222 ++++++++++++++++++++++ 1 file changed, 222 insertions(+)
tools/testing/selftests/bpf/test_sysctl.c +222 -0
@@ -128,6 +128,228 @@ static struct sysctl_test tests[] = { .open_flags = O_RDONLY, .result = LOAD_REJECT, }, + { + .descr = "sysctl_get_name sysctl_value:base ok", + .insns = { + /* sysctl_get_name arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_name arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 8), + + /* sysctl_get_name arg4 (flags) */ + BPF_MOV64_IMM(BPF_REG_4, BPF_F_SYSCTL_BASE_NAME), + + /* sysctl_get_name(ctx, buf, buf_len, flags) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_name), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, sizeof("tcp_mem") - 1, 6), + /* buf == "tcp_mem\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x006d656d5f706374ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_name sysctl_value:base E2BIG truncated", + .insns = { + /* sysctl_get_name arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_name arg3 (buf_len) too small */ + BPF_MOV64_IMM(BPF_REG_3, 7), + + /* sysctl_get_name arg4 (flags) */ + BPF_MOV64_IMM(BPF_REG_4, BPF_F_SYSCTL_BASE_NAME), + + /* sysctl_get_name(ctx, buf, buf_len, flags) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_name), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -E2BIG, 6), + + /* buf[0:7] == "tcp_me\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x00656d5f706374ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_name sysctl:full ok", + .insns = { + /* sysctl_get_name arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -24), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 8), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 16), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_name arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 17), + + /* sysctl_get_name arg4 (flags) */ + BPF_MOV64_IMM(BPF_REG_4, 0), + + /* sysctl_get_name(ctx, buf, buf_len, flags) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_name), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 16, 14), + + /* buf[0:8] == "net/ipv4" && */ + BPF_LD_IMM64(BPF_REG_8, 0x347670692f74656eULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 10), + + /* buf[8:16] == "/tcp_mem" && */ + BPF_LD_IMM64(BPF_REG_8, 0x6d656d5f7063742fULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 8), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 6), + + /* buf[16:24] == "\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x0ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 16), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_name sysctl:full E2BIG truncated", + .insns = { + /* sysctl_get_name arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -16), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 8), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_name arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 16), + + /* sysctl_get_name arg4 (flags) */ + BPF_MOV64_IMM(BPF_REG_4, 0), + + /* sysctl_get_name(ctx, buf, buf_len, flags) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_name), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -E2BIG, 10), + + /* buf[0:8] == "net/ipv4" && */ + BPF_LD_IMM64(BPF_REG_8, 0x347670692f74656eULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 6), + + /* buf[8:16] == "/tcp_me\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x00656d5f7063742fULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 8), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_name sysctl:full E2BIG truncated small", + .insns = { + /* sysctl_get_name arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_name arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 7), + + /* sysctl_get_name arg4 (flags) */ + BPF_MOV64_IMM(BPF_REG_4, 0), + + /* sysctl_get_name(ctx, buf, buf_len, flags) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_name), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -E2BIG, 6), + + /* buf[0:8] == "net/ip\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x000070692f74656eULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, }; static size_t probe_prog_length(const struct bpf_insn *fp)
Signature · 1 line
2.17.1
PATCH v2 12/21 selftests/bpf: Test sysctl_get_current_value helper
In reply to Andrey IgnatovTest sysctl_get_current_value on sysctl read and write, buffers with enough space and too small buffers to get E2BIG and truncated result, etc.
# ./test_sysctl ... Test case: sysctl_get_current_value sysctl:read ok, gt .. [PASS] Test case: sysctl_get_current_value sysctl:read ok, eq .. [PASS] Test case: sysctl_get_current_value sysctl:read E2BIG truncated .. [PASS] Test case: sysctl_get_current_value sysctl:read EINVAL .. [PASS] Test case: sysctl_get_current_value sysctl:write ok .. [PASS] Summary: 16 PASSED, 0 FAILED
--- tools/testing/selftests/bpf/test_sysctl.c | 228 ++++++++++++++++++++++ 1 file changed, 228 insertions(+)
tools/testing/selftests/bpf/test_sysctl.c +228 -0
@@ -18,11 +18,13 @@#define CG_PATH "/foo"#define MAX_INSNS 512 +#define FIXUP_SYSCTL_VALUE 0char bpf_log_buf[BPF_LOG_BUF_SIZE ];struct sysctl_test {const char *descr; +size_t fixup_value_insn;struct bpf_insn insns[MAX_INSNS ];enum bpf_attach_type attach_type;const char *sysctl;
@@ -350,6 +352,190 @@ static struct sysctl_test tests[] = { .open_flags = O_RDONLY, .result = SUCCESS, }, + { + .descr = "sysctl_get_current_value sysctl:read ok, gt", + .insns = { + /* sysctl_get_current_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_current_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 8), + + /* sysctl_get_current_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_current_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 6, 6), + + /* buf[0:6] == "Linux\n\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x000a78756e694cULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_current_value sysctl:read ok, eq", + .insns = { + /* sysctl_get_current_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_B, BPF_REG_7, BPF_REG_0, 7), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_current_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 7), + + /* sysctl_get_current_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_current_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 6, 6), + + /* buf[0:6] == "Linux\n\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x000a78756e694cULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_current_value sysctl:read E2BIG truncated", + .insns = { + /* sysctl_get_current_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_H, BPF_REG_7, BPF_REG_0, 6), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_current_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 6), + + /* sysctl_get_current_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_current_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -E2BIG, 6), + + /* buf[0:6] == "Linux\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x000078756e694cULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_current_value sysctl:read EINVAL", + .insns = { + /* sysctl_get_current_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_current_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 8), + + /* sysctl_get_current_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_current_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -EINVAL, 4), + + /* buf[0:8] is NUL-filled) */ + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0, 2), + + /* return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_A(1), + + /* else return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv6/conf/lo/stable_secret", /* -EIO */ + .open_flags = O_RDONLY, + .result = OP_EPERM, + }, + { + .descr = "sysctl_get_current_value sysctl:write ok", + .fixup_value_insn = 6, + .insns = { + /* sysctl_get_current_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_current_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 8), + + /* sysctl_get_current_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_current_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 4, 6), + + /* buf[0:4] == expected) */ + BPF_LD_IMM64(BPF_REG_8, FIXUP_SYSCTL_VALUE), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_A(1), + + /* else return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_WRONLY, + .newval = "600", /* same as default, should fail anyway */ + .result = OP_EPERM, + }, }; static size_t probe_prog_length(const struct bpf_insn *fp)
@@ -362,6 +548,27 @@ static size_t probe_prog_length(const struct bpf_insn *fp)return len +1 ; } +static int fixup_sysctl_value (const char *buf,size_t buf_len, +struct bpf_insn *prog,size_t insn_num) +{ +uint32_t value_num =0 ; +uint8_t c, i; + +if (buf_len >sizeof (value_num)) { +log_err ("Value is too big (%zd) to use in fixup" , buf_len); +return -1 ; + } + +for (i =0 ; i < buf_len; ++i) { + c = buf[i]; + value_num |= (c << i *8 ); + } + + prog[insn_num].imm = value_num; + +return 0 ; +} +static int load_sysctl_prog (struct sysctl_test *test,const char *sysctl_path) {struct bpf_insn *prog = test->insns;
@@ -374,6 +581,27 @@ static int load_sysctl_prog(struct sysctl_test *test, const char *sysctl_path) attr.insns_cnt =probe_prog_length (attr.insns); attr.license ="GPL" ; +if (test->fixup_value_insn) { +char buf[128 ]; +ssize_t len; +int fd; + + fd =open (sysctl_path,O_RDONLY |O_CLOEXEC ); +if (fd <0 ) { +log_err ("open(%s) failed" , sysctl_path); +return -1 ; + } + len =read (fd, buf,sizeof (buf)); +if (len ==-1 ) { +log_err ("read(%s) failed" , sysctl_path); +close (fd); +return -1 ; + } +close (fd); +if (fixup_sysctl_value (buf, len, prog, test->fixup_value_insn)) +return -1 ; + } + ret =bpf_load_program_xattr (&attr, bpf_log_buf,BPF_LOG_BUF_SIZE );if (ret <0 && test->result !=LOAD_REJECT ) { log_err(">>> Loading program error.\n"
Signature · 1 line
2.17.1
PATCH v2 13/21 selftests/bpf: Test bpf_sysctl_{get,set}_new_value helpers
In reply to Andrey IgnatovTest that new value provided by user space on sysctl write can be read by bpf_sysctl_get_new_value and overridden by bpf_sysctl_set_new_value.
# ./test_sysctl ... Test case: sysctl_get_new_value sysctl:read EINVAL .. [PASS] Test case: sysctl_get_new_value sysctl:write ok .. [PASS] Test case: sysctl_get_new_value sysctl:write ok long .. [PASS] Test case: sysctl_get_new_value sysctl:write E2BIG .. [PASS] Test case: sysctl_set_new_value sysctl:read EINVAL .. [PASS] Test case: sysctl_set_new_value sysctl:write ok .. [PASS] Summary: 22 PASSED, 0 FAILED
--- tools/testing/selftests/bpf/test_sysctl.c | 222 ++++++++++++++++++++++ 1 file changed, 222 insertions(+)
tools/testing/selftests/bpf/test_sysctl.c +222 -0
@@ -536,6 +536,228 @@ static struct sysctl_test tests[] = { .newval = "600", /* same as default, should fail anyway */ .result = OP_EPERM, }, + { + .descr = "sysctl_get_new_value sysctl:read EINVAL", + .insns = { + /* sysctl_get_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 8), + + /* sysctl_get_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_new_value), + + /* if (ret == expected) */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -EINVAL, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_get_new_value sysctl:write ok", + .insns = { + /* sysctl_get_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 4), + + /* sysctl_get_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_new_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 3, 4), + + /* buf[0:4] == "606\0") */ + BPF_LDX_MEM(BPF_W, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0x00363036, 2), + + /* return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_A(1), + + /* else return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_WRONLY, + .newval = "606", + .result = OP_EPERM, + }, + { + .descr = "sysctl_get_new_value sysctl:write ok long", + .insns = { + /* sysctl_get_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -24), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 24), + + /* sysctl_get_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_new_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 23, 14), + + /* buf[0:8] == "3000000 " && */ + BPF_LD_IMM64(BPF_REG_8, 0x2030303030303033ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 10), + + /* buf[8:16] == "4000000 " && */ + BPF_LD_IMM64(BPF_REG_8, 0x2030303030303034ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 8), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 6), + + /* buf[16:24] == "6000000\0") */ + BPF_LD_IMM64(BPF_REG_8, 0x0030303030303036ULL), + BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 16), + BPF_JMP_REG(BPF_JNE, BPF_REG_8, BPF_REG_9, 2), + + /* return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_A(1), + + /* else return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_WRONLY, + .newval = "3000000 4000000 6000000", + .result = OP_EPERM, + }, + { + .descr = "sysctl_get_new_value sysctl:write E2BIG", + .insns = { + /* sysctl_get_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_B, BPF_REG_7, BPF_REG_0, 3), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_get_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 3), + + /* sysctl_get_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_get_new_value), + + /* if (ret == expected && */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -E2BIG, 4), + + /* buf[0:3] == "60\0") */ + BPF_LDX_MEM(BPF_W, BPF_REG_9, BPF_REG_7, 0), + BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0x003036, 2), + + /* return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_A(1), + + /* else return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_WRONLY, + .newval = "606", + .result = OP_EPERM, + }, + { + .descr = "sysctl_set_new_value sysctl:read EINVAL", + .insns = { + /* sysctl_set_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, 0x00303036), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_set_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 3), + + /* sysctl_set_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_set_new_value), + + /* if (ret == expected) */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, -EINVAL, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr = "sysctl_set_new_value sysctl:write ok", + .fixup_value_insn = 2, + .insns = { + /* sysctl_set_new_value arg2 (buf) */ + BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), + BPF_MOV64_IMM(BPF_REG_0, FIXUP_SYSCTL_VALUE), + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), + + BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), + + /* sysctl_set_new_value arg3 (buf_len) */ + BPF_MOV64_IMM(BPF_REG_3, 3), + + /* sysctl_set_new_value(ctx, buf, buf_len) */ + BPF_EMIT_CALL(BPF_FUNC_sysctl_set_new_value), + + /* if (ret == expected) */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), + + /* return ALLOW; */ + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_JMP_A(1), + + /* else return DENY; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_WRONLY, + .newval = "606", + .result = SUCCESS, + }, }; static size_t probe_prog_length(const struct bpf_insn *fp)
Signature · 1 line
2.17.1
PATCH v2 14/21 selftests/bpf: Test file_pos field in bpf_sysctl ctx
In reply to Andrey IgnatovTest access to file_pos field of bpf_sysctl context, both read (incl. narrow read) and write.
# ./test_sysctl ... Test case: ctx:file_pos sysctl:read read ok .. [PASS] Test case: ctx:file_pos sysctl:read read ok narrow .. [PASS] Test case: ctx:file_pos sysctl:read write ok .. [PASS] ...
--- tools/testing/selftests/bpf/test_sysctl.c | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+)
tools/testing/selftests/bpf/test_sysctl.c +64 -0
@@ -30,6 +30,7 @@ struct sysctl_test {const char *sysctl;int open_flags;const char *newval; +const char *oldval;enum {LOAD_REJECT ,ATTACH_REJECT ,
@@ -130,6 +131,64 @@ static struct sysctl_test tests[] = { .open_flags = O_RDONLY, .result = LOAD_REJECT, }, + { + .descr = "ctx:file_pos sysctl:read read ok", + .insns = { +/* If (file_pos == X) */ +BPF_LDX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_1 , + offsetof(struct bpf_sysctl , file_pos)), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,0 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr ="ctx:file_pos sysctl:read read ok narrow" , + .insns = { +/* If (file_pos == X) */ +BPF_LDX_MEM (BPF_B ,BPF_REG_7 ,BPF_REG_1 , + offsetof(struct bpf_sysctl , file_pos)), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_7 ,0 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { + .descr ="ctx:file_pos sysctl:read write ok" , + .insns = { +/* file_pos = X */ +BPF_MOV64_IMM (BPF_REG_0 ,2 ), +BPF_STX_MEM (BPF_W ,BPF_REG_1 ,BPF_REG_0 , + offsetof(struct bpf_sysctl , file_pos)), +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "kernel/ostype", + .open_flags = O_RDONLY, + .oldval = "nux\n", + .result = SUCCESS, + }, { .descr ="sysctl_get_name sysctl_value:base ok" , .insns = {
@@ -848,6 +907,11 @@ static int access_sysctl(const char *sysctl_path,if (read (fd, buf,sizeof (buf)) ==-1 ) goto err; +if (test->oldval && +strncmp (buf, test->oldval,strlen (test->oldval))) { +log_err ("Read value %s != %s" , buf, test->oldval); + goto err; + } }else if (test->open_flags ==O_WRONLY ) {if (!test->newval) {log_err ("New value for sysctl is not set" );
Signature · 1 line
2.17.1
PATCH v2 15/21 bpf: Introduce ARG_PTR_TO_{INT,LONG} arg types
In reply to Andrey IgnatovCurrently the way to pass result from BPF helper to BPF program is to provide memory area defined by pointer and size: func(void *, size_t).
It works great for generic use-case, but for simple types, such as int, it's overkill and consumes two arguments when it could use just one.
Introduce new argument types ARG_PTR_TO_INT and ARG_PTR_TO_LONG to be able to pass result from helper to program via pointer to int and long
correspondingly: func(int *) or func(long *).
New argument types are similar to ARG_PTR_TO_MEM with the following differences:
* they don't require corresponding ARG_CONST_SIZE argument, predefined access sizes are used instead (32bit for int, 64bit for long);
* it's possible to use more than one such an argument in a helper;
* provided pointers have to be aligned.
It's easy to introduce similar ARG_PTR_TO_CHAR and ARG_PTR_TO_SHORT argument types. It's not done due to lack of use-case though.
--- include/linux/bpf.h | 2 ++ kernel/bpf/verifier.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+)
include/linux/bpf.h +2 -0
@@ -195,6 +195,8 @@ enum bpf_arg_type {ARG_ANYTHING ,/* any (initialized) argument is ok */ ARG_PTR_TO_SPIN_LOCK ,/* pointer to bpf_spin_lock */ ARG_PTR_TO_SOCK_COMMON ,/* pointer to sock_common */ +ARG_PTR_TO_INT ,/* pointer to int */ + ARG_PTR_TO_LONG, /* pointer to long */ };/* type of values returned from helper functions */
kernel/bpf/verifier.c +29 -0
@@ -2344,6 +2344,22 @@ static bool arg_type_is_mem_size(enum bpf_arg_type type) type ==ARG_CONST_SIZE_OR_ZERO ; } +static bool arg_type_is_int_ptr (enum bpf_arg_type type) +{ +return type ==ARG_PTR_TO_INT || + type ==ARG_PTR_TO_LONG ; +} + +static int int_ptr_type_to_size (enum bpf_arg_type type) +{ +if (type ==ARG_PTR_TO_INT ) +return sizeof (u32); +else if (type ==ARG_PTR_TO_LONG ) +return sizeof (u64); + +return -EINVAL ; +} +static int check_func_arg (struct bpf_verifier_env *env,u32 regno,enum bpf_arg_type arg_type,struct bpf_call_arg_meta *meta)
@@ -2436,6 +2452,12 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno, type != expected_type) goto err_type; meta->raw_mode = arg_type ==ARG_PTR_TO_UNINIT_MEM ; + }else if (arg_type_is_int_ptr (arg_type)) { + expected_type =PTR_TO_STACK ; +if (!type_is_pkt_pointer (type) && + type !=PTR_TO_MAP_VALUE && + type != expected_type) + goto err_type; }else {verbose (env,"unsupported arg_type %d\n" , arg_type);return -EFAULT ;
@@ -2517,6 +2539,13 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno, err =check_helper_mem_access (env, regno -1 , reg->umax_value, zero_size_allowed, meta); + }else if (arg_type_is_int_ptr (arg_type)) { +int size =int_ptr_type_to_size (arg_type); + + err =check_helper_mem_access (env, regno, size, false, meta); +if (err) +return err; + err =check_ptr_alignment (env, reg,0 , size, true); }return err;
Signature · 1 line
2.17.1
PATCH v2 16/21 bpf: Introduce bpf_strtol and bpf_strtoul helpers
In reply to Andrey IgnatovAdd bpf_strtol and bpf_strtoul to convert a string to long and unsigned long correspondingly. It's similar to user space strtol(3) and strtoul(3) with a few changes to the API:
* instead of NUL-terminated C string the helpers expect buffer and buffer length;
* resulting long or unsigned long is returned in a separate result-argument;
* return value is used to indicate success or failure, on success number of consumed bytes is returned that can be used to identify position to read next if the buffer is expected to contain multiple integers;
* instead of *base* argument, *flags* is used that provides base in 5 LSB, other bits are reserved for future use;
* number of supported bases is limited.
Documentation for the new helpers is provided in bpf.h UAPI.
The helpers are made available to BPF_PROG_TYPE_CGROUP_SYSCTL programs to be able to convert string input to e.g. "ulongvec" output.
E.g. "net/ipv4/tcp_mem" consists of three ulong integers. They can be parsed by calling to bpf_strtoul three times.
Implementation notes:
Implementation includes "../../lib/kstrtox.h" to reuse integer parsing functions. It's done exactly same way as fs/proc/base.c already does.
Unfortunately existing kstrtoX function can't be used directly since they fail if any invalid character is present right after integer in the string. Existing simple_strtoX functions can't be used either since they're obsolete and don't handle overflow properly.
--- include/linux/bpf.h | 2 + include/uapi/linux/bpf.h | 51 ++++++++++++++- kernel/bpf/cgroup.c | 4 ++ kernel/bpf/helpers.c | 131 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 1 deletion(-)
include/linux/bpf.h +2 -0
@@ -932,6 +932,8 @@ extern const struct bpf_func_proto bpf_sk_redirect_map_proto;extern const struct bpf_func_proto bpf_spin_lock_proto;extern const struct bpf_func_proto bpf_spin_unlock_proto;extern const struct bpf_func_proto bpf_get_local_storage_proto; +extern const struct bpf_func_proto bpf_strtol_proto; +extern const struct bpf_func_proto bpf_strtoul_proto;/* Shared helpers among cBPF and eBPF. */ void bpf_user_rnd_init_once (void );
include/uapi/linux/bpf.h +50 -1
63 lines not on this page · raw message
kernel/bpf/cgroup.c +4 -0
@@ -1016,6 +1016,10 @@ 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_strtol: + return &bpf_strtol_proto; +case BPF_FUNC_strtoul: +return &bpf_strtoul_proto;case BPF_FUNC_sysctl_get_name:return &bpf_sysctl_get_name_proto;case BPF_FUNC_sysctl_get_current_value:
kernel/bpf/helpers.c +131 -0
141 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v2 17/21 bpf: Sync bpf.h to tools/
In reply to Andrey IgnatovSync bpf_strtoX related bpf UAPI changes to tools/.
--- tools/include/uapi/linux/bpf.h | 51 +++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
tools/include/uapi/linux/bpf.h +50 -1
@@ -2548,6 +2548,53 @@ union bpf_attr {* **-E2BIG** if the *buf_len* is too big. * * **-EINVAL** if sysctl is being read. +* +* int bpf_strtol(const char *buf, size_t buf_len, u64 flags, long *res) +* Description +* Convert the initial part of the string from buffer *buf* of +* size *buf_len* to a long integer according to the given base +* and save the result in *res*. +* +* The string may begin with an arbitrary amount of white space +* (as determined by isspace(3)) followed by a single optional '-' +* sign. +* +* Five least significant bits of *flags* encode base, other bits +* are currently unused. +* +* Base must be either 8, 10, 16 or 0 to detect it automatically +* similar to user space strtol(3). +* Return +* Number of characters consumed on success. Must be positive but +* no more than buf_len. +* +* **-EINVAL** if no valid digits were found or unsupported base +* was provided. +* +* **-ERANGE** if resulting value was out of range. +* +* int bpf_strtoul(const char *buf, size_t buf_len, u64 flags, unsigned long *res) +* Description +* Convert the initial part of the string from buffer *buf* of +* size *buf_len* to an unsigned long integer according to the +* given base and save the result in *res*. +* +* The string may begin with an arbitrary amount of white space +* (as determined by isspace(3)). +* +* Five least significant bits of *flags* encode base, other bits +* are currently unused. +* +* Base must be either 8, 10, 16 or 0 to detect it automatically +* similar to user space strtoul(3). +* Return +* Number of characters consumed on success. Must be positive but +* no more than buf_len. +* +* **-EINVAL** if no valid digits were found or unsupported base +* was provided. +* +* **-ERANGE** if resulting value was out of range. */ #define __BPF_FUNC_MAPPER (FN ) \ FN(unspec), \
@@ -2654,7 +2701,9 @@ union bpf_attr {FN (sysctl_get_name), \FN (sysctl_get_current_value), \FN (sysctl_get_new_value), \ -FN (sysctl_set_new_value), +FN (sysctl_set_new_value), \ +FN (strtol), \ +FN (strtoul),/* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call
Signature · 1 line
2.17.1
PATCH v2 18/21 selftests/bpf: Add sysctl and strtoX helpers to bpf_helpers.h
In reply to Andrey IgnatovAdd bpf_sysctl_* and bpf_strtoX helpers to bpf_helpers.h.
--- tools/testing/selftests/bpf/bpf_helpers.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
tools/testing/selftests/bpf/bpf_helpers.h +19 -0
@@ -192,6 +192,25 @@ static int (*bpf_skb_ecn_set_ce)(void *ctx) =static int (*bpf_tcp_check_syncookie)(struct bpf_sock *sk,void *ip,int ip_len,void *tcp,int tcp_len) = (void *) BPF_FUNC_tcp_check_syncookie; +static int (*bpf_sysctl_get_name)(void *ctx,char *buf, +unsigned long long buf_len, +unsigned long long flags) = + (void *) BPF_FUNC_sysctl_get_name; +static int (*bpf_sysctl_get_current_value)(void *ctx,char *buf, +unsigned long long buf_len) = + (void *) BPF_FUNC_sysctl_get_current_value; +static int (*bpf_sysctl_get_new_value)(void *ctx,char *buf, +unsigned long long buf_len) = + (void *) BPF_FUNC_sysctl_get_new_value; +static int (*bpf_sysctl_set_new_value)(void *ctx,const char *buf, +unsigned long long buf_len) = + (void *) BPF_FUNC_sysctl_set_new_value; +static int (*bpf_strtol)(const char *buf,unsigned long long buf_len, +unsigned long long flags,long *res) = + (void *) BPF_FUNC_strtol; +static int (*bpf_strtoul)(const char *buf,unsigned long long buf_len, +unsigned long long flags,unsigned long *res) = + (void *) BPF_FUNC_strtoul;/* llvm builtin functions that eBPF C program may use to * emit BPF_LD_ABS and BPF_LD_IND instructions
Signature · 1 line
2.17.1
PATCH v2 19/21 selftests/bpf: Test ARG_PTR_TO_LONG arg type
In reply to Andrey IgnatovTest that verifier handles new argument types properly, including uninitialized or partially initialized value, misaligned stack access, etc.
Example of output:
#456/p ARG_PTR_TO_LONG uninitialized OK
#457/p ARG_PTR_TO_LONG half-uninitialized OK
#458/p ARG_PTR_TO_LONG misaligned OK
#459/p ARG_PTR_TO_LONG size < sizeof(long) OK
#460/p ARG_PTR_TO_LONG initialized OK
--- .../testing/selftests/bpf/verifier/int_ptr.c | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tools/testing/selftests/bpf/verifier/int_ptr.c
tools/testing/selftests/bpf/verifier/int_ptr.c new file +160 -0
@@ -0,0 +1,160 @@ +{ + "ARG_PTR_TO_LONG uninitialized", + .insns = { +/* bpf_strtoul arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* bpf_strtoul arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* bpf_strtoul arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* bpf_strtoul arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +/* bpf_strtoul() */ +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .result =REJECT , + .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL, + .errstr = "invalid indirect read from stack off -16+0 size 8", +}, +{ +"ARG_PTR_TO_LONG half-uninitialized" , + .insns = { +/* bpf_strtoul arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* bpf_strtoul arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* bpf_strtoul arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* bpf_strtoul arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +/* bpf_strtoul() */ +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .result =REJECT , + .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL, + .errstr = "invalid indirect read from stack off -16+4 size 8", +}, +{ +"ARG_PTR_TO_LONG misaligned" , + .insns = { +/* bpf_strtoul arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* bpf_strtoul arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* bpf_strtoul arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* bpf_strtoul arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-12 ), +BPF_MOV64_IMM (BPF_REG_0 ,0 ), +BPF_STX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,4 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +/* bpf_strtoul() */ +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .result =REJECT , + .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL, + .errstr = "misaligned stack access off (0x0; 0x0)+-20+0 size 8", +}, +{ +"ARG_PTR_TO_LONG size < sizeof(long)" , + .insns = { +/* bpf_strtoul arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-16 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* bpf_strtoul arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* bpf_strtoul arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* bpf_strtoul arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,12 ), +BPF_STX_MEM (BPF_W ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +/* bpf_strtoul() */ +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .result =REJECT , + .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL, + .errstr = "invalid stack type R4 off=-4 access_size=8", +}, +{ +"ARG_PTR_TO_LONG initialized" , + .insns = { +/* bpf_strtoul arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* bpf_strtoul arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* bpf_strtoul arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* bpf_strtoul arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +/* bpf_strtoul() */ +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .result =ACCEPT , + .prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL, +},
Signature · 1 line
2.17.1
PATCH v2 20/21 selftests/bpf: Test bpf_strtol and bpf_strtoul helpers
In reply to Andrey IgnatovTest that bpf_strtol and bpf_strtoul helpers can be used to convert provided buffer to long or unsigned long correspondingly and return both correct result and number of consumed bytes, or proper errno.
Example of output:
# ./test_sysctl .. Test case: bpf_strtoul one number string .. [PASS] Test case: bpf_strtoul multi number string .. [PASS] Test case: bpf_strtoul buf_len = 0, reject .. [PASS] Test case: bpf_strtoul supported base, ok .. [PASS] Test case: bpf_strtoul unsupported base, EINVAL .. [PASS] Test case: bpf_strtoul buf with spaces only, EINVAL .. [PASS] Test case: bpf_strtoul negative number, EINVAL .. [PASS] Test case: bpf_strtol negative number, ok .. [PASS] Test case: bpf_strtol hex number, ok .. [PASS] Test case: bpf_strtol max long .. [PASS] Test case: bpf_strtol overflow, ERANGE .. [PASS] Summary: 36 PASSED, 0 FAILED
--- tools/testing/selftests/bpf/test_sysctl.c | 485 ++++++++++++++++++++++ 1 file changed, 485 insertions(+)
tools/testing/selftests/bpf/test_sysctl.c +485 -0
@@ -817,6 +817,491 @@ static struct sysctl_test tests[] = { .newval = "606", .result = SUCCESS, }, + { + "bpf_strtoul one number string", + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,3 ,4 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,600 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtoul multi number string" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +/* "600 602\0" */ +BPF_LD_IMM64 (BPF_REG_0 ,0x0032303620303036ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,8 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,3 ,18 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,600 ,16 ), + +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_ALU64_REG (BPF_ADD ,BPF_REG_7 ,BPF_REG_0 ), +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,8 ), +BPF_ALU64_REG (BPF_SUB ,BPF_REG_2 ,BPF_REG_0 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-16 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,4 ,4 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,602 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtoul buf_len = 0, reject" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,0 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +BPF_MOV64_IMM (BPF_REG_0 ,1 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = LOAD_REJECT, + }, + { +"bpf_strtoul supported base, ok" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00373730 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,8 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,3 ,4 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,63 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtoul unsupported base, EINVAL" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00303036 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,3 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected) */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 , -EINVAL ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtoul buf with spaces only, EINVAL" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x090a0c0d ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected) */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 , -EINVAL ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtoul negative number, EINVAL" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00362d0a ),/* " -6\0" */ +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtoul), + +/* if (ret == expected) */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 , -EINVAL ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtol negative number, ok" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x00362d0a ),/* " -6\0" */ +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,10 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtol), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,3 ,4 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,-6 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtol hex number, ok" , + .insns = { +/* arg1 (buf) */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_MOV64_IMM (BPF_REG_0 ,0x65667830 ),/* "0xfe" */ +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,4 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtol), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,4 ,4 ), +/* res == expected) */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_IMM (BPF_JNE ,BPF_REG_9 ,254 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtol max long" , + .insns = { +/* arg1 (buf) 9223372036854775807 */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-24 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x3032373333323239ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x3537373435383633ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,8 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x0000000000373038ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,16 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,19 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtol), + +/* if (ret == expected && */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 ,19 ,6 ), +/* res == expected) */ +BPF_LD_IMM64 (BPF_REG_8 ,0x7fffffffffffffffULL ), +BPF_LDX_MEM (BPF_DW ,BPF_REG_9 ,BPF_REG_7 ,0 ), +BPF_JMP_REG (BPF_JNE ,BPF_REG_8 ,BPF_REG_9 ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, + { +"bpf_strtol overflow, ERANGE" , + .insns = { +/* arg1 (buf) 9223372036854775808 */ +BPF_MOV64_REG (BPF_REG_7 ,BPF_REG_10 ), +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-24 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x3032373333323239ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x3537373435383633ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,8 ), +BPF_LD_IMM64 (BPF_REG_0 ,0x0000000000383038ULL ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,16 ), + +BPF_MOV64_REG (BPF_REG_1 ,BPF_REG_7 ), + +/* arg2 (buf_len) */ +BPF_MOV64_IMM (BPF_REG_2 ,19 ), + +/* arg3 (flags) */ +BPF_MOV64_IMM (BPF_REG_3 ,0 ), + +/* arg4 (res) */ +BPF_ALU64_IMM (BPF_ADD ,BPF_REG_7 ,-8 ), +BPF_STX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_0 ,0 ), +BPF_MOV64_REG (BPF_REG_4 ,BPF_REG_7 ), + +BPF_EMIT_CALL (BPF_FUNC_strtol), + +/* if (ret == expected) */ +BPF_JMP_IMM (BPF_JNE ,BPF_REG_0 , -ERANGE ,2 ), + +/* return ALLOW; */ +BPF_MOV64_IMM (BPF_REG_0 ,1 ), +BPF_JMP_A (1 ), + +/* else return DENY; */ +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .attach_type =BPF_CGROUP_SYSCTL , + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, };static size_t probe_prog_length (const struct bpf_insn *fp)
Signature · 1 line
2.17.1
PATCH v2 21/21 selftests/bpf: C based test for sysctl and strtoX
In reply to Andrey IgnatovAdd C based test for a few bpf_sysctl_* helpers and bpf_strtoul.
Make sure that sysctl can be identified by name and that multiple integers can be parsed from sysctl value with bpf_strtoul.
net/ipv4/tcp_mem is chosen as a testing sysctl, it contains 3 unsigned longs, they all are parsed and compared (val[0] < val[1] < val[2]).
One verifier limitation is being worked around in the test: variable stack access. Since such an access is unsupported, offset, returned by bpf_strtoul while parsing current ulong, can't be used directly when specifying 'buf' pointer to the next bpf_strtoul. That's why instead of 'base + offset' where offset non-const tnum, the test uses brute force search to find static number, that matches with 'offset', and use 'base + static' to access the stack.
Example of output:
# ./test_sysctl ... Test case: C prog: deny all writes .. [PASS] Test case: C prog: deny access by name .. [PASS] Test case: C prog: read tcp_mem .. [PASS] Summary: 39 PASSED, 0 FAILED
--- .../selftests/bpf/progs/test_sysctl_prog.c | 89 +++++++++++++++++++ tools/testing/selftests/bpf/test_sysctl.c | 57 +++++++++++- 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/bpf/progs/test_sysctl_prog.c
tools/testing/selftests/bpf/progs/test_sysctl_prog.c new file +89 -0
@@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook + +#include <stdint.h> +#include <string.h> + +#include <linux/stddef.h> +#include <linux/bpf.h> + +#include "bpf_helpers.h" + +/* Min/max supported lengths of a string with unsigned long in base 10. */ +#define MIN_ULONG_STR_LEN 2 +#define MAX_ULONG_STR_LEN 15 + +static __always_inline int is_tcp_mem(struct bpf_sysctl *ctx) +{ +char tcp_mem_name[] ="net/ipv4/tcp_mem" ; +unsigned char i; +char name[64 ]; +int ret; + +memset (name,0 ,sizeof (name)); + ret =bpf_sysctl_get_name (ctx, name,sizeof (name),0 ); +if (ret <0 || ret !=sizeof (tcp_mem_name) -1 ) +return 0 ; + +#pragma clang loop unroll(full) +for (i =0 ; i <sizeof (tcp_mem_name); ++i) +if (name[i] != tcp_mem_name[i]) +return 0 ; + +return 1 ; +} + +SEC("cgroup/sysctl") +int sysctl_tcp_mem (struct bpf_sysctl *ctx) +{ +unsigned long mem_min =0 , mem_pressure =0 , mem_max =0 ; +unsigned char i, off; +char value[64 ]; +int ret; + +if (ctx->write) +return 0 ; + +if (!is_tcp_mem (ctx)) +return 0 ; + + ret =bpf_sysctl_get_current_value (ctx, value,sizeof (value)); +if (ret <0 || ret >=sizeof (value)) +return 0 ; + + ret =bpf_strtoul (value,sizeof (value),0 , &mem_min); +if (ret <=0 || ret >MAX_ULONG_STR_LEN ) +return 0 ; + off = ret; + ret =-1 ; + +/* Make verifier happy, prevent "invalid variable stack read R1 +* var_off=(0x0; 0xf)" by using static offset for value. +*/ +#pragma clang loop unroll(full) +for (i =MIN_ULONG_STR_LEN ; i <=MAX_ULONG_STR_LEN ; ++i) +if (off == i) { + ret =bpf_strtoul (value + i,sizeof (value) - i,0 , + &mem_pressure); +break ; + } +if (ret <=0 || ret >MAX_ULONG_STR_LEN ) +return 0 ; + off += ret; + ret =-1 ; + +/* Same here. */ +#pragma clang loop unroll(full) +for (i =MIN_ULONG_STR_LEN *2 ; i <=MAX_ULONG_STR_LEN *2 +2 ; ++i) +if (off == i) { + ret =bpf_strtoul (value + i,sizeof (value) - i,0 , + &mem_max); +break ; + } +if (ret <=0 || ret >MAX_ULONG_STR_LEN ) +return 0 ; + +return mem_min < mem_pressure && mem_pressure < mem_max; +} + +char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/test_sysctl.c +56 -1
87 lines not on this page · raw message
Signature · 1 line
2.17.1
Quoted 2 lines · Andrey Ignatov
v1->v2:
- addfs/proc/proc_sysctl.cmainteners to Cc:.
Kees, Luis,
any concerns with bpf hook in sysctl ?
Pls take a look at patch 2 where it touches fs/proc/proc_sysctl.c This facility is for root to monitor dumb root tasks. More detailed description below and in patches.
Quoted 95 lines
The patch set introduces new BPF hook for sysctl.
It adds new program type
BPF_PROG_TYPE_CGROUP_SYSCTLand attach typeBPF_CGROUP_SYSCTL.
BPF_CGROUP_SYSCTLhook is placed before calling to sysctl'sproc_handlerso that accesses (read/write) to sysctl can be controlled for specific cgroup and either allowed or denied, or traced.The hook has access to sysctl name, current sysctl value and (on write only) to new sysctl value via corresponding helpers. New sysctl value can be overridden by program. Both name and values (current/new) are represented as strings same way they're visible in
/proc/sys/. It is up to program to parse these strings.To help with parsing the most common kind of sysctl value, vector of integers, two new helpers are provided:
bpf_strtolandbpf_strtoulwith semantic similar to user space strtol(3) and strtoul(3).The hook also provides
bpf_sysctlcontext with two fields:
* @write indicates whether sysctl is being read (= 0) or written (= 1);
* @file_pos is sysctl file position to read from or write to, can be overridden.The hook allows to make better isolation for containerized applications that are run as root so that one container can't change a sysctl and affect all other containers on a host, make changes to allowed sysctl in a safer way and simplify sysctl tracing for cgroups.
Patch 1 is preliminary refactoring.
Patch 2 adds new program and attach types.
Patches 3-5 implement helpers to access sysctl name and value. Patch 6 addsfile_posfield tobpf_sysctlcontext.
Patch 7 updates UAPI in tools.
Patches 8-9 add support for the new hook to libbpf and corresponding test. Patches 10-14 add selftests for the new hook.
Patch 15 adds support for new arg types to verifier: pointer to integer. Patch 16 adds bpf_strto{l,ul} helpers to parse integers from sysctl value. Patch 17 updates UAPI in tools.
Patch 18 updatesbpf_helpers.h.
Patch 19 adds selftests for pointer to integer in verifier. Patches 20-21 add selftests for bpf_strto{l,ul}, including integration
C based test for sysctl value parsing.Andrey Ignatov (21):
bpf: Add base proto function for cgroup-bpf programs
bpf: Sysctl hook
bpf: Introducebpf_sysctl_get_namehelper
bpf: Introducebpf_sysctl_get_current_valuehelper
bpf: Introduce bpf_sysctl_{get,set}_new_value helpers
bpf: Addfile_posfield tobpf_sysctlctx
bpf: Syncbpf.htotools/
libbpf: Support sysctl hook
selftests/bpf: Test sysctl section name selftests/bpf: TestBPF_CGROUP_SYSCTLselftests/bpf: Testbpf_sysctl_get_namehelper selftests/bpf: Testsysctl_get_current_valuehelper selftests/bpf: Test bpf_sysctl_{get,set}_new_value helpers selftests/bpf: Testfile_posfield inbpf_sysctlctx
bpf: Introduce ARG_PTR_TO_{INT,LONG} arg types
bpf: Introducebpf_strtolandbpf_strtoulhelpers
bpf: Syncbpf.htotools/
selftests/bpf: Add sysctl and strtoX helpers tobpf_helpers.hselftests/bpf: TestARG_PTR_TO_LONGarg type selftests/bpf: Testbpf_strtolandbpf_strtoulhelpers selftests/bpf: C based test for sysctl and strtoXfs/proc/proc_sysctl.c | 25 +- include/linux/bpf-cgroup.h | 21 + include/linux/bpf.h | 4 + include/linux/bpf_types.h | 1 + include/linux/filter.h | 16 + include/uapi/linux/bpf.h | 139 +- kernel/bpf/cgroup.c | 364 +++- kernel/bpf/helpers.c | 131 ++ kernel/bpf/syscall.c | 7 + kernel/bpf/verifier.c | 30 + tools/include/uapi/linux/bpf.h | 139 +- tools/lib/bpf/libbpf.c | 3 + tools/lib/bpf/libbpf_probes.c | 1 + tools/testing/selftests/bpf/Makefile | 3 +- tools/testing/selftests/bpf/bpf_helpers.h | 19 + .../selftests/bpf/progs/test_sysctl_prog.c | 89 + .../selftests/bpf/test_section_names.c | 5 + tools/testing/selftests/bpf/test_sysctl.c | 1567 +++++++++++++++++ .../testing/selftests/bpf/verifier/int_ptr.c | 160 ++ 19 files changed, 2716 insertions(+), 8 deletions(-) create mode 100644 tools/testing/selftests/bpf/progs/test_sysctl_prog.c create mode 100644 tools/testing/selftests/bpf/test_sysctl.c create mode 100644 tools/testing/selftests/bpf/verifier/int_ptr.cSignature · 1 line
2.17.1