Hi All,
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
In V3 I've been using 'fixup' section in eBPF program to tell kernel
which instructions are accessing maps. With new instruction 'fixup' is gone
and map IDR (internal map_ids) are removed.
To understand the logic behind new insn, I need to explain two main
eBPF design constraints:
1. eBPF interpreter must be generic. It should know nothing about maps or
any custom instructions or functions.
2. llvm compiler backend must be generic. It also should know nothing about
maps, helper functions, sockets, tracing, etc. LLVM just takes normal C
and compiles it for some 'fake' HW that happened to be called eBPF ISA.
patch #1 implements BPF_LD_IMM64 insn. It's just a move of 64-bit immediate
value into a register. Nothing fancy.
The reason it improved eBPF program run-time is the following:
in V3 the program used to look like:
bpf_mov r1, const_internal_map_id
bpf_call bpf_map_lookup
so in-kernel bpf_map_lookup() helper would do map_id->map_ptr conversion via
map = idr_find(&bpf_map_id_idr, map_id);
For the life of the program map_id is constant and that lookup was returning
the same value, but there was no easy way to store pointer inside eBPF insn.
With new insn the programs look like:
bpf_ld_imm64 r1, const_internal_map_ptr
bpf_call bpf_map_lookup
and the bpf_map_lookup() helper does:
struct bpf_map *map = (struct bpf_map *) (unsigned long) r1;
Though it's a small performance gain, every nsec counts.
Also new insn allows further optimizations in JIT compilers.
How does it help to cleanup program interface towards maps?
Obviously user space doesn't know what kernel map pointer is associated
with process-local map-FD.
So it's using pseudo BPF_LD_IMM64 instruction.
BPF_LD_IMM64 with src_reg == 0 -> generic move 64-bit immediate into dst_reg
BPF_LD_IMM64 with src_reg == BPF_PSEUDO_MAP_FD -> mov map_fd into dst_reg
Other values are reserved for now. (They will be used to implement
global variables, strings and other constants and per-cpu areas in the future)
So the programs look like:
BPF_LD_MAP_FD(BPF_REG_1, process_local_map_fd),
BPF_CALL(BPF_FUNC_map_lookup_elem),
eBPF verifier scans the program for such pseudo instructions, converts
process_local_map_fd -> in-kernel map pointer
and drops 'pseudo' flag of BPF_LD_IMM64 instruction.
eBPF interpreter stays generic and LLVM stays generic, since they know
nothing about pseudo instructions.
Another pseudo instruction is BPF_CALL. User space encodes one of
BPF_FUNC_xxx function ids as part of 'imm' field of the instruction
and eBPF program loader converts it to in-kernel helper function pointer.
The idea to use special instructions to access maps was suggested by Jonathan ;)
It took awhile to figure out how to do it within above two design constraints,
but the end result I think is much cleaner than what I had in V2/V3.
Another difference vs previous set is verifier split into 6 patches and
verifier testsuite is added. Beyond old checks verifier got 'tidiness' checks
to make sure all unused fields of instructions are zero.
Unfortunately classic BPF doesn't check for this. Lesson learned.
Tracing use case got some improvements as well. Now eBPF programs can be
attached to tracepoint, syscall, kprobe and C examples are more usable:
ex1_kern.c - demonstrate how programs can walk in-kernel data structures
ex2_kern.c - in-kernel event accounting and user space histograms
See patch #25
TODO:
- verifier is safe, but not secure, since it allows kernel address leaking.
fix that before lifting root-only restriction
- allow seecomp to use eBPF
- write manpage for eBPF syscall
As always all patches are available at:
git://git.kernel.org/pub/scm/linux/kernel/git/ast/bpf master
V3->V4:
- introduced 'load 64-bit immediate' eBPF instruction
- use BPF_LD_IMM64 in LLVM, verifier, programs
- got rid of 'fixup' section in eBPF programs
- got rid of map IDR and internal map_id
- split verifier into 6 patches and added verifier testsuite
- add verifier check for reserved instruction fields
- fixed bug in LLVM eBPF backend (it was miscompiling __builtin_expect)
- fixed race condition in htab_map_update_elem()
- tracing filters can now attach to tracepoint, syscall, kprobe events
- improved C examples
V2->V3:
- fixed verifier register range bug and addressed other comments (Thanks Kees!)
- re-added LLVM eBPF backend
- added two examples in C
- user space ELF parser and loader example
V1->V2:
- got rid of global id, everything now FD based (Thanks Andy!)
- split type enum in verifier (as suggested by Andy and Namhyung)
- switched gpl enforcement to be kmod like (as suggested by Andy and David)
- addressed feedback from Namhyung, Chema, Joe
- added more comments to verifier
- renamed sock_filter_int -> bpf_insn
- rebased on net-next
FD approach made eBPF user interface much cleaner for sockets/seccomp/tracing
use cases. Now socket and tracing examples (patch 15 and 16) can be Ctrl-C in
the middle and kernel will auto cleanup everything including tracing filters.
----
Old V1 cover letter:
'maps' is a generic storage of different types for sharing data between kernel
and userspace. Maps are referrenced by file descriptor. Root process can create
multiple maps of different types where key/value are opaque bytes of data.
It's up to user space and eBPF program to decide what they store in the maps.
eBPF programs are similar to kernel modules. They are loaded by the user space
program and unload on closing of fd. Each program is a safe run-to-completion
set of instructions. eBPF verifier statically determines that the program
terminates and safe to execute. During verification the program takes a hold of
maps that it intends to use, so selected maps cannot be removed until program is
unloaded. The program can be attached to different events. These events can
be packets, tracepoint events and other types in the future. New event triggers
execution of the program which may store information about the event in the maps.
Beyond storing data the programs may call into in-kernel helper functions
which may, for example, dump stack, do trace_printk or other forms of live
kernel debugging. Same program can be attached to multiple events. Different
programs can access the same map:
tracepoint tracepoint tracepoint sk_buff sk_buff
event A event B event C on eth0 on eth1
| | | | |
| | | | |
--> tracing <-- tracing socket socket
prog_1 prog_2 prog_3 prog_4
| | | |
|--- -----| |-------| map_3
map_1 map_2
User space (via syscall) and eBPF programs access maps concurrently.
------
Alexei Starovoitov (26):
net: filter: add "load 64-bit immediate" eBPF instruction
net: filter: split filter.h and expose eBPF to user space
bpf: introduce syscall(BPF, ...) and BPF maps
bpf: enable bpf syscall on x64
bpf: add lookup/update/delete/iterate methods to BPF maps
bpf: add hashtable type of BPF maps
bpf: expand BPF syscall with program load/unload
bpf: handle pseudo BPF_CALL insn
bpf: verifier (add docs)
bpf: verifier (add ability to receive verification log)
bpf: handle pseudo BPF_LD_IMM64 insn
bpf: verifier (add branch/goto checks)
bpf: verifier (add verifier core)
bpf: verifier (add state prunning optimization)
bpf: allow eBPF programs to use maps
net: sock: allow eBPF programs to be attached to sockets
tracing: allow eBPF programs to be attached to events
tracing: allow eBPF programs to be attached to kprobe/kretprobe
samples: bpf: add mini eBPF library to manipulate maps and programs
samples: bpf: example of stateful socket filtering
samples: bpf: example of tracing filters with eBPF
bpf: llvm backend
samples: bpf: elf file loader
samples: bpf: eBPF example in C
samples: bpf: counting eBPF example in C
bpf: verifier test
--
1.7.9.5
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM
which loads 32-bit immediate value into a register.
x64 JITs it as single 'movabsq %rax, imm64'
arm64 may JIT as sequence of four 'movk x0, #imm16, lsl #shift' insn
Note that old eBPF programs are binary compatible with new interpreter.
Signed-off-by: Alexei Starovoitov <redacted>
---
Documentation/networking/filter.txt | 8 +++++++-
arch/x86/net/bpf_jit_comp.c | 9 +++++++++
include/linux/filter.h | 11 +++++++++++
kernel/bpf/core.c | 5 +++++
lib/test_bpf.c | 22 ++++++++++++++++++++++
5 files changed, 54 insertions(+), 1 deletion(-)
@@ -951,7 +951,7 @@ Size modifier is one of ... Mode modifier is one of:- BPF_IMM 0x00 /* classic BPF only, reserved in eBPF */+ BPF_IMM 0x00 /* used for 32-bit mov in classic BPF and 64-bit in eBPF */ BPF_ABS 0x20 BPF_IND 0x40 BPF_MEM 0x60
@@ -995,6 +995,12 @@ BPF_XADD | BPF_DW | BPF_STX: lock xadd *(u64 *)(dst_reg + off16) += src_reg Where size is one of: BPF_B or BPF_H or BPF_W or BPF_DW. Note that 1 and 2 byte atomic increments are not supported.+eBPF has one 16-byte instruction: BPF_LD | BPF_DW | BPF_IMM which consists+of two consecutive 'struct bpf_insn' 8-byte blocks and interpreted as single+instruction that loads 64-bit immediate value into a dst_reg.+Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM which loads+32-bit immediate value into a register.+ Testing -------
@@ -161,6 +161,17 @@ enum {.off=0,\.imm=IMM})+/* use two of BPF_LD_IMM64 to encode single move 64-bit insn+*firstmacrotocarrylower32-bitsandsecondforhigher32-bits+*/+#define BPF_LD_IMM64(DST, IMM) \+((structbpf_insn){\+.code=BPF_LD|BPF_DW|BPF_IMM,\+.dst_reg=DST,\+.src_reg=0,\+.off=0,\+.imm=IMM})+/* Short form of mov based on type, BPF_X: dst_reg = src_reg, BPF_K: dst_reg = imm32 */#define BPF_MOV64_RAW(TYPE, DST, SRC, IMM) \
BPF syscall is a demux for different BPF releated commands.
'maps' is a generic storage of different types for sharing data between kernel
and userspace.
The maps can be created from user space via BPF syscall:
- create a map with given type and attributes
fd = bpf_map_create(map_type, struct nlattr *attr, int len)
returns fd or negative error
- close(fd) deletes the map
Next patch allows userspace programs to populate/read maps that eBPF programs
are concurrently updating.
maps can have different types: hash, bloom filter, radix-tree, etc.
The map is defined by:
. type
. max number of elements
. key size in bytes
. value size in bytes
This patch establishes core infrastructure for BPF maps.
Next patches implement lookup/update and hashtable type.
More map types can be added in the future.
syscall is using type-length-value style of passing arguments to be backwards
compatible with future extensions to map attributes. Different map types may
use different attributes as well.
The concept of type-lenght-value is borrowed from netlink, but netlink itself
is not applicable here, since BPF programs and maps can be used in NET-less
configurations.
Signed-off-by: Alexei Starovoitov <redacted>
---
Documentation/networking/filter.txt | 71 ++++++++++++++++
include/linux/bpf.h | 42 ++++++++++
include/uapi/linux/bpf.h | 24 ++++++
kernel/bpf/Makefile | 2 +-
kernel/bpf/syscall.c | 156 +++++++++++++++++++++++++++++++++++
5 files changed, 294 insertions(+), 1 deletion(-)
create mode 100644 include/linux/bpf.h
create mode 100644 kernel/bpf/syscall.c
@@ -1001,6 +1001,77 @@ instruction that loads 64-bit immediate value into a dst_reg. Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM which loads 32-bit immediate value into a register.+eBPF maps+---------+'maps' is a generic storage of different types for sharing data between kernel+and userspace.++The maps are accessed from user space via BPF syscall, which has commands:+- create a map with given type and attributes+ map_fd = bpf_map_create(map_type, struct nlattr *attr, int len)+ returns process-local file descriptor or negative error++- lookup key in a given map+ err = bpf_map_lookup_elem(int fd, void *key, void *value)+ returns zero and stores found elem into value or negative error++- create or update key/value pair in a given map+ err = bpf_map_update_elem(int fd, void *key, void *value)+ returns zero or negative error++- find and delete element by key in a given map+ err = bpf_map_delete_elem(int fd, void *key)++- to delete map: close(fd)+ Exiting process will delete maps automatically++userspace programs uses this API to create/populate/read maps that eBPF programs+are concurrently updating.++maps can have different types: hash, array, bloom filter, radix-tree, etc.++The map is defined by:+ . type+ . max number of elements+ . key size in bytes+ . value size in bytes++The maps are accesible from eBPF program with API:+ void * bpf_map_lookup_elem(u32 map_fd, void *key);+ int bpf_map_update_elem(u32 map_fd, void *key, void *value);+ int bpf_map_delete_elem(u32 map_fd, void *key);++The kernel replaces process-local map_fd with kernel internal map pointer,+while loading eBPF program.++If eBPF verifier is configured to recognize extra calls in the program+bpf_map_lookup_elem() and bpf_map_update_elem() then access to maps looks like:+ ...+ ptr_to_value = bpf_map_lookup_elem(map_fd, key)+ access memory range [ptr_to_value, ptr_to_value + value_size_in_bytes)+ ...+ prepare key2 and value2 on stack of key_size and value_size+ err = bpf_map_update_elem(map_fd, key2, value2)+ ...++eBPF program cannot create or delete maps+(such calls will be unknown to verifier)++During program loading the refcnt of used maps is incremented, so they don't get+deleted while program is running++bpf_map_update_elem() can fail if maximum number of elements reached.+if key2 already exists, bpf_map_update_elem() replaces it with value2 atomically++bpf_map_lookup_elem() returns NULL or ptr_to_value, so program must do+if (ptr_to_value != NULL) check before accessing it.+NULL means that element with given 'key' was not found.++The verifier will check that the program accesses map elements within specified+size. It will not let programs pass junk values to bpf_map_*_elem() functions,+so these functions (implemented in C inside kernel) can safely access+the pointers in all cases.+ Testing -------
@@ -311,4 +311,28 @@ struct bpf_insn {__s32imm;/* signed immediate constant */};+/* BPF syscall commands */+enumbpf_cmd{+/* create a map with given type and attributes+*fd=bpf_map_create(bpf_map_type,structnlattr*attr,intlen)+*returnsfdornegativeerror+*mapisdeletedwhenfdisclosed+*/+BPF_MAP_CREATE,+};++enumbpf_map_attributes{+BPF_MAP_UNSPEC,+BPF_MAP_KEY_SIZE,/* size of key in bytes */+BPF_MAP_VALUE_SIZE,/* size of value in bytes */+BPF_MAP_MAX_ENTRIES,/* maximum number of entries in a map */+__BPF_MAP_ATTR_MAX,+};+#define BPF_MAP_ATTR_MAX (__BPF_MAP_ATTR_MAX - 1)+#define BPF_MAP_MAX_ATTR_SIZE 65535++enumbpf_map_type{+BPF_MAP_TYPE_UNSPEC,+};+#endif /* _UAPI__LINUX_BPF_H__ */
@@ -0,0 +1,156 @@+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,but+*WITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.SeetheGNU+*GeneralPublicLicenseformoredetails.+*/+#include<linux/bpf.h>+#include<linux/syscalls.h>+#include<net/netlink.h>+#include<linux/anon_inodes.h>++staticLIST_HEAD(bpf_map_types);++staticstructbpf_map*find_and_alloc_map(enumbpf_map_typetype,+structnlattr*tb[BPF_MAP_ATTR_MAX+1])+{+structbpf_map_type_list*tl;+structbpf_map*map;++list_for_each_entry(tl,&bpf_map_types,list_node){+if(tl->type==type){+map=tl->ops->map_alloc(tb);+if(IS_ERR(map))+returnmap;+map->ops=tl->ops;+map->map_type=type;+returnmap;+}+}+returnERR_PTR(-EINVAL);+}++/* boot time registration of different map implementations */+voidbpf_register_map_type(structbpf_map_type_list*tl)+{+list_add(&tl->list_node,&bpf_map_types);+}++/* called from workqueue */+staticvoidbpf_map_free_deferred(structwork_struct*work)+{+structbpf_map*map=container_of(work,structbpf_map,work);++/* implementation dependent freeing */+map->ops->map_free(map);+}++/* decrement map refcnt and schedule it for freeing via workqueue+*(unrelyingmapimplementationops->map_free()mightsleep)+*/+voidbpf_map_put(structbpf_map*map)+{+if(atomic_dec_and_test(&map->refcnt)){+INIT_WORK(&map->work,bpf_map_free_deferred);+schedule_work(&map->work);+}+}++staticintbpf_map_release(structinode*inode,structfile*filp)+{+structbpf_map*map=filp->private_data;++bpf_map_put(map);+return0;+}++staticconststructfile_operationsbpf_map_fops={+.release=bpf_map_release,+};++staticconststructnla_policymap_policy[BPF_MAP_ATTR_MAX+1]={+[BPF_MAP_KEY_SIZE]={.type=NLA_U32},+[BPF_MAP_VALUE_SIZE]={.type=NLA_U32},+[BPF_MAP_MAX_ENTRIES]={.type=NLA_U32},+};++/* called via syscall */+staticintmap_create(enumbpf_map_typetype,structnlattr__user*uattr,intlen)+{+structnlattr*tb[BPF_MAP_ATTR_MAX+1];+structbpf_map*map;+structnlattr*attr;+interr;++if(len<=0||len>BPF_MAP_MAX_ATTR_SIZE)+return-EINVAL;++attr=kmalloc(len,GFP_USER);+if(!attr)+return-ENOMEM;++/* copy map attributes from user space */+err=-EFAULT;+if(copy_from_user(attr,uattr,len)!=0)+gotofree_attr;++/* perform basic validation */+err=nla_parse(tb,BPF_MAP_ATTR_MAX,attr,len,map_policy);+if(err<0)+gotofree_attr;++/* find map type and init map: hashtable vs rbtree vs bloom vs ... */+map=find_and_alloc_map(type,tb);+if(IS_ERR(map)){+err=PTR_ERR(map);+gotofree_attr;+}++atomic_set(&map->refcnt,1);++err=anon_inode_getfd("bpf-map",&bpf_map_fops,map,O_RDWR|O_CLOEXEC);++if(err<0)+/* failed to allocate fd */+gotofree_map;++/* user supplied array of map attributes is no longer needed */+kfree(attr);++returnerr;++free_map:+map->ops->map_free(map);+free_attr:+kfree(attr);+returnerr;+}++SYSCALL_DEFINE5(bpf,int,cmd,unsignedlong,arg2,unsignedlong,arg3,+unsignedlong,arg4,unsignedlong,arg5)+{+/* eBPF syscall is limited to root temporarily. This restriction will+*beliftedwhenverifierhasenoughmileageandsecurityauditis+*clean.Notethattracing/networkinganalyticsusecaseswillbe+*turningoff'secure'modeofverifier,sincetheyneedtopass+*kerneldatabacktouserspace+*/+if(!capable(CAP_SYS_ADMIN))+return-EPERM;++if(arg5!=0)+return-EINVAL;++switch(cmd){+caseBPF_MAP_CREATE:+returnmap_create((enumbpf_map_type)arg2,+(structnlattr__user*)arg3,(int)arg4);+default:+return-EINVAL;+}+}
@@ -0,0 +1,372 @@+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,but+*WITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.SeetheGNU+*GeneralPublicLicenseformoredetails.+*/+#include<linux/bpf.h>+#include<net/netlink.h>+#include<linux/jhash.h>++structbpf_htab{+structbpf_mapmap;+structhlist_head*buckets;+structkmem_cache*elem_cache;+spinlock_tlock;+u32count;/* number of elements in this hashtable */+u32n_buckets;/* number of hash buckets */+u32elem_size;/* size of each element in bytes */+};++/* each htab element is struct htab_elem + key + value */+structhtab_elem{+structhlist_nodehash_node;+structrcu_headrcu;+structbpf_htab*htab;+u32hash;+u32pad;+charkey[0];+};++#define HASH_MAX_BUCKETS 1024+#define BPF_MAP_MAX_KEY_SIZE 256+staticstructbpf_map*htab_map_alloc(structnlattr*attr[BPF_MAP_ATTR_MAX+1])+{+structbpf_htab*htab;+interr,i;++htab=kzalloc(sizeof(*htab),GFP_USER);+if(!htab)+returnERR_PTR(-ENOMEM);++/* look for mandatory map attributes */+err=-EINVAL;+if(!attr[BPF_MAP_KEY_SIZE])+gotofree_htab;+htab->map.key_size=nla_get_u32(attr[BPF_MAP_KEY_SIZE]);++if(!attr[BPF_MAP_VALUE_SIZE])+gotofree_htab;+htab->map.value_size=nla_get_u32(attr[BPF_MAP_VALUE_SIZE]);++if(!attr[BPF_MAP_MAX_ENTRIES])+gotofree_htab;+htab->map.max_entries=nla_get_u32(attr[BPF_MAP_MAX_ENTRIES]);++htab->n_buckets=(htab->map.max_entries<=HASH_MAX_BUCKETS)?+htab->map.max_entries:HASH_MAX_BUCKETS;++/* hash table size must be power of 2 */+if((htab->n_buckets&(htab->n_buckets-1))!=0)+gotofree_htab;++err=-E2BIG;+if(htab->map.key_size>BPF_MAP_MAX_KEY_SIZE)+gotofree_htab;++err=-ENOMEM;+htab->buckets=kmalloc_array(htab->n_buckets,+sizeof(structhlist_head),GFP_USER);++if(!htab->buckets)+gotofree_htab;++for(i=0;i<htab->n_buckets;i++)+INIT_HLIST_HEAD(&htab->buckets[i]);++spin_lock_init(&htab->lock);+htab->count=0;++htab->elem_size=sizeof(structhtab_elem)++round_up(htab->map.key_size,8)++htab->map.value_size;++htab->elem_cache=kmem_cache_create("bpf_htab",htab->elem_size,0,0,+NULL);+if(!htab->elem_cache)+gotofree_buckets;++return&htab->map;++free_buckets:+kfree(htab->buckets);+free_htab:+kfree(htab);+returnERR_PTR(err);+}++staticinlineu32htab_map_hash(constvoid*key,u32key_len)+{+returnjhash(key,key_len,0);+}++staticinlinestructhlist_head*select_bucket(structbpf_htab*htab,u32hash)+{+return&htab->buckets[hash&(htab->n_buckets-1)];+}++staticstructhtab_elem*lookup_elem_raw(structhlist_head*head,u32hash,+void*key,u32key_size)+{+structhtab_elem*l;++hlist_for_each_entry_rcu(l,head,hash_node){+if(l->hash==hash&&!memcmp(&l->key,key,key_size))+returnl;+}+returnNULL;+}++/* Must be called with rcu_read_lock. */+staticvoid*htab_map_lookup_elem(structbpf_map*map,void*key)+{+structbpf_htab*htab=container_of(map,structbpf_htab,map);+structhlist_head*head;+structhtab_elem*l;+u32hash,key_size;++WARN_ON_ONCE(!rcu_read_lock_held());++key_size=map->key_size;++hash=htab_map_hash(key,key_size);++head=select_bucket(htab,hash);++l=lookup_elem_raw(head,hash,key,key_size);++if(l)+returnl->key+round_up(map->key_size,8);+else+returnNULL;+}++/* Must be called with rcu_read_lock. */+staticinthtab_map_get_next_key(structbpf_map*map,void*key,void*next_key)+{+structbpf_htab*htab=container_of(map,structbpf_htab,map);+structhlist_head*head;+structhtab_elem*l,*next_l;+u32hash,key_size;+inti;++WARN_ON_ONCE(!rcu_read_lock_held());++key_size=map->key_size;++hash=htab_map_hash(key,key_size);++head=select_bucket(htab,hash);++/* lookup the key */+l=lookup_elem_raw(head,hash,key,key_size);++if(!l){+i=0;+gotofind_first_elem;+}++/* key was found, get next key in the same bucket */+next_l=hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(&l->hash_node)),+structhtab_elem,hash_node);++if(next_l){+/* if next elem in this hash list is non-zero, just return it */+memcpy(next_key,next_l->key,key_size);+return0;+}else{+/* no more elements in this hash list, go to the next bucket */+i=hash&(htab->n_buckets-1);+i++;+}++find_first_elem:+/* iterate over buckets */+for(;i<htab->n_buckets;i++){+head=select_bucket(htab,i);++/* pick first element in the bucket */+next_l=hlist_entry_safe(rcu_dereference_raw(hlist_first_rcu(head)),+structhtab_elem,hash_node);+if(next_l){+/* if it's not empty, just return it */+memcpy(next_key,next_l->key,key_size);+return0;+}+}++/* itereated over all buckets and all elements */+return-ENOENT;+}++staticstructhtab_elem*htab_alloc_elem(structbpf_htab*htab)+{+void*l;++l=kmem_cache_alloc(htab->elem_cache,GFP_ATOMIC);+if(!l)+returnERR_PTR(-ENOMEM);+returnl;+}++staticvoidfree_htab_elem_rcu(structrcu_head*rcu)+{+structhtab_elem*l=container_of(rcu,structhtab_elem,rcu);++kmem_cache_free(l->htab->elem_cache,l);+}++staticvoidrelease_htab_elem(structbpf_htab*htab,structhtab_elem*l)+{+l->htab=htab;+call_rcu(&l->rcu,free_htab_elem_rcu);+}++/* Must be called with rcu_read_lock. */+staticinthtab_map_update_elem(structbpf_map*map,void*key,void*value)+{+structbpf_htab*htab=container_of(map,structbpf_htab,map);+structhtab_elem*l_new,*l_old;+structhlist_head*head;+unsignedlongflags;+u32key_size;++WARN_ON_ONCE(!rcu_read_lock_held());++l_new=htab_alloc_elem(htab);+if(IS_ERR(l_new))+return-ENOMEM;++key_size=map->key_size;++memcpy(l_new->key,key,key_size);+memcpy(l_new->key+round_up(key_size,8),value,map->value_size);++l_new->hash=htab_map_hash(l_new->key,key_size);++/* bpf_map_update_elem() can be called in_irq() as well, so+*spin_lock()orspin_lock_bh()cannotbeused+*/+spin_lock_irqsave(&htab->lock,flags);++head=select_bucket(htab,l_new->hash);++l_old=lookup_elem_raw(head,l_new->hash,key,key_size);++if(!l_old&&unlikely(htab->count>=map->max_entries)){+/* if elem with this 'key' doesn't exist and we've reached+*max_entrieslimit,failinsertionofnewelem+*/+spin_unlock_irqrestore(&htab->lock,flags);+kmem_cache_free(htab->elem_cache,l_new);+return-EFBIG;+}++/* add new element to the head of the list, so that concurrent+*searchwillfinditbeforeoldelem+*/+hlist_add_head_rcu(&l_new->hash_node,head);+if(l_old){+hlist_del_rcu(&l_old->hash_node);+release_htab_elem(htab,l_old);+}else{+htab->count++;+}+spin_unlock_irqrestore(&htab->lock,flags);++return0;+}++/* Must be called with rcu_read_lock. */+staticinthtab_map_delete_elem(structbpf_map*map,void*key)+{+structbpf_htab*htab=container_of(map,structbpf_htab,map);+structhlist_head*head;+structhtab_elem*l;+unsignedlongflags;+u32hash,key_size;+intret=-ESRCH;++WARN_ON_ONCE(!rcu_read_lock_held());++key_size=map->key_size;++hash=htab_map_hash(key,key_size);++spin_lock_irqsave(&htab->lock,flags);++head=select_bucket(htab,hash);++l=lookup_elem_raw(head,hash,key,key_size);++if(l){+hlist_del_rcu(&l->hash_node);+htab->count--;+release_htab_elem(htab,l);+ret=0;+}++spin_unlock_irqrestore(&htab->lock,flags);+returnret;+}++staticvoiddelete_all_elements(structbpf_htab*htab)+{+inti;++for(i=0;i<htab->n_buckets;i++){+structhlist_head*head=select_bucket(htab,i);+structhlist_node*n;+structhtab_elem*l;++hlist_for_each_entry_safe(l,n,head,hash_node){+hlist_del_rcu(&l->hash_node);+htab->count--;+kmem_cache_free(htab->elem_cache,l);+}+}+}++/* called when map->refcnt goes to zero */+staticvoidhtab_map_free(structbpf_map*map)+{+structbpf_htab*htab=container_of(map,structbpf_htab,map);++/* wait for all outstanding updates to complete */+synchronize_rcu();++/* kmem_cache_free all htab elements */+delete_all_elements(htab);++/* and destroy cache, which might sleep */+kmem_cache_destroy(htab->elem_cache);++kfree(htab->buckets);+kfree(htab);+}++staticstructbpf_map_opshtab_ops={+.map_alloc=htab_map_alloc,+.map_free=htab_map_free,+.map_get_next_key=htab_map_get_next_key,+.map_lookup_elem=htab_map_lookup_elem,+.map_update_elem=htab_map_update_elem,+.map_delete_elem=htab_map_delete_elem,+};++staticstructbpf_map_type_listtl={+.ops=&htab_ops,+.type=BPF_MAP_TYPE_HASH,+};++staticint__initregister_htab_map(void)+{+bpf_register_map_type(&tl);+return0;+}+late_initcall(register_htab_map);
in native eBPF programs userspace is using pseudo BPF_CALL instructions
which encode one of 'enum bpf_func_id' inside insn->imm field.
Verifier checks that program using correct function arguments to given func_id.
If all checks passed, kernel needs to fixup BPF_CALL->imm fields by
replacing func_id with in-kernel function pointer.
eBPF interpreter just calls the function.
In-kernel eBPF users continue to use generic BPF_CALL.
Signed-off-by: Alexei Starovoitov <redacted>
---
kernel/bpf/syscall.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
@@ -338,6 +338,40 @@ void bpf_register_prog_type(struct bpf_prog_type_list *tl)list_add(&tl->list_node,&bpf_prog_types);}+/* fixup insn->imm field of bpf_call instructions:+*if(insn->imm==BPF_FUNC_map_lookup_elem)+*insn->imm=bpf_map_lookup_elem-__bpf_call_base;+*elseif(insn->imm==BPF_FUNC_map_update_elem)+*insn->imm=bpf_map_update_elem-__bpf_call_base;+*else...+*+*thisfunctioniscalledaftereBPFprogrampassedverification+*/+staticvoidfixup_bpf_calls(structbpf_prog*prog)+{+conststructbpf_func_proto*fn;+inti;++for(i=0;i<prog->len;i++){+structbpf_insn*insn=&prog->insnsi[i];++if(insn->code==(BPF_JMP|BPF_CALL)){+/* we reach here when program has bpf_call instructions+*anditpassedbpf_check(),meansthat+*ops->get_func_protomusthavebeensupplied,checkit+*/+BUG_ON(!prog->info->ops->get_func_proto);++fn=prog->info->ops->get_func_proto(insn->imm);+/* all functions that have prototype and verifier allowed+*programstocallthem,mustberealin-kernelfunctions+*/+BUG_ON(!fn->func);+insn->imm=fn->func-__bpf_call_base;+}+}+}+/* drop refcnt on maps used by eBPF program and free auxilary data */staticvoidfree_bpf_prog_info(structbpf_prog_info*info){
@@ -485,6 +519,9 @@ static int bpf_prog_load(enum bpf_prog_type type, struct nlattr __user *uattr,if(err<0)gotofree_prog_info;+/* fixup BPF_CALL->imm field */+fixup_bpf_calls(prog);+/* eBPF program is ready to be JITed */bpf_prog_select_runtime(prog);
eBPF programs passed from userspace are using pseudo BPF_LD_IMM64 instructions
to refer to process-local map_fd. Scan the program for such instructions and
if FDs are valid, convert them to 'struct bpf_map' pointers which will be used
by verifier to check access to maps in bpf_map_lookup/update() calls.
If program passes verifier, convert pseudo BPF_LD_IMM64 into generic by dropping
BPF_PSEUDO_MAP_FD flag.
Note that eBPF interpreter is generic and knows nothing about pseudo insns.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
include/uapi/linux/bpf.h | 12 ++++
kernel/bpf/verifier.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
@@ -143,10 +143,15 @@*load/storetobpf_contextarecheckedagainstknownfields*/+#define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */+/* single container for all structs*oneverifier_envperbpf_check()call*/structverifier_env{+structbpf_prog*prog;/* eBPF program being verified */+structbpf_map*used_maps[MAX_USED_MAPS];/* array of map's used by eBPF program */+u32used_map_cnt;/* number of used maps */};/* verbose verifier prints what it's seeing
@@ -318,6 +323,114 @@ static void print_bpf_insn(struct bpf_insn *insn)}}+/* return the map pointer stored inside BPF_LD_IMM64 instruction */+staticstructbpf_map*ld_imm64_to_map_ptr(structbpf_insn*insn)+{+u64imm64=((u64)(u32)insn[0].imm)|((u64)(u32)insn[1].imm)<<32;++return(structbpf_map*)(unsignedlong)imm64;+}++/* look for pseudo eBPF instructions that access map FDs and+*replacethemwithactualmappointers+*/+staticintreplace_map_fd_with_map_ptr(structverifier_env*env)+{+structbpf_insn*insn=env->prog->insnsi;+intinsn_cnt=env->prog->len;+inti,j;++for(i=0;i<insn_cnt;i++,insn++){+if(insn[0].code==(BPF_LD|BPF_IMM|BPF_DW)){+structbpf_map*map;+structfdf;++if(i==insn_cnt-1||+insn[1].code!=(BPF_LD|BPF_IMM|BPF_DW)){+verbose("invalid bpf_ld_imm64 insn\n");+return-EINVAL;+}++if(insn->src_reg==0)+/* valid generic load 64-bit imm */+gotonext_insn;++if(insn->src_reg!=BPF_PSEUDO_MAP_FD){+verbose("unrecognized bpf_ld_imm64 insn\n");+return-EINVAL;+}++f=fdget(insn->imm);++map=bpf_map_get(f);+if(IS_ERR(map)){+verbose("fd %d is not pointing to valid bpf_map\n",+insn->imm);+fdput(f);+returnPTR_ERR(map);+}++/* store map pointer inside BPF_LD_IMM64 instruction */+insn[0].imm=(u32)(unsignedlong)map;+insn[1].imm=((u64)(unsignedlong)map)>>32;++/* check whether we recorded this map already */+for(j=0;j<env->used_map_cnt;j++)+if(env->used_maps[j]==map){+fdput(f);+gotonext_insn;+}++if(env->used_map_cnt>=MAX_USED_MAPS){+fdput(f);+return-E2BIG;+}++/* remember this map */+env->used_maps[env->used_map_cnt++]=map;++/* hold the map. If the program is rejected by verifier,+*themapwillbereleasedbyrelease_maps()orit+*willbeusedbythevalidprogramuntilit'sunloaded+*andallmapsarereleasedinfree_bpf_prog_info()+*/+atomic_inc(&map->refcnt);++fdput(f);+next_insn:+insn++;+i++;+}+}++/* now all pseudo BPF_LD_IMM64 instructions load valid+*'structbpf_map*'intoaregisterinsteadofusermap_fd.+*Thesepointerswillbeusedlaterbyverifiertovalidatemapaccess.+*/+return0;+}++/* drop refcnt of maps used by the rejected program */+staticvoidrelease_maps(structverifier_env*env)+{+inti;++for(i=0;i<env->used_map_cnt;i++)+bpf_map_put(env->used_maps[i]);+}++/* convert pseudo BPF_LD_IMM64 into generic BPF_LD_IMM64 */+staticvoidconvert_pseudo_ld_imm64(structverifier_env*env)+{+structbpf_insn*insn=env->prog->insnsi;+intinsn_cnt=env->prog->len;+inti;++for(i=0;i<insn_cnt;i++,insn++)+if(insn->code==(BPF_LD|BPF_IMM|BPF_DW))+insn->src_reg=0;+}+intbpf_check(structbpf_prog*prog,structnlattr*tb[BPF_PROG_ATTR_MAX+1]){void__user*log_ubuf=NULL;
@@ -334,6 +447,8 @@ int bpf_check(struct bpf_prog *prog, struct nlattr *tb[BPF_PROG_ATTR_MAX + 1])if(!env)return-ENOMEM;+env->prog=prog;+/* grab the mutex to protect few globals used by verifier */mutex_lock(&bpf_verifier_lock);
@@ -361,8 +476,14 @@ int bpf_check(struct bpf_prog *prog, struct nlattr *tb[BPF_PROG_ATTR_MAX + 1])log_level=0;}+ret=replace_map_fd_with_map_ptr(env);+if(ret<0)+gotoskip_full_check;+/* ret = do_check(env); */+skip_full_check:+if(log_level&&log_len>=log_size-1){BUG_ON(log_len>=log_size);/* verifier log exceeded user supplied buffer */
@@ -376,11 +497,36 @@ int bpf_check(struct bpf_prog *prog, struct nlattr *tb[BPF_PROG_ATTR_MAX + 1])gotofree_log_buf;}+if(ret==0&&env->used_map_cnt){+/* if program passed verifier, update used_maps in bpf_prog_info */+prog->info->used_maps=kmalloc_array(env->used_map_cnt,+sizeof(env->used_maps[0]),+GFP_KERNEL);++if(!prog->info->used_maps){+ret=-ENOMEM;+gotofree_log_buf;+}++memcpy(prog->info->used_maps,env->used_maps,+sizeof(env->used_maps[0])*env->used_map_cnt);+prog->info->used_map_cnt=env->used_map_cnt;++/* program is valid. Convert pseudo bpf_ld_imm64 into generic+*bpf_ld_imm64instructions+*/+convert_pseudo_ld_imm64(env);+}free_log_buf:if(log_level)vfree(log_buf);free_env:+if(!prog->info->used_maps)+/* if we didn't copy map pointers into bpf_prog_info, release+*themnow.Otherwisefree_bpf_prog_info()willreleasethem.+*/+release_maps(env);kfree(env);mutex_unlock(&bpf_verifier_lock);returnret;
add optional attributes for BPF_PROG_LOAD syscall:
BPF_PROG_LOG_LEVEL, /* verbosity level of eBPF verifier */
BPF_PROG_LOG_BUF, /* user supplied buffer */
BPF_PROG_LOG_SIZE, /* size of user buffer */
In such case the verifier will return its verification log in the user
supplied buffer which can be used by humans to analyze why verifier
rejected given program
Signed-off-by: Alexei Starovoitov <redacted>
---
include/uapi/linux/bpf.h | 4 +
kernel/bpf/syscall.c | 3 +
kernel/bpf/verifier.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 243 insertions(+)
since verifier walks all possible paths it's important to recognize
equivalent verifier states to speed up verification.
If one of the old states is more strict than the current state, it means
the current state doesn't need to be explored further, since verifier already
concluded that more strict state leads to valid bpf_exit.
Signed-off-by: Alexei Starovoitov <redacted>
---
kernel/bpf/verifier.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
@@ -219,6 +219,7 @@ struct verifier_env {structverifier_stack_elem*head;/* stack of verifier states to be processed */intstack_size;/* number of states to be processed */structverifier_statecur_state;/* current verifier state */+structverifier_state_list**branch_landing;/* search prunning optimization */structbpf_map*used_maps[MAX_USED_MAPS];/* array of map's used by eBPF program */u32used_map_cnt;/* number of used maps */};
@@ -1248,6 +1251,9 @@ enum {ret=-EINVAL;\gotofree_st;\}\+if(E==BRANCH)\+/* mark branch target for state pruning */\+env->branch_landing[w]=STATE_END;\if(st[w]==0){\/* tree-edge */\st[T]=DISCOVERED|E;\
@@ -1315,6 +1321,10 @@ peek_stack:PUSH_INSN(t,t+1,FALLTHROUGH);PUSH_INSN(t,t+insns[t].off+1,BRANCH);}+/* tell verifier to check for equivalent verifier states+*aftereverycallandjump+*/+env->branch_landing[t+1]=STATE_END;}else{/* all other non-branch instructions with single*fall-throughedge
@@ -1346,6 +1356,88 @@ free_st:returnret;}+/* compare two verifier states+*+*allstatesstoredinstate_listareknowntobevalid,since+*verifierreached'bpf_exit'instructionthroughthem+*+*thisfunctioniscalledwhenverifierexploringdifferentbranchesof+*executionpoppedfromthestatestack.Ifitseesanoldstatethathas+*morestrictregisterstateandmorestrictstackstatethenthisexecution+*branchdoesn'tneedtobeexploredfurther,sinceverifieralready+*concludedthatmorestrictstateleadstovalidfinish.+*+*Thereforetwostatesareequivalentifregisterstateismoreconservative+*andexploredstackstateismoreconservativethanthecurrentone.+*Example:+*exploredcurrent+*(slot1=INVslot2=MISC)==(slot1=MISCslot2=MISC)+*(slot1=MISCslot2=MISC)!=(slot1=INVslot2=MISC)+*+*Inotherwordsifcurrentstackstate(onebeingexplored)hasmore+*validslotsthanoldonethatalreadypassedvalidation,itmeans+*theverifiercanstopexploringandconcludethatcurrentstateisvalidtoo+*+*Similarlywithregisters.Ifexploredstatehasregistertypeasinvalid+*whereasregistertypeincurrentstateismeaningful,itmeansthat+*thecurrentstatewillreach'bpf_exit'instructionsafely+*/+staticboolstates_equal(structverifier_state*old,structverifier_state*cur)+{+inti;++for(i=0;i<MAX_BPF_REG;i++){+if(memcmp(&old->regs[i],&cur->regs[i],+sizeof(old->regs[0]))!=0){+if(old->regs[i].type==NOT_INIT||+old->regs[i].type==UNKNOWN_VALUE)+continue;+returnfalse;+}+}++for(i=0;i<MAX_BPF_STACK;i++){+if(memcmp(&old->stack[i],&cur->stack[i],+sizeof(old->stack[0]))!=0){+if(old->stack[i].stype==STACK_INVALID)+continue;+returnfalse;+}+}+returntrue;+}++staticintis_state_visited(structverifier_env*env,intinsn_idx)+{+structverifier_state_list*new_sl;+structverifier_state_list*sl;++sl=env->branch_landing[insn_idx];+if(!sl)+/* no branch jump to this insn, ignore it */+return0;++while(sl!=STATE_END){+if(states_equal(&sl->state,&env->cur_state))+/* reached equivalent register/stack state,+*prunethesearch+*/+return1;+sl=sl->next;+}+new_sl=kmalloc(sizeof(structverifier_state_list),GFP_KERNEL);++if(!new_sl)+/* ignore ENOMEM, it doesn't affect correctness */+return0;++/* add new state to the head of linked list */+memcpy(&new_sl->state,&env->cur_state,sizeof(env->cur_state));+new_sl->next=env->branch_landing[insn_idx];+env->branch_landing[insn_idx]=new_sl;+return0;+}+staticintdo_check(structverifier_env*env){structverifier_state*state=&env->cur_state;
@@ -1377,6 +1469,17 @@ static int do_check(struct verifier_env *env)return-E2BIG;}+if(is_state_visited(env,insn_idx)){+if(log_level){+if(do_print_state)+verbose("\nfrom %d to %d: safe\n",+prev_insn_idx,insn_idx);+else+verbose("%d: safe\n",insn_idx);+}+gotoprocess_bpf_exit;+}+if(log_level&&do_print_state){verbose("\nfrom %d to %d:",prev_insn_idx,insn_idx);print_verifier_state(env);
@@ -1464,6 +1567,7 @@ static int do_check(struct verifier_env *env)*somethingintoitearlier*/_(check_reg_arg(regs,BPF_REG_0,1));+process_bpf_exit:insn_idx=pop_stack(env,&prev_insn_idx);if(insn_idx<0){break;
@@ -1120,6 +1125,132 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)}EXPORT_SYMBOL_GPL(sk_attach_filter);+intsk_attach_filter_ebpf(u32ufd,structsock*sk)+{+structsk_filter*fp,*old_fp;+structbpf_prog*prog;++if(sock_flag(sk,SOCK_FILTER_LOCKED))+return-EPERM;++prog=bpf_prog_get(ufd);+if(!prog)+return-EINVAL;++if(prog->info->prog_type!=BPF_PROG_TYPE_SOCKET_FILTER){+/* valid fd, but invalid program type */+bpf_prog_put(prog);+return-EINVAL;+}++fp=kmalloc(sizeof(*fp),GFP_KERNEL);+if(!fp){+bpf_prog_put(prog);+return-ENOMEM;+}+fp->prog=prog;++atomic_set(&fp->refcnt,0);++if(!sk_filter_charge(sk,fp)){+__sk_filter_release(fp);+return-ENOMEM;+}++old_fp=rcu_dereference_protected(sk->sk_filter,+sock_owned_by_user(sk));+rcu_assign_pointer(sk->sk_filter,fp);++if(old_fp)+sk_filter_uncharge(sk,old_fp);++return0;+}++staticstructbpf_func_protosock_filter_funcs[]={+[BPF_FUNC_map_lookup_elem]={+.func=bpf_map_lookup_elem,+.gpl_only=false,+.ret_type=RET_PTR_TO_MAP_VALUE_OR_NULL,+.arg1_type=ARG_CONST_MAP_PTR,+.arg2_type=ARG_PTR_TO_MAP_KEY,+},+[BPF_FUNC_map_update_elem]={+.func=bpf_map_update_elem,+.gpl_only=false,+.ret_type=RET_INTEGER,+.arg1_type=ARG_CONST_MAP_PTR,+.arg2_type=ARG_PTR_TO_MAP_KEY,+.arg3_type=ARG_PTR_TO_MAP_VALUE,+},+[BPF_FUNC_map_delete_elem]={+.func=bpf_map_delete_elem,+.gpl_only=false,+.ret_type=RET_INTEGER,+.arg1_type=ARG_CONST_MAP_PTR,+.arg2_type=ARG_PTR_TO_MAP_KEY,+},+};++/* allow socket filters to call+*bpf_map_lookup_elem(),bpf_map_update_elem(),bpf_map_delete_elem()+*/+staticconststructbpf_func_proto*sock_filter_func_proto(enumbpf_func_idfunc_id)+{+if(func_id<0||func_id>=ARRAY_SIZE(sock_filter_funcs))+returnNULL;+return&sock_filter_funcs[func_id];+}++staticconststructbpf_context_access{+intsize;+enumbpf_access_typetype;+}sock_filter_ctx_access[]={+[offsetof(structsk_buff,mark)]={+FIELD_SIZEOF(structsk_buff,mark),BPF_READ+},+[offsetof(structsk_buff,protocol)]={+FIELD_SIZEOF(structsk_buff,protocol),BPF_READ+},+[offsetof(structsk_buff,queue_mapping)]={+FIELD_SIZEOF(structsk_buff,queue_mapping),BPF_READ+},+};++/* allow socket filters to access to 'mark', 'protocol' and 'queue_mapping'+*fieldsof'structsk_buff'+*/+staticboolsock_filter_is_valid_access(intoff,intsize,enumbpf_access_typetype)+{+conststructbpf_context_access*access;++if(off<0||off>=ARRAY_SIZE(sock_filter_ctx_access))+returnfalse;++access=&sock_filter_ctx_access[off];+if(access->size==size&&(access->type&type))+returntrue;++returnfalse;+}++staticstructbpf_verifier_opssock_filter_ops={+.get_func_proto=sock_filter_func_proto,+.is_valid_access=sock_filter_is_valid_access,+};++staticstructbpf_prog_type_listtl={+.ops=&sock_filter_ops,+.type=BPF_PROG_TYPE_SOCKET_FILTER,+};++staticint__initregister_sock_filter_ops(void)+{+bpf_register_prog_type(&tl);+return0;+}+late_initcall(register_sock_filter_ops);+intsk_detach_filter(structsock*sk){intret=-ENOENT;
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
where 123 is process local FD associated with eBPF program previously loaded.
__event__ is static tracepoint event or syscall.
(kprobe support is in next patch)
Once program is successfully attached to tracepoint event, the tracepoint
will be auto-enabled
close(fd)
auto-disables tracepoint event and detaches eBPF program from it
eBPF programs can call in-kernel helper functions to:
- lookup/update/delete elements in maps
- memcmp
- trace_printk
- dump_stack
- fetch_ptr/u64/u32/u16/u8 values from unsafe address via probe_kernel_read(),
so that eBPF program can walk any kernel data structures
Signed-off-by: Alexei Starovoitov <redacted>
---
include/linux/ftrace_event.h | 5 +
include/trace/bpf_trace.h | 30 +++++
include/trace/ftrace.h | 10 ++
include/uapi/linux/bpf.h | 9 ++
kernel/trace/Kconfig | 1 +
kernel/trace/Makefile | 1 +
kernel/trace/bpf_trace.c | 241 ++++++++++++++++++++++++++++++++++++
kernel/trace/trace.h | 3 +
kernel/trace/trace_events.c | 36 +++++-
kernel/trace/trace_events_filter.c | 72 ++++++++++-
kernel/trace/trace_syscalls.c | 18 +++
11 files changed, 424 insertions(+), 2 deletions(-)
create mode 100644 include/trace/bpf_trace.h
create mode 100644 kernel/trace/bpf_trace.c
@@ -0,0 +1,30 @@+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*/+#ifndef _LINUX_KERNEL_BPF_TRACE_H+#define _LINUX_KERNEL_BPF_TRACE_H++/* For tracing filters save first six arguments of tracepoint events.+*On64-bitarchitecturesargNfieldswillmatchonetoonetoargumentspassed+*totracepointevents.+*On32-bitarchitecturesu64argumentstoeventswillbeseenintotwo+*consecutiveargN,argN+1fields.Pointers,u32,u16,u8,booltypeswill+*matchonetoone+*/+structbpf_context{+unsignedlongarg1;+unsignedlongarg2;+unsignedlongarg3;+unsignedlongarg4;+unsignedlongarg5;+unsignedlongarg6;+unsignedlongret;+};++/* call from ftrace_raw_event_*() to copy tracepoint arguments into ctx */+voidpopulate_bpf_context(structbpf_context*ctx,...);++#endif /* _LINUX_KERNEL_BPF_TRACE_H */
@@ -396,6 +396,7 @@ enum bpf_prog_attributes {enumbpf_prog_type{BPF_PROG_TYPE_UNSPEC,BPF_PROG_TYPE_SOCKET_FILTER,+BPF_PROG_TYPE_TRACING_FILTER,};/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -406,6 +407,14 @@ enum bpf_func_id {BPF_FUNC_map_lookup_elem,/* void *map_lookup_elem(&map, &key) */BPF_FUNC_map_update_elem,/* int map_update_elem(&map, &key, &value) */BPF_FUNC_map_delete_elem,/* int map_delete_elem(&map, &key) */+BPF_FUNC_fetch_ptr,/* void *bpf_fetch_ptr(void *unsafe_ptr) */+BPF_FUNC_fetch_u64,/* u64 bpf_fetch_u64(void *unsafe_ptr) */+BPF_FUNC_fetch_u32,/* u32 bpf_fetch_u32(void *unsafe_ptr) */+BPF_FUNC_fetch_u16,/* u16 bpf_fetch_u16(void *unsafe_ptr) */+BPF_FUNC_fetch_u8,/* u8 bpf_fetch_u8(void *unsafe_ptr) */+BPF_FUNC_memcmp,/* int bpf_memcmp(void *unsafe_ptr, void *safe_ptr, int size) */+BPF_FUNC_dump_stack,/* void bpf_dump_stack(void) */+BPF_FUNC_printk,/* int bpf_printk(const char *fmt, int fmt_size, ...) */__BPF_FUNC_MAX_ID,};
@@ -1051,6 +1051,26 @@ event_filter_read(struct file *filp, char __user *ubuf, size_t cnt,returnr;}+staticintevent_filter_release(structinode*inode,structfile*filp)+{+structftrace_event_file*file;+charbuf[2]="0";++mutex_lock(&event_mutex);+file=event_file_data(filp);+if(file){+if(file->event_call->flags&TRACE_EVENT_FL_BPF){+/* auto-disable the filter */+ftrace_event_enable_disable(file,0);++/* if BPF filter was used, clear it on fd close */+apply_event_filter(file,buf);+}+}+mutex_unlock(&event_mutex);+return0;+}+staticssize_tevent_filter_write(structfile*filp,constchar__user*ubuf,size_tcnt,loff_t*ppos)
@@ -1074,10 +1094,23 @@ event_filter_write(struct file *filp, const char __user *ubuf, size_t cnt,mutex_lock(&event_mutex);file=event_file_data(filp);-if(file)+if(file){err=apply_event_filter(file,buf);+if(!err&&file->event_call->flags&TRACE_EVENT_FL_BPF)+/* once filter is applied, auto-enable it */+ftrace_event_enable_disable(file,1);+}+mutex_unlock(&event_mutex);+if(file&&file->event_call->flags&TRACE_EVENT_FL_BPF){+/*+*allocateper-cpuprintkbuffers,sinceeBPFprogram+*mightbecallingbpf_trace_printk+*/+trace_printk_init_buffers();+}+free_page((unsignedlong)buf);if(err<0)returnerr;
@@ -1857,6 +1872,48 @@ static int create_filter_start(char *filter_str, bool set_str,returnerr;}+staticintcreate_filter_bpf(char*filter_str,structevent_filter**filterp)+{+structevent_filter*filter;+structbpf_prog*prog;+longufd;+interr=0;++*filterp=NULL;++filter=__alloc_filter();+if(!filter)+return-ENOMEM;++err=replace_filter_string(filter,filter_str);+if(err)+gotofree_filter;++err=kstrtol(filter_str+4,0,&ufd);+if(err)+gotofree_filter;++err=-ESRCH;+prog=bpf_prog_get(ufd);+if(!prog)+gotofree_filter;++filter->prog=prog;++err=-EINVAL;+if(prog->info->prog_type!=BPF_PROG_TYPE_TRACING_FILTER)+/* prog_id is valid, but it's not a tracing filter program */+gotofree_filter;++*filterp=filter;++return0;++free_filter:+__free_filter(filter);+returnerr;+}+staticvoidcreate_filter_finish(structfilter_parse_state*ps){if(ps){
@@ -1966,7 +2023,20 @@ int apply_event_filter(struct ftrace_event_file *file, char *filter_string)return0;}-err=create_filter(call,filter_string,true,&filter);+/*+*'bpf_123'stringisarequesttoattacheBPFprogramwithid==123+*alsoaccept'bpf123','bpf.123','bpf-123'variants+*/+if(memcmp(filter_string,"bpf",3)==0&&filter_string[3]!=0&&+filter_string[4]!=0){+err=create_filter_bpf(filter_string,&filter);+if(!err)+call->flags|=TRACE_EVENT_FL_BPF;+}else{+err=create_filter(call,filter_string,true,&filter);+if(!err)+call->flags&=~TRACE_EVENT_FL_BPF;+}/**Alwaysswapthecallfilterwiththenewfilter
simple packet drop monitor:
- in-kernel eBPF program attaches to kfree_skb() event and records number
of packet drops at given location
- userspace iterates over the map every second and prints stats
Signed-off-by: Alexei Starovoitov <redacted>
---
samples/bpf/Makefile | 3 +-
samples/bpf/dropmon.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 samples/bpf/dropmon.c
@@ -2,9 +2,10 @@obj-:=dummy.o# List of programs to build-hostprogs-y:=sock_example+hostprogs-y:=sock_exampledropmonsock_example-objs:=sock_example.olibbpf.o+dropmon-objs:=dropmon.olibbpf.o# Tell kbuild to always build the programsalways:=$(hostprogs-y)
simple .o parser and loader using BPF syscall.
.o is a standard ELF generated by LLVM backend
It parses elf file compiled by llvm .c->.o
- parses 'maps' section and creates maps via BPF syscall
- parses 'license' section and passes it to syscall
- parses elf relocations for BPF maps and adjusts BPF_LD_IMM64 insns
by storing map_fd into insn->imm and marking such insns as BPF_PSEUDO_MAP_FD
- loads eBPF program via BPF syscall
- attaches program FD to tracepoint events
One ELF file can contain multiple BPF programs attached to multiple
tracepoint events
int load_bpf_file(char *path);
bpf_helpers.h is a set of in-kernel helper functions available to eBPF programs
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
samples/bpf/bpf_helpers.h | 25 +++++
samples/bpf/bpf_load.c | 232 +++++++++++++++++++++++++++++++++++++++++++++
samples/bpf/bpf_load.h | 26 +++++
3 files changed, 283 insertions(+)
create mode 100644 samples/bpf/bpf_helpers.h
create mode 100644 samples/bpf/bpf_load.c
create mode 100644 samples/bpf/bpf_load.h
@@ -0,0 +1,232 @@+#include<stdio.h>+#include<sys/types.h>+#include<sys/stat.h>+#include<fcntl.h>+#include<libelf.h>+#include<gelf.h>+#include<errno.h>+#include<unistd.h>+#include<string.h>+#include<stdbool.h>+#include<linux/bpf.h>+#include<linux/filter.h>+#include"libbpf.h"+#include"bpf_helpers.h"+#include"bpf_load.h"++#define DEBUGFS "/sys/kernel/debug/tracing/"++staticcharlicense[128];+staticboolprocessed_sec[128];+intmap_fd[MAX_MAPS];++staticintload_and_attach(constchar*event,structbpf_insn*prog,intsize)+{+intfd,event_fd,err;+charfmt[32];+charpath[256]=DEBUGFS;++fd=bpf_prog_load(BPF_PROG_TYPE_TRACING_FILTER,prog,size,license);++if(fd<0){+printf("err %d errno %d\n",fd,errno);+returnfd;+}++snprintf(fmt,sizeof(fmt),"bpf-%d",fd);++strcat(path,event);+strcat(path,"/filter");++printf("writing %s -> %s\n",fmt,path);++event_fd=open(path,O_WRONLY,0);+if(event_fd<0){+printf("failed to open event %s\n",event);+returnevent_fd;+}++err=write(event_fd,fmt,strlen(fmt));+(void)err;++return0;+}++staticintload_maps(structbpf_map_def*maps,intlen)+{+inti;++for(i=0;i<len/sizeof(structbpf_map_def);i++){++map_fd[i]=bpf_create_map(maps[i].type,+maps[i].key_size,+maps[i].value_size,+maps[i].max_entries);+if(map_fd[i]<0)+return1;+}+return0;+}++staticintget_sec(Elf*elf,inti,GElf_Ehdr*ehdr,char**shname,+GElf_Shdr*shdr,Elf_Data**data)+{+Elf_Scn*scn;++scn=elf_getscn(elf,i);+if(!scn)+return1;++if(gelf_getshdr(scn,shdr)!=shdr)+return2;++*shname=elf_strptr(elf,ehdr->e_shstrndx,shdr->sh_name);+if(!*shname||!shdr->sh_size)+return3;++*data=elf_getdata(scn,0);+if(!*data||elf_getdata(scn,*data)!=NULL)+return4;++return0;+}++staticintparse_relo_and_apply(Elf_Data*data,Elf_Data*symbols,+GElf_Shdr*shdr,structbpf_insn*insn)+{+inti,nrels;++nrels=shdr->sh_size/shdr->sh_entsize;++for(i=0;i<nrels;i++){+GElf_Symsym;+GElf_Relrel;+unsignedintinsn_idx;++gelf_getrel(data,i,&rel);++insn_idx=rel.r_offset/sizeof(structbpf_insn);++gelf_getsym(symbols,GELF_R_SYM(rel.r_info),&sym);++if(insn[insn_idx].code!=(BPF_LD|BPF_IMM|BPF_DW)){+printf("invalid relo for insn->code %d\n",+insn[insn_idx].code);+return1;+}+insn[insn_idx].src_reg=BPF_PSEUDO_MAP_FD;+insn[insn_idx].imm=map_fd[sym.st_value/sizeof(structbpf_map_def)];+}++return0;+}++intload_bpf_file(char*path)+{+intfd,i;+Elf*elf;+GElf_Ehdrehdr;+GElf_Shdrshdr,shdr_prog;+Elf_Data*data,*data_prog,*symbols=NULL;+char*shname,*shname_prog;++if(elf_version(EV_CURRENT)==EV_NONE)+return1;++fd=open(path,O_RDONLY,0);+if(fd<0)+return1;++elf=elf_begin(fd,ELF_C_READ,NULL);++if(!elf)+return1;++if(gelf_getehdr(elf,&ehdr)!=&ehdr)+return1;++/* scan over all elf sections to get license and map info */+for(i=1;i<ehdr.e_shnum;i++){++if(get_sec(elf,i,&ehdr,&shname,&shdr,&data))+continue;++if(0)+printf("section %d:%s data %p size %zd link %d flags %d\n",+i,shname,data->d_buf,data->d_size,+shdr.sh_link,(int)shdr.sh_flags);++if(strcmp(shname,"license")==0){+processed_sec[i]=true;+memcpy(license,data->d_buf,data->d_size);+}elseif(strcmp(shname,"maps")==0){+processed_sec[i]=true;+if(load_maps(data->d_buf,data->d_size))+return1;+}elseif(shdr.sh_type==SHT_SYMTAB){+symbols=data;+}+}++/* load programs that need map fixup (relocations) */+for(i=1;i<ehdr.e_shnum;i++){++if(get_sec(elf,i,&ehdr,&shname,&shdr,&data))+continue;+if(shdr.sh_type==SHT_REL){+structbpf_insn*insns;++if(get_sec(elf,shdr.sh_info,&ehdr,&shname_prog,+&shdr_prog,&data_prog))+continue;++if(0)+printf("relo %s into %s\n",shname,shname_prog);++insns=(structbpf_insn*)data_prog->d_buf;++processed_sec[shdr.sh_info]=true;+processed_sec[i]=true;++if(parse_relo_and_apply(data,symbols,&shdr,insns))+continue;++if(memcmp(shname_prog,"events/",sizeof("events/")-1)==0)+load_and_attach(shname_prog,insns,data_prog->d_size);+}+}++/* load programs that don't use maps */+for(i=1;i<ehdr.e_shnum;i++){++if(processed_sec[i])+continue;++if(get_sec(elf,i,&ehdr,&shname,&shdr,&data))+continue;++if(memcmp(shname,"events/",sizeof("events/")-1)==0)+load_and_attach(shname,data->d_buf,data->d_size);+}++close(fd);+return0;+}++voidread_trace_pipe(void)+{+inttrace_fd;++trace_fd=open(DEBUGFS"trace_pipe",O_RDONLY,0);+if(trace_fd<0)+return;++while(1){+staticcharbuf[4096];+ssize_tsz;++sz=read(trace_fd,buf,sizeof(buf));+if(sz)+puts(buf);+}+}
@@ -2,19 +2,21 @@obj-:=dummy.o# List of programs to build-hostprogs-y:=sock_exampledropmonex1+hostprogs-y:=sock_exampledropmonex1ex2sock_example-objs:=sock_example.olibbpf.odropmon-objs:=dropmon.olibbpf.oex1-objs:=bpf_load.olibbpf.oex1_user.o+ex2-objs:=bpf_load.olibbpf.oex2_user.o# Tell kbuild to always build the programs-always:=$(hostprogs-y)ex1_kern.o+always:=$(hostprogs-y)ex1_kern.oex2_kern.oHOSTCFLAGS+=-I$(objtree)/usr/includeHOSTCFLAGS_bpf_load.o+=-I$(objtree)/usr/include-Wno-unused-variableHOSTLOADLIBES_ex1+=-lelf+HOSTLOADLIBES_ex2+=-lelfLLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc
@@ -2,12 +2,13 @@obj-:=dummy.o# List of programs to build-hostprogs-y:=sock_exampledropmonex1ex2+hostprogs-y:=sock_exampledropmonex1ex2test_verifiersock_example-objs:=sock_example.olibbpf.odropmon-objs:=dropmon.olibbpf.oex1-objs:=bpf_load.olibbpf.oex1_user.oex2-objs:=bpf_load.olibbpf.oex2_user.o+test_verifier-objs:=test_verifier.olibbpf.o# Tell kbuild to always build the programsalways:=$(hostprogs-y)ex1_kern.oex2_kern.o
@@ -2,12 +2,23 @@obj-:=dummy.o# List of programs to build-hostprogs-y:=sock_exampledropmon+hostprogs-y:=sock_exampledropmonex1sock_example-objs:=sock_example.olibbpf.odropmon-objs:=dropmon.olibbpf.o+ex1-objs:=bpf_load.olibbpf.oex1_user.o# Tell kbuild to always build the programs-always:=$(hostprogs-y)+always:=$(hostprogs-y)ex1_kern.oHOSTCFLAGS+=-I$(objtree)/usr/include++HOSTCFLAGS_bpf_load.o+=-I$(objtree)/usr/include-Wno-unused-variable+HOSTLOADLIBES_ex1+=-lelf++LLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc++%.o:%.c+clang$(NOSTDINC_FLAGS)$(LINUXINCLUDE)$(EXTRA_CFLAGS)\+-D__KERNEL__-Wno-unused-value-Wno-pointer-sign\+-O2-emit-llvm-c$<-o-|$(LLC)-o$@
this socket filter example does:
- creates a hashtable in kernel with key 4 bytes and value 8 bytes
- populates map[6] = 0; map[17] = 0; // 6 - tcp_proto, 17 - udp_proto
- loads eBPF program:
r0 = skb[14 + 9]; // load one byte of ip->proto
*(u32*)(fp - 4) = r0;
value = bpf_map_lookup_elem(map_fd, fp - 4);
if (value)
(*(u64*)value) += 1;
- attaches this program to eth0 raw socket
- every second user space reads map[6] and map[17] to see how many
TCP and UDP packets were seen on eth0
Signed-off-by: Alexei Starovoitov <redacted>
---
samples/bpf/.gitignore | 1 +
samples/bpf/Makefile | 12 ++++
samples/bpf/sock_example.c | 158 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 171 insertions(+)
create mode 100644 samples/bpf/.gitignore
create mode 100644 samples/bpf/Makefile
create mode 100644 samples/bpf/sock_example.c
@@ -0,0 +1,12 @@+# kbuild trick to avoid linker error. Can be omitted if a module is built.+obj-:=dummy.o++# List of programs to build+hostprogs-y:=sock_example++sock_example-objs:=sock_example.olibbpf.o++# Tell kbuild to always build the programs+always:=$(hostprogs-y)++HOSTCFLAGS+=-I$(objtree)/usr/include
@@ -930,6 +931,18 @@ __kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs,if(ftrace_trigger_soft_disabled(ftrace_file))return;+if(call->flags&TRACE_EVENT_FL_BPF){+structbpf_context__ctx={};+/* get first 3 arguments of the function. x64 syscall ABI uses+*thesame3registersasx64callingconvention.+*todo:implementitcleanlyviaarchspecific+*regs_get_argument_nth()helper+*/+syscall_get_arguments(current,regs,0,3,&__ctx.arg1);+trace_filter_call_bpf(ftrace_file->filter,&__ctx);+return;+}+local_save_flags(irq_flags);pc=preempt_count();
@@ -978,6 +991,17 @@ __kretprobe_trace_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,if(ftrace_trigger_soft_disabled(ftrace_file))return;+if(call->flags&TRACE_EVENT_FL_BPF){+structbpf_context__ctx={};+/* assume that register used to return a value from syscall is+*thesameasregisterusedtoreturnavaluefromafunction+*todo:providearchspecifichelper+*/+__ctx.ret=syscall_get_return_value(current,regs);+trace_filter_call_bpf(ftrace_file->filter,&__ctx);+return;+}+local_save_flags(irq_flags);pc=preempt_count();
@@ -588,3 +588,71 @@ SYSCALL_DEFINE5(bpf, int, cmd, unsigned long, arg2, unsigned long, arg3,return-EINVAL;}}++/* called from eBPF program under rcu lock+*+*ifkernelsubsystemisallowingeBPFprogramstocallthisfunction,+*insideitsownverifier_ops->get_func_proto()callbackitshouldreturn+*(structbpf_func_proto){+*.ret_type=RET_PTR_TO_MAP_VALUE_OR_NULL,+*.arg1_type=ARG_CONST_MAP_PTR,+*.arg2_type=ARG_PTR_TO_MAP_KEY,+*}+*sothateBPFverifierproperlychecksthearguments+*/+u64bpf_map_lookup_elem(u64r1,u64r2,u64r3,u64r4,u64r5)+{+structbpf_map*map=(structbpf_map*)(unsignedlong)r1;+void*key=(void*)(unsignedlong)r2;+void*value;++WARN_ON_ONCE(!rcu_read_lock_held());++value=map->ops->map_lookup_elem(map,key);++return(unsignedlong)value;+}++/* called from eBPF program under rcu lock+*+*ifkernelsubsystemisallowingeBPFprogramstocallthisfunction,+*insideitsownverifier_ops->get_func_proto()callbackitshouldreturn+*(structbpf_func_proto){+*.ret_type=RET_INTEGER,+*.arg1_type=ARG_CONST_MAP_PTR,+*.arg2_type=ARG_PTR_TO_MAP_KEY,+*.arg3_type=ARG_PTR_TO_MAP_VALUE,+*}+*sothateBPFverifierproperlychecksthearguments+*/+u64bpf_map_update_elem(u64r1,u64r2,u64r3,u64r4,u64r5)+{+structbpf_map*map=(structbpf_map*)(unsignedlong)r1;+void*key=(void*)(unsignedlong)r2;+void*value=(void*)(unsignedlong)r3;++WARN_ON_ONCE(!rcu_read_lock_held());++returnmap->ops->map_update_elem(map,key,value);+}++/* called from eBPF program under rcu lock+*+*ifkernelsubsystemisallowingeBPFprogramstocallthisfunction,+*insideitsownverifier_ops->get_func_proto()callbackitshouldreturn+*(structbpf_func_proto){+*.ret_type=RET_INTEGER,+*.arg1_type=ARG_CONST_MAP_PTR,+*.arg2_type=ARG_PTR_TO_MAP_KEY,+*}+*sothateBPFverifierproperlychecksthearguments+*/+u64bpf_map_delete_elem(u64r1,u64r2,u64r3,u64r4,u64r5)+{+structbpf_map*map=(structbpf_map*)(unsignedlong)r1;+void*key=(void*)(unsignedlong)r2;++WARN_ON_ONCE(!rcu_read_lock_held());++returnmap->ops->map_delete_elem(map,key);+}
This patch adds verifier core which simulates execution of every insn and
records the state of registers and program stack. Every branch instruction seen
during simulation is pushed into state stack. When verifier reaches BPF_EXIT,
it pops the state from the stack and continues until it reaches BPF_EXIT again.
For program:
1: bpf_mov r1, xxx
2: if (r1 == 0) goto 5
3: bpf_mov r0, 1
4: goto 6
5: bpf_mov r0, 2
6: bpf_exit
The verifier will walk insns: 1, 2, 3, 4, 6
then it will pop the state recorded at insn#2 and will continue: 5, 6
This way it walks all possible paths through the program and checks all
possible values of registers. While doing so, it checks for:
- invalid instructions
- uninitialized register access
- uninitialized stack access
- misaligned stack access
- out of range stack access
- invalid calling convention
- BPF_LD_ABS|IND instructions are only used in socket filters
- instruction encoding is not using reserved fields
Kernel subsystem configures the verifier with two callbacks:
- bool (*is_valid_access)(int off, int size, enum bpf_access_type type);
that provides information to the verifer which fields of 'ctx'
are accessible (remember 'ctx' is the first argument to eBPF program)
- const struct bpf_func_proto *(*get_func_proto)(enum bpf_func_id func_id);
returns argument constraints of kernel helper functions that eBPF program
may call, so that verifier can checks that R1-R5 types match the prototype
More details in Documentation/networking/filter.txt and in kernel/bpf/verifier.c
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
include/linux/bpf.h | 47 +++
include/uapi/linux/bpf.h | 1 +
kernel/bpf/verifier.c | 990 +++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 1037 insertions(+), 1 deletion(-)
@@ -47,6 +47,31 @@ void bpf_register_map_type(struct bpf_map_type_list *tl);voidbpf_map_put(structbpf_map*map);structbpf_map*bpf_map_get(structfdf);+/* function argument constraints */+enumbpf_arg_type{+ARG_ANYTHING=0,/* any argument is ok */++/* the following constraints used to prototype+*bpf_map_lookup/update/delete_elem()functions+*/+ARG_CONST_MAP_PTR,/* const argument used as pointer to bpf_map */+ARG_PTR_TO_MAP_KEY,/* pointer to stack used as map key */+ARG_PTR_TO_MAP_VALUE,/* pointer to stack used as map value */++/* the following constraints used to prototype bpf_memcmp() and other+*functionsthataccessdataoneBPFprogramstack+*/+ARG_PTR_TO_STACK,/* any pointer to eBPF program stack */+ARG_CONST_STACK_SIZE,/* number of bytes accessed from stack */+};++/* type of values returned from helper functions */+enumbpf_return_type{+RET_INTEGER,/* function returns integer */+RET_VOID,/* function doesn't return anything */+RET_PTR_TO_MAP_VALUE_OR_NULL,/* returns a pointer to map elem value or NULL */+};+/* eBPF function prototype used by verifier to allow BPF_CALLs from eBPF programs*toin-kernelhelperfunctionsandforadjustingimm32fieldinBPF_CALL*instructionsafterverifying
@@ -54,11 +79,33 @@ struct bpf_map *bpf_map_get(struct fd f);structbpf_func_proto{u64(*func)(u64r1,u64r2,u64r3,u64r4,u64r5);boolgpl_only;+enumbpf_return_typeret_type;+enumbpf_arg_typearg1_type;+enumbpf_arg_typearg2_type;+enumbpf_arg_typearg3_type;+enumbpf_arg_typearg4_type;+enumbpf_arg_typearg5_type;+};++/* bpf_context is intentionally undefined structure. Pointer to bpf_context is+*thefirstargumenttoeBPFprograms.+*Forsocketfilters:'structbpf_context*'=='structsk_buff*'+*/+structbpf_context;++enumbpf_access_type{+BPF_READ=1,+BPF_WRITE=2};structbpf_verifier_ops{/* return eBPF function prototype for verification */conststructbpf_func_proto*(*get_func_proto)(enumbpf_func_idfunc_id);++/* return true if 'size' wide access at offset 'off' within bpf_context+*with'type'(readorwrite)isallowed+*/+bool(*is_valid_access)(intoff,intsize,enumbpf_access_typetype);};structbpf_prog_type_list{
@@ -395,6 +395,7 @@ enum bpf_prog_attributes {enumbpf_prog_type{BPF_PROG_TYPE_UNSPEC,+BPF_PROG_TYPE_SOCKET_FILTER,};/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -143,6 +143,72 @@*load/storetobpf_contextarecheckedagainstknownfields*/+#define _(OP) ({ int ret = (OP); if (ret < 0) return ret; })++/* types of values stored in eBPF registers */+enumbpf_reg_type{+NOT_INIT=0,/* nothing was written into register */+UNKNOWN_VALUE,/* reg doesn't contain a valid pointer */+PTR_TO_CTX,/* reg points to bpf_context */+CONST_PTR_TO_MAP,/* reg points to struct bpf_map */+PTR_TO_MAP_VALUE,/* reg points to map element value */+PTR_TO_MAP_VALUE_OR_NULL,/* points to map elem value or NULL */+FRAME_PTR,/* reg == frame_pointer */+PTR_TO_STACK,/* reg == frame_pointer + imm */+CONST_IMM,/* constant integer value */+};++structreg_state{+enumbpf_reg_typetype;+union{+/* valid when type == CONST_IMM | PTR_TO_STACK */+intimm;++/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |+*PTR_TO_MAP_VALUE_OR_NULL+*/+structbpf_map*map_ptr;+};+};++enumbpf_stack_slot_type{+STACK_INVALID,/* nothing was stored in this stack slot */+STACK_SPILL,/* 1st byte of register spilled into stack */+STACK_SPILL_PART,/* other 7 bytes of register spill */+STACK_MISC/* BPF program wrote some data into this slot */+};++structbpf_stack_slot{+enumbpf_stack_slot_typestype;+structreg_statereg_st;+};++/* state of the program:+*typeofallregistersandstackinfo+*/+structverifier_state{+structreg_stateregs[MAX_BPF_REG];+structbpf_stack_slotstack[MAX_BPF_STACK];+};++/* linked list of verifier states used to prune search */+structverifier_state_list{+structverifier_statestate;+structverifier_state_list*next;+};++/* verifier_state + insn_idx are pushed to stack when branch is encountered */+structverifier_stack_elem{+/* verifer state is 'st'+*beforeprocessinginstruction'insn_idx'+*andafterprocessinginstruction'prev_insn_idx'+*/+structverifier_statest;+intinsn_idx;+intprev_insn_idx;+structverifier_stack_elem*next;+};+#define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program *//* single container for all structs
@@ -150,6 +216,9 @@*/structverifier_env{structbpf_prog*prog;/* eBPF program being verified */+structverifier_stack_elem*head;/* stack of verifier states to be processed */+intstack_size;/* number of states to be processed */+structverifier_statecur_state;/* current verifier state */structbpf_map*used_maps[MAX_USED_MAPS];/* array of map's used by eBPF program */u32used_map_cnt;/* number of used maps */};
@@ -323,6 +431,647 @@ static void print_bpf_insn(struct bpf_insn *insn)}}+staticintpop_stack(structverifier_env*env,int*prev_insn_idx)+{+structverifier_stack_elem*elem;+intinsn_idx;++if(env->head==NULL)+return-1;++memcpy(&env->cur_state,&env->head->st,sizeof(env->cur_state));+insn_idx=env->head->insn_idx;+if(prev_insn_idx)+*prev_insn_idx=env->head->prev_insn_idx;+elem=env->head->next;+kfree(env->head);+env->head=elem;+env->stack_size--;+returninsn_idx;+}++staticstructverifier_state*push_stack(structverifier_env*env,intinsn_idx,+intprev_insn_idx)+{+structverifier_stack_elem*elem;++elem=kmalloc(sizeof(structverifier_stack_elem),GFP_KERNEL);+if(!elem)+gotoerr;++memcpy(&elem->st,&env->cur_state,sizeof(env->cur_state));+elem->insn_idx=insn_idx;+elem->prev_insn_idx=prev_insn_idx;+elem->next=env->head;+env->head=elem;+env->stack_size++;+if(env->stack_size>1024){+verbose("BPF program is too complex\n");+gotoerr;+}+return&elem->st;+err:+/* pop all elements and return */+while(pop_stack(env,NULL)>=0);+returnNULL;+}++#define CALLER_SAVED_REGS 6+staticconstintcaller_saved[CALLER_SAVED_REGS]={+BPF_REG_0,BPF_REG_1,BPF_REG_2,BPF_REG_3,BPF_REG_4,BPF_REG_5+};++staticvoidinit_reg_state(structreg_state*regs)+{+inti;++for(i=0;i<MAX_BPF_REG;i++){+regs[i].type=NOT_INIT;+regs[i].imm=0;+regs[i].map_ptr=NULL;+}++/* frame pointer */+regs[BPF_REG_FP].type=FRAME_PTR;++/* 1st arg to a function */+regs[BPF_REG_1].type=PTR_TO_CTX;+}++staticvoidmark_reg_unknown_value(structreg_state*regs,intregno)+{+regs[regno].type=UNKNOWN_VALUE;+regs[regno].imm=0;+regs[regno].map_ptr=NULL;+}++staticintcheck_reg_arg(structreg_state*regs,intregno,boolis_src)+{+if(regno>=MAX_BPF_REG){+verbose("R%d is invalid\n",regno);+return-EINVAL;+}++if(is_src){+if(regs[regno].type==NOT_INIT){+verbose("R%d !read_ok\n",regno);+return-EACCES;+}+}else{+if(regno==BPF_REG_FP){+verbose("frame pointer is read only\n");+return-EACCES;+}+mark_reg_unknown_value(regs,regno);+}+return0;+}++staticintbpf_size_to_bytes(intbpf_size)+{+if(bpf_size==BPF_W)+return4;+elseif(bpf_size==BPF_H)+return2;+elseif(bpf_size==BPF_B)+return1;+elseif(bpf_size==BPF_DW)+return8;+else+return-EACCES;+}++staticintcheck_stack_write(structverifier_state*state,intoff,intsize,+intvalue_regno)+{+structbpf_stack_slot*slot;+inti;++if(value_regno>=0&&+(state->regs[value_regno].type==PTR_TO_MAP_VALUE||+state->regs[value_regno].type==PTR_TO_STACK||+state->regs[value_regno].type==PTR_TO_CTX)){++/* register containing pointer is being spilled into stack */+if(size!=8){+verbose("invalid size of register spill\n");+return-EACCES;+}++slot=&state->stack[MAX_BPF_STACK+off];+slot->stype=STACK_SPILL;+/* save register state */+slot->reg_st=state->regs[value_regno];+for(i=1;i<8;i++){+slot=&state->stack[MAX_BPF_STACK+off+i];+slot->stype=STACK_SPILL_PART;+slot->reg_st.type=UNKNOWN_VALUE;+slot->reg_st.map_ptr=NULL;+}+}else{++/* regular write of data into stack */+for(i=0;i<size;i++){+slot=&state->stack[MAX_BPF_STACK+off+i];+slot->stype=STACK_MISC;+slot->reg_st.type=UNKNOWN_VALUE;+slot->reg_st.map_ptr=NULL;+}+}+return0;+}++staticintcheck_stack_read(structverifier_state*state,intoff,intsize,+intvalue_regno)+{+inti;+structbpf_stack_slot*slot;++slot=&state->stack[MAX_BPF_STACK+off];++if(slot->stype==STACK_SPILL){+if(size!=8){+verbose("invalid size of register spill\n");+return-EACCES;+}+for(i=1;i<8;i++){+if(state->stack[MAX_BPF_STACK+off+i].stype!=+STACK_SPILL_PART){+verbose("corrupted spill memory\n");+return-EACCES;+}+}++/* restore register state from stack */+state->regs[value_regno]=slot->reg_st;+return0;+}else{+for(i=0;i<size;i++){+if(state->stack[MAX_BPF_STACK+off+i].stype!=+STACK_MISC){+verbose("invalid read from stack off %d+%d size %d\n",+off,i,size);+return-EACCES;+}+}+/* have read misc data from the stack */+mark_reg_unknown_value(state->regs,value_regno);+return0;+}+}++/* check read/write into map element returned by bpf_map_lookup_elem() */+staticintcheck_map_access(structverifier_env*env,intregno,intoff,+intsize)+{+structbpf_map*map=env->cur_state.regs[regno].map_ptr;++if(off<0||off+size>map->value_size){+verbose("invalid access to map value, value_size=%d off=%d size=%d\n",+map->value_size,off,size);+return-EACCES;+}+return0;+}++/* check access to 'struct bpf_context' fields */+staticintcheck_ctx_access(structverifier_env*env,intoff,intsize,+enumbpf_access_typet)+{+if(env->prog->info->ops->is_valid_access&&+env->prog->info->ops->is_valid_access(off,size,t))+return0;++verbose("invalid bpf_context access off=%d size=%d\n",off,size);+return-EACCES;+}++staticintcheck_mem_access(structverifier_env*env,intregno,intoff,+intbpf_size,enumbpf_access_typet,+intvalue_regno)+{+structverifier_state*state=&env->cur_state;+intsize;++_(size=bpf_size_to_bytes(bpf_size));++if(off%size!=0){+verbose("misaligned access off %d size %d\n",off,size);+return-EACCES;+}++if(state->regs[regno].type==PTR_TO_MAP_VALUE){+_(check_map_access(env,regno,off,size));+if(t==BPF_READ)+mark_reg_unknown_value(state->regs,value_regno);+}elseif(state->regs[regno].type==PTR_TO_CTX){+_(check_ctx_access(env,off,size,t));+if(t==BPF_READ)+mark_reg_unknown_value(state->regs,value_regno);+}elseif(state->regs[regno].type==FRAME_PTR){+if(off>=0||off<-MAX_BPF_STACK){+verbose("invalid stack off=%d size=%d\n",off,size);+return-EACCES;+}+if(t==BPF_WRITE)+_(check_stack_write(state,off,size,value_regno));+else+_(check_stack_read(state,off,size,value_regno));+}else{+verbose("R%d invalid mem access '%s'\n",+regno,reg_type_str[state->regs[regno].type]);+return-EACCES;+}+return0;+}++/* when register 'regno' is passed into function that will read 'access_size'+*bytesfromthatpointer,makesurethatit'swithinstackboundary+*andallelementsofstackareinitialized+*/+staticintcheck_stack_boundary(structverifier_env*env,+intregno,intaccess_size)+{+structverifier_state*state=&env->cur_state;+structreg_state*regs=state->regs;+intoff,i;++if(regs[regno].type!=PTR_TO_STACK)+return-EACCES;++off=regs[regno].imm;+if(off>=0||off<-MAX_BPF_STACK||off+access_size>0||+access_size<=0){+verbose("invalid stack type R%d off=%d access_size=%d\n",+regno,off,access_size);+return-EACCES;+}++for(i=0;i<access_size;i++){+if(state->stack[MAX_BPF_STACK+off+i].stype!=STACK_MISC){+verbose("invalid indirect read from stack off %d+%d size %d\n",+off,i,access_size);+return-EACCES;+}+}+return0;+}++staticintcheck_func_arg(structverifier_env*env,u32regno,+enumbpf_arg_typearg_type,structbpf_map**mapp)+{+structreg_state*reg=env->cur_state.regs+regno;+enumbpf_reg_typeexpected_type;++if(arg_type==ARG_ANYTHING)+return0;++if(reg->type==NOT_INIT){+verbose("R%d !read_ok\n",regno);+return-EACCES;+}++if(arg_type==ARG_PTR_TO_STACK||arg_type==ARG_PTR_TO_MAP_KEY||+arg_type==ARG_PTR_TO_MAP_VALUE){+expected_type=PTR_TO_STACK;+}elseif(arg_type==ARG_CONST_STACK_SIZE){+expected_type=CONST_IMM;+}elseif(arg_type==ARG_CONST_MAP_PTR){+expected_type=CONST_PTR_TO_MAP;+}else{+verbose("unsupported arg_type %d\n",arg_type);+return-EFAULT;+}++if(reg->type!=expected_type){+verbose("R%d type=%s expected=%s\n",regno,+reg_type_str[reg->type],reg_type_str[expected_type]);+return-EACCES;+}++if(arg_type==ARG_CONST_MAP_PTR){+/* bpf_map_xxx(map_ptr) call: remember that map_ptr */+*mapp=reg->map_ptr;++}elseif(arg_type==ARG_PTR_TO_MAP_KEY){+/* bpf_map_xxx(..., map_ptr, ..., key) call:+*checkthat[key,key+map->key_size)arewithin+*stacklimitsandinitialized+*/+if(!*mapp){+/* in function declaration map_ptr must come before+*map_keyormap_elem,sothatit'sverified+*andknownbeforewehavetocheckmap_keyhere.+*Itmeansthatkernelsubsystemmisconfiguredverifier+*/+verbose("invalid map_ptr to access map->key\n");+return-EACCES;+}+_(check_stack_boundary(env,regno,(*mapp)->key_size));++}elseif(arg_type==ARG_PTR_TO_MAP_VALUE){+/* bpf_map_xxx(..., map_ptr, ..., value) call:+*check[value,value+map->value_size)validity+*/+if(!*mapp){+/* kernel subsystem misconfigured verifier */+verbose("invalid map_ptr to access map->elem\n");+return-EACCES;+}+_(check_stack_boundary(env,regno,(*mapp)->value_size));++}elseif(arg_type==ARG_CONST_STACK_SIZE){+/* bpf_xxx(..., buf, len) call will access 'len' bytes+*fromstackpointer'buf'.Checkit+*note:regno==len,regno-1==buf+*/+if(regno==0){+/* kernel subsystem misconfigured verifier */+verbose("ARG_CONST_STACK_SIZE cannot be first argument\n");+return-EACCES;+}+_(check_stack_boundary(env,regno-1,reg->imm));+}++return0;+}++staticintcheck_call(structverifier_env*env,intfunc_id)+{+structverifier_state*state=&env->cur_state;+conststructbpf_func_proto*fn=NULL;+structreg_state*regs=state->regs;+structbpf_map*map=NULL;+structreg_state*reg;+inti;++/* find function prototype */+if(func_id<=0||func_id>=__BPF_FUNC_MAX_ID){+verbose("invalid func %d\n",func_id);+return-EINVAL;+}++if(env->prog->info->ops->get_func_proto)+fn=env->prog->info->ops->get_func_proto(func_id);++if(!fn){+verbose("unknown func %d\n",func_id);+return-EINVAL;+}++/* eBPF programs must be GPL compatible to use GPL-ed functions */+if(!env->prog->info->is_gpl_compatible&&fn->gpl_only){+verbose("cannot call GPL only function from proprietary program\n");+return-EINVAL;+}++/* check args */+_(check_func_arg(env,BPF_REG_1,fn->arg1_type,&map));+_(check_func_arg(env,BPF_REG_2,fn->arg2_type,&map));+_(check_func_arg(env,BPF_REG_3,fn->arg3_type,&map));+_(check_func_arg(env,BPF_REG_4,fn->arg4_type,&map));+_(check_func_arg(env,BPF_REG_5,fn->arg5_type,&map));++/* reset caller saved regs */+for(i=0;i<CALLER_SAVED_REGS;i++){+reg=regs+caller_saved[i];+reg->type=NOT_INIT;+reg->imm=0;+}++/* update return register */+if(fn->ret_type==RET_INTEGER){+regs[BPF_REG_0].type=UNKNOWN_VALUE;+}elseif(fn->ret_type==RET_VOID){+regs[BPF_REG_0].type=NOT_INIT;+}elseif(fn->ret_type==RET_PTR_TO_MAP_VALUE_OR_NULL){+regs[BPF_REG_0].type=PTR_TO_MAP_VALUE_OR_NULL;+/*+*remembermap_ptr,sothatcheck_map_access()+*cancheck'value_size'boundaryofmemoryaccess+*tomapelementreturnedfrombpf_map_lookup_elem()+*/+if(map==NULL){+verbose("kernel subsystem misconfigured verifier\n");+return-EINVAL;+}+regs[BPF_REG_0].map_ptr=map;+}else{+verbose("unknown return type %d of func %d\n",+fn->ret_type,func_id);+return-EINVAL;+}+return0;+}++/* check validity of 32-bit and 64-bit arithmetic operations */+staticintcheck_alu_op(structreg_state*regs,structbpf_insn*insn)+{+u8opcode=BPF_OP(insn->code);++if(opcode==BPF_END||opcode==BPF_NEG){+if(opcode==BPF_NEG){+if(BPF_SRC(insn->code)!=0||+insn->src_reg!=BPF_REG_0||+insn->off!=0||insn->imm!=0){+verbose("BPF_NEG uses reserved fields\n");+return-EINVAL;+}+}else{+if(insn->src_reg!=BPF_REG_0||insn->off!=0||+(insn->imm!=16&&insn->imm!=32&&insn->imm!=64)){+verbose("BPF_END uses reserved fields\n");+return-EINVAL;+}+}++/* check src operand */+_(check_reg_arg(regs,insn->dst_reg,1));++/* check dest operand */+_(check_reg_arg(regs,insn->dst_reg,0));++}elseif(opcode==BPF_MOV){++if(BPF_SRC(insn->code)==BPF_X){+if(insn->imm!=0||insn->off!=0){+verbose("BPF_MOV uses reserved fields\n");+return-EINVAL;+}++/* check src operand */+_(check_reg_arg(regs,insn->src_reg,1));+}else{+if(insn->src_reg!=BPF_REG_0||insn->off!=0){+verbose("BPF_MOV uses reserved fields\n");+return-EINVAL;+}+}++/* check dest operand */+_(check_reg_arg(regs,insn->dst_reg,0));++if(BPF_SRC(insn->code)==BPF_X){+if(BPF_CLASS(insn->code)==BPF_ALU64){+/* case: R1 = R2+*copyregisterstatetodestreg+*/+regs[insn->dst_reg].type=regs[insn->src_reg].type;+regs[insn->dst_reg].imm=regs[insn->src_reg].imm;+}else{+regs[insn->dst_reg].type=UNKNOWN_VALUE;+regs[insn->dst_reg].imm=0;+}+}else{+/* case: R = imm+*rememberthevaluewestoredintothisreg+*/+regs[insn->dst_reg].type=CONST_IMM;+regs[insn->dst_reg].imm=insn->imm;+}++}elseif(opcode>BPF_END){+verbose("invalid BPF_ALU opcode %x\n",opcode);+return-EINVAL;++}else{/* all other ALU ops: and, sub, xor, add, ... */++intstack_relative=0;++if(BPF_SRC(insn->code)==BPF_X){+if(insn->imm!=0||insn->off!=0){+verbose("BPF_ALU uses reserved fields\n");+return-EINVAL;+}+/* check src1 operand */+_(check_reg_arg(regs,insn->src_reg,1));+}else{+if(insn->src_reg!=BPF_REG_0||insn->off!=0){+verbose("BPF_ALU uses reserved fields\n");+return-EINVAL;+}+}++/* check src2 operand */+_(check_reg_arg(regs,insn->dst_reg,1));++if((opcode==BPF_MOD||opcode==BPF_DIV)&&+BPF_SRC(insn->code)==BPF_K&&insn->imm==0){+verbose("div by zero\n");+return-EINVAL;+}++if(opcode==BPF_ADD&&BPF_CLASS(insn->code)==BPF_ALU64&&+regs[insn->dst_reg].type==FRAME_PTR&&+BPF_SRC(insn->code)==BPF_K)+stack_relative=1;++/* check dest operand */+_(check_reg_arg(regs,insn->dst_reg,0));++if(stack_relative){+regs[insn->dst_reg].type=PTR_TO_STACK;+regs[insn->dst_reg].imm=insn->imm;+}+}++return0;+}++staticintcheck_cond_jmp_op(structverifier_env*env,+structbpf_insn*insn,int*insn_idx)+{+structreg_state*regs=env->cur_state.regs;+structverifier_state*other_branch;+u8opcode=BPF_OP(insn->code);++if(opcode>BPF_EXIT){+verbose("invalid BPF_JMP opcode %x\n",opcode);+return-EINVAL;+}++if(BPF_SRC(insn->code)==BPF_X){+if(insn->imm!=0){+verbose("BPF_JMP uses reserved fields\n");+return-EINVAL;+}++/* check src1 operand */+_(check_reg_arg(regs,insn->src_reg,1));+}else{+if(insn->src_reg!=BPF_REG_0){+verbose("BPF_JMP uses reserved fields\n");+return-EINVAL;+}+}++/* check src2 operand */+_(check_reg_arg(regs,insn->dst_reg,1));++/* detect if R == 0 where R was initialized to zero earlier */+if(BPF_SRC(insn->code)==BPF_K&&+(opcode==BPF_JEQ||opcode==BPF_JNE)&&+regs[insn->dst_reg].type==CONST_IMM&&+regs[insn->dst_reg].imm==insn->imm){+if(opcode==BPF_JEQ){+/* if (imm == imm) goto pc+off;+*onlyfollowthegoto,ignorefall-through+*/+*insn_idx+=insn->off;+return0;+}else{+/* if (imm != imm) goto pc+off;+*onlyfollowfall-throughbranch,since+*that'swheretheprogramwillgo+*/+return0;+}+}++other_branch=push_stack(env,*insn_idx+insn->off+1,*insn_idx);+if(!other_branch)+return-EFAULT;++/* detect if R == 0 where R is returned value from bpf_map_lookup_elem() */+if(BPF_SRC(insn->code)==BPF_K&&+insn->imm==0&&(opcode==BPF_JEQ||+opcode==BPF_JNE)&&+regs[insn->dst_reg].type==PTR_TO_MAP_VALUE_OR_NULL){+if(opcode==BPF_JEQ){+/* next fallthrough insn can access memory via+*thisregister+*/+regs[insn->dst_reg].type=PTR_TO_MAP_VALUE;+/* branch targer cannot access it, since reg == 0 */+other_branch->regs[insn->dst_reg].type=CONST_IMM;+other_branch->regs[insn->dst_reg].imm=0;+}else{+other_branch->regs[insn->dst_reg].type=PTR_TO_MAP_VALUE;+regs[insn->dst_reg].type=CONST_IMM;+regs[insn->dst_reg].imm=0;+}+}elseif(BPF_SRC(insn->code)==BPF_K&&+(opcode==BPF_JEQ||opcode==BPF_JNE)){++if(opcode==BPF_JEQ){+/* detect if (R == imm) goto+*andinthetargetstaterecognizethatR=imm+*/+other_branch->regs[insn->dst_reg].type=CONST_IMM;+other_branch->regs[insn->dst_reg].imm=insn->imm;+}else{+/* detect if (R != imm) goto+*andinthefall-throughstaterecognizethatR=imm+*/+regs[insn->dst_reg].type=CONST_IMM;+regs[insn->dst_reg].imm=insn->imm;+}+}+if(log_level)+print_verifier_state(env);+return0;+}+/* return the map pointer stored inside BPF_LD_IMM64 instruction */staticstructbpf_map*ld_imm64_to_map_ptr(structbpf_insn*insn){
@@ -331,6 +1080,93 @@ static struct bpf_map *ld_imm64_to_map_ptr(struct bpf_insn *insn)return(structbpf_map*)(unsignedlong)imm64;}+/* verify BPF_LD_IMM64 instruction */+staticintcheck_ld_imm(structverifier_env*env,structbpf_insn*insn)+{+structreg_state*regs=env->cur_state.regs;++if(BPF_SIZE(insn->code)!=BPF_DW){+verbose("invalid BPF_LD_IMM insn\n");+return-EINVAL;+}+if(insn->off!=0){+verbose("BPF_LD_IMM64 uses reserved fields\n");+return-EINVAL;+}++_(check_reg_arg(regs,insn->dst_reg,0));++if(insn->src_reg==0)+/* generic move 64-bit immediate into a register */+return0;++/* replace_map_fd_with_map_ptr() should have caught bad ld_imm64 */+BUG_ON(insn->src_reg!=BPF_PSEUDO_MAP_FD);++regs[insn->dst_reg].type=CONST_PTR_TO_MAP;+regs[insn->dst_reg].map_ptr=ld_imm64_to_map_ptr(insn);+return0;+}++/* verify safety of LD_ABS|LD_IND instructions:+*-theycanonlyappearintheprogramswherectx==skb+*-sincetheyarewrappersoffunctioncalls,theyscratchR1-R5registers,+*preserveR6-R9,andstorereturnvalueintoR0+*+*Implicitinput:+*ctx==skb==R6==CTX+*+*Explicitinput:+*SRC==anyregister+*IMM==32-bitimmediate+*+*Output:+*R0-8/16/32-bitskbdataconvertedtocpuendianness+*/+staticintcheck_ld_abs(structverifier_env*env,structbpf_insn*insn)+{+structreg_state*regs=env->cur_state.regs;+u8mode=BPF_MODE(insn->code);+structreg_state*reg;+inti;++if(env->prog->info->prog_type!=BPF_PROG_TYPE_SOCKET_FILTER){+verbose("BPF_LD_ABS|IND instructions are only allowed in socket filters\n");+return-EINVAL;+}++if(insn->dst_reg!=BPF_REG_0||insn->off!=0||+(mode==BPF_ABS&&insn->src_reg!=BPF_REG_0)){+verbose("BPF_LD_ABS uses reserved fields\n");+return-EINVAL;+}++/* check whether implicit source operand (register R6) is readable */+_(check_reg_arg(regs,BPF_REG_6,1));++if(regs[BPF_REG_6].type!=PTR_TO_CTX){+verbose("at the time of BPF_LD_ABS|IND R6 != pointer to skb\n");+return-EINVAL;+}++if(mode==BPF_IND)+/* check explicit source operand */+_(check_reg_arg(regs,insn->src_reg,1));++/* reset caller saved regs to unreadable */+for(i=0;i<CALLER_SAVED_REGS;i++){+reg=regs+caller_saved[i];+reg->type=NOT_INIT;+reg->imm=0;+}++/* mark destination R0 register as readable, since it contains+*thevaluefetchedfromthepacket+*/+regs[BPF_REG_0].type=UNKNOWN_VALUE;+return0;+}+/* non-recursive DFS pseudo code*1procedureDFS-iterative(G,v):*2labelvasdiscovered
@@ -510,6 +1346,157 @@ free_st:returnret;}+staticintdo_check(structverifier_env*env)+{+structverifier_state*state=&env->cur_state;+structbpf_insn*insns=env->prog->insnsi;+structreg_state*regs=state->regs;+intinsn_cnt=env->prog->len;+intinsn_idx,prev_insn_idx=0;+intinsn_processed=0;+booldo_print_state=false;++init_reg_state(regs);+insn_idx=0;+for(;;){+structbpf_insn*insn;+u8class;++if(insn_idx>=insn_cnt){+verbose("invalid insn idx %d insn_cnt %d\n",+insn_idx,insn_cnt);+return-EFAULT;+}++insn=&insns[insn_idx];+class=BPF_CLASS(insn->code);++if(++insn_processed>32768){+verbose("BPF program is too large. Proccessed %d insn\n",+insn_processed);+return-E2BIG;+}++if(log_level&&do_print_state){+verbose("\nfrom %d to %d:",prev_insn_idx,insn_idx);+print_verifier_state(env);+do_print_state=false;+}++if(log_level){+verbose("%d: ",insn_idx);+print_bpf_insn(insn);+}++if(class==BPF_ALU||class==BPF_ALU64){+_(check_alu_op(regs,insn));++}elseif(class==BPF_LDX){+if(BPF_MODE(insn->code)!=BPF_MEM)+return-EINVAL;++/* check src operand */+_(check_reg_arg(regs,insn->src_reg,1));++_(check_mem_access(env,insn->src_reg,insn->off,+BPF_SIZE(insn->code),BPF_READ,+insn->dst_reg));++/* dest reg state will be updated by mem_access */++}elseif(class==BPF_STX){+/* check src1 operand */+_(check_reg_arg(regs,insn->src_reg,1));+/* check src2 operand */+_(check_reg_arg(regs,insn->dst_reg,1));+_(check_mem_access(env,insn->dst_reg,insn->off,+BPF_SIZE(insn->code),BPF_WRITE,+insn->src_reg));++}elseif(class==BPF_ST){+if(BPF_MODE(insn->code)!=BPF_MEM)+return-EINVAL;+/* check src operand */+_(check_reg_arg(regs,insn->dst_reg,1));+_(check_mem_access(env,insn->dst_reg,insn->off,+BPF_SIZE(insn->code),BPF_WRITE,+-1));++}elseif(class==BPF_JMP){+u8opcode=BPF_OP(insn->code);++if(opcode==BPF_CALL){+if(BPF_SRC(insn->code)!=BPF_K||+insn->off!=0||+insn->src_reg!=BPF_REG_0||+insn->dst_reg!=BPF_REG_0){+verbose("BPF_CALL uses reserved fields\n");+return-EINVAL;+}++_(check_call(env,insn->imm));++}elseif(opcode==BPF_JA){+if(BPF_SRC(insn->code)!=BPF_K||+insn->imm!=0||+insn->src_reg!=BPF_REG_0||+insn->dst_reg!=BPF_REG_0){+verbose("BPF_JA uses reserved fields\n");+return-EINVAL;+}++insn_idx+=insn->off+1;+continue;++}elseif(opcode==BPF_EXIT){+if(BPF_SRC(insn->code)!=BPF_K||+insn->imm!=0||+insn->src_reg!=BPF_REG_0||+insn->dst_reg!=BPF_REG_0){+verbose("BPF_EXIT uses reserved fields\n");+return-EINVAL;+}++/* eBPF calling convetion is such that R0 is used+*toreturnthevaluefromeBPFprogram.+*Makesurethatit'sreadableatthistime+*ofbpf_exit,whichmeansthatprogramwrote+*somethingintoitearlier+*/+_(check_reg_arg(regs,BPF_REG_0,1));+insn_idx=pop_stack(env,&prev_insn_idx);+if(insn_idx<0){+break;+}else{+do_print_state=true;+continue;+}+}else{+_(check_cond_jmp_op(env,insn,&insn_idx));+}+}elseif(class==BPF_LD){+u8mode=BPF_MODE(insn->code);++if(mode==BPF_ABS||mode==BPF_IND){+_(check_ld_abs(env,insn));+}elseif(mode==BPF_IMM){+_(check_ld_imm(env,insn));+insn_idx++;+}else{+verbose("invalid BPF_LD mode\n");+return-EINVAL;+}+}else{+verbose("unknown insn class %d\n",class);+return-EINVAL;+}++insn_idx++;+}++return0;+}+/* look for pseudo eBPF instructions that access map FDs and*replacethemwithactualmappointers*/
@@ -663,9 +1650,10 @@ int bpf_check(struct bpf_prog *prog, struct nlattr *tb[BPF_PROG_ATTR_MAX + 1])if(ret<0)gotoskip_full_check;-/* ret = do_check(env); */+ret=do_check(env);skip_full_check:+while(pop_stack(env,NULL)>=0);if(log_level&&log_len>=log_size-1){BUG_ON(log_len>=log_size);
check that control flow graph of eBPF program is a directed acyclic graph
check_cfg() does:
- detect loops
- detect unreachable instructions
- check that program terminates with BPF_EXIT insn
- check that all branches are within program boundary
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
kernel/bpf/verifier.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 183 insertions(+)
@@ -331,6 +331,185 @@ static struct bpf_map *ld_imm64_to_map_ptr(struct bpf_insn *insn)return(structbpf_map*)(unsignedlong)imm64;}+/* non-recursive DFS pseudo code+*1procedureDFS-iterative(G,v):+*2labelvasdiscovered+*3letSbeastack+*4S.push(v)+*5whileSisnotempty+*6t<-S.pop()+*7iftiswhatwe'relookingfor:+*8returnt+*9foralledgeseinG.adjacentEdges(t)do+*10ifedgeeisalreadylabelled+*11continuewiththenextedge+*12w<-G.adjacentVertex(t,e)+*13ifvertexwisnotdiscoveredandnotexplored+*14labeleastree-edge+*15labelwasdiscovered+*16S.push(w)+*17continueat5+*18elseifvertexwisdiscovered+*19labeleasback-edge+*20else+*21// vertex w is explored+*22labeleasforward-orcross-edge+*23labeltasexplored+*24S.pop()+*+*convention:+*0x10-discovered+*0x11-discoveredandfall-throughedgelabelled+*0x12-discoveredandfall-throughandbranchedgeslabelled+*0x20-explored+*/++enum{+DISCOVERED=0x10,+EXPLORED=0x20,+FALLTHROUGH=1,+BRANCH=2,+};++#define PUSH_INT(I) \+do{\+if(cur_stack>=insn_cnt){\+ret=-E2BIG;\+gotofree_st;\+}\+stack[cur_stack++]=I;\+}while(0)++#define PEEK_INT() \+({\+int_ret;\+if(cur_stack==0)\+_ret=-1;\+else\+_ret=stack[cur_stack-1];\+_ret;\+})++#define POP_INT() \+({\+int_ret;\+if(cur_stack==0)\+_ret=-1;\+else\+_ret=stack[--cur_stack];\+_ret;\+})++#define PUSH_INSN(T, W, E) \+do{\+intw=W;\+if(E==FALLTHROUGH&&st[T]>=(DISCOVERED|FALLTHROUGH))\+break;\+if(E==BRANCH&&st[T]>=(DISCOVERED|BRANCH))\+break;\+if(w<0||w>=insn_cnt){\+verbose("jump out of range from insn %d to %d\n",T,w);\+ret=-EINVAL;\+gotofree_st;\+}\+if(st[w]==0){\+/* tree-edge */\+st[T]=DISCOVERED|E;\+st[w]=DISCOVERED;\+PUSH_INT(w);\+gotopeek_stack;\+}elseif((st[w]&0xF0)==DISCOVERED){\+verbose("back-edge from insn %d to %d\n",T,w);\+ret=-EINVAL;\+gotofree_st;\+}elseif(st[w]==EXPLORED){\+/* forward- or cross-edge */\+st[T]=DISCOVERED|E;\+}else{\+verbose("insn state internal bug\n");\+ret=-EFAULT;\+gotofree_st;\+}\+}while(0)++/* non-recursive depth-first-search to detect loops in BPF program+*loop==back-edgeindirectedgraph+*/+staticintcheck_cfg(structverifier_env*env)+{+structbpf_insn*insns=env->prog->insnsi;+intinsn_cnt=env->prog->len;+intcur_stack=0;+int*stack;+intret=0;+int*st;+inti,t;++st=kzalloc(sizeof(int)*insn_cnt,GFP_KERNEL);+if(!st)+return-ENOMEM;++stack=kzalloc(sizeof(int)*insn_cnt,GFP_KERNEL);+if(!stack){+kfree(st);+return-ENOMEM;+}++st[0]=DISCOVERED;/* mark 1st insn as discovered */+PUSH_INT(0);++peek_stack:+while((t=PEEK_INT())!=-1){+if(BPF_CLASS(insns[t].code)==BPF_JMP){+u8opcode=BPF_OP(insns[t].code);++if(opcode==BPF_EXIT){+gotomark_explored;+}elseif(opcode==BPF_CALL){+PUSH_INSN(t,t+1,FALLTHROUGH);+}elseif(opcode==BPF_JA){+if(BPF_SRC(insns[t].code)!=BPF_K){+ret=-EINVAL;+gotofree_st;+}+/* unconditional jump with single edge */+PUSH_INSN(t,t+insns[t].off+1,FALLTHROUGH);+}else{+/* conditional jump with two edges */+PUSH_INSN(t,t+1,FALLTHROUGH);+PUSH_INSN(t,t+insns[t].off+1,BRANCH);+}+}else{+/* all other non-branch instructions with single+*fall-throughedge+*/+PUSH_INSN(t,t+1,FALLTHROUGH);+}++mark_explored:+st[t]=EXPLORED;+if(POP_INT()==-1){+verbose("pop_int internal bug\n");+ret=-EFAULT;+gotofree_st;+}+}+++for(i=0;i<insn_cnt;i++){+if(st[i]!=EXPLORED){+verbose("unreachable insn %d\n",i);+ret=-EINVAL;+gotofree_st;+}+}++free_st:+kfree(st);+kfree(stack);+returnret;+}+/* look for pseudo eBPF instructions that access map FDs and*replacethemwithactualmappointers*/
@@ -480,6 +659,10 @@ int bpf_check(struct bpf_prog *prog, struct nlattr *tb[BPF_PROG_ATTR_MAX + 1])if(ret<0)gotoskip_full_check;+ret=check_cfg(env);+if(ret<0)+gotoskip_full_check;+/* ret = do_check(env); */skip_full_check:
this patch adds all of eBPF verfier documentation and empty bpf_check()
The end goal for the verifier is to statically check safety of the program.
Verifier will catch:
- loops
- out of range jumps
- unreachable instructions
- invalid instructions
- uninitialized register access
- uninitialized stack access
- misaligned stack access
- out of range stack access
- invalid calling convention
More details in Documentation/networking/filter.txt
Signed-off-by: Alexei Starovoitov <redacted>
---
Documentation/networking/filter.txt | 230 +++++++++++++++++++++++++++++++++++
include/linux/bpf.h | 2 +
kernel/bpf/Makefile | 2 +-
kernel/bpf/syscall.c | 2 +-
kernel/bpf/verifier.c | 151 +++++++++++++++++++++++
5 files changed, 385 insertions(+), 2 deletions(-)
create mode 100644 kernel/bpf/verifier.c
@@ -1001,6 +1001,105 @@ instruction that loads 64-bit immediate value into a dst_reg. Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM which loads 32-bit immediate value into a register.+eBPF verifier+-------------+The safety of the eBPF program is determined in two steps.++First step does DAG check to disallow loops and other CFG validation.+In particular it will detect programs that have unreachable instructions.+(though classic BPF checker allows them)++Second step starts from the first insn and descends all possible paths.+It simulates execution of every insn and observes the state change of+registers and stack.++At the start of the program the register R1 contains a pointer to context+and has type PTR_TO_CTX.+If verifier sees an insn that does R2=R1, then R2 has now type+PTR_TO_CTX as well and can be used on the right hand side of expression.+If R1=PTR_TO_CTX and insn is R2=R1+R1, then R2=UNKNOWN_VALUE,+since addition of two valid pointers makes invalid pointer.+(In 'secure' mode verifier will reject any type of pointer arithmetic to make+sure that kernel addresses don't leak to unprivileged users)++If register was never written to, it's not readable:+ bpf_mov R0 = R2+ bpf_exit+will be rejected, since R2 is unreadable at the start of the program.++After kernel function call, R1-R5 are reset to unreadable and+R0 has a return type of the function.++Since R6-R9 are callee saved, their state is preserved across the call.+ bpf_mov R6 = 1+ bpf_call foo+ bpf_mov R0 = R6+ bpf_exit+is a correct program. If there was R1 instead of R6, it would have+been rejected.++Classic BPF register X is mapped to eBPF register R7 inside sk_convert_filter(),+so that its state is preserved across calls.++load/store instructions are allowed only with registers of valid types, which+are PTR_TO_CTX, PTR_TO_MAP, FRAME_PTR. They are bounds and alignment checked.+For example:+ bpf_mov R1 = 1+ bpf_mov R2 = 2+ bpf_xadd *(u32 *)(R1 + 3) += R2+ bpf_exit+will be rejected, since R1 doesn't have a valid pointer type at the time of+execution of instruction bpf_xadd.++At the start R1 contains pointer to ctx and R1 type is PTR_TO_CTX.+ctx is generic. verifier is configured to known what context is for particular+class of bpf programs. For example, context == skb (for socket filters) and+ctx == seccomp_data for seccomp filters.+A callback is used to customize verifier to restrict eBPF program access to only+certain fields within ctx structure with specified size and alignment.++For example, the following insn:+ bpf_ld R0 = *(u32 *)(R6 + 8)+intends to load a word from address R6 + 8 and store it into R0+If R6=PTR_TO_CTX, via is_valid_access() callback the verifier will know+that offset 8 of size 4 bytes can be accessed for reading, otherwise+the verifier will reject the program.+If R6=FRAME_PTR, then access should be aligned and be within+stack bounds, which are [-MAX_BPF_STACK, 0). In this example offset is 8,+so it will fail verification, since it's out of bounds.++The verifier will allow eBPF program to read data from stack only after+it wrote into it.+Classic BPF verifier does similar check with M[0-15] memory slots.+For example:+ bpf_ld R0 = *(u32 *)(R10 - 4)+ bpf_exit+is invalid program.+Though R10 is correct read-only register and has type FRAME_PTR+and R10 - 4 is within stack bounds, there were no stores into that location.++Pointer register spill/fill is tracked as well, since four (R6-R9)+callee saved registers may not be enough for some programs.++Allowed function calls are customized with bpf_verifier_ops->get_func_proto()+The eBPF verifier will check that registers match argument constraints.+After the call register R0 will be set to return type of the function.++Function calls is a main mechanism to extend functionality of eBPF programs.+Socket filters may let programs to call one set of functions, whereas tracing+filters may allow completely different set.++If a function made accessible to eBPF program, it needs to be thought through+from security point of view. The verifier will guarantee that the function is+called with valid arguments.++seccomp vs socket filters have different security restrictions for classic BPF.+Seccomp solves this by two stage verifier: classic BPF verifier is followed+by seccomp verifier. In case of eBPF one configurable verifier is shared for+all use cases.++See details of eBPF verifier in kernel/bpf/verifier.c+ eBPF maps --------- 'maps' is a generic storage of different types for sharing data between kernel
@@ -1072,6 +1171,137 @@ size. It will not let programs pass junk values to bpf_map_*_elem() functions, so these functions (implemented in C inside kernel) can safely access the pointers in all cases.+Understanding eBPF verifier messages+------------------------------------++The following are few examples of invalid eBPF programs and verifier error+messages as seen in the log:++Program with unreachable instructions:+static struct bpf_insn prog[] = {+ BPF_EXIT_INSN(),+ BPF_EXIT_INSN(),+};+Error:+ unreachable insn 1++Program that reads uninitialized register:+ BPF_MOV64_REG(BPF_REG_0, BPF_REG_2),+ BPF_EXIT_INSN(),+Error:+ 0: (bf) r0 = r2+ R2 !read_ok++Program that doesn't initialize R0 before exiting:+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_1),+ BPF_EXIT_INSN(),+Error:+ 0: (bf) r2 = r1+ 1: (95) exit+ R0 !read_ok++Program that accesses stack out of bounds:+ BPF_ST_MEM(BPF_DW, BPF_REG_10, 8, 0),+ BPF_EXIT_INSN(),+Error:+ 0: (7a) *(u64 *)(r10 +8) = 0+ invalid stack off=8 size=8++Program that doesn't initialize stack before passing its address into function:+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),+ BPF_LD_MAP_FD(BPF_REG_1, 0),+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),+ BPF_EXIT_INSN(),+Error:+ 0: (bf) r2 = r10+ 1: (07) r2 += -8+ 2: (b7) r1 = 0x0+ 3: (85) call 1+ invalid indirect read from stack off -8+0 size 8++Program that uses invalid map_fd=0 while calling to map_lookup_elem() function:+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),+ BPF_LD_MAP_FD(BPF_REG_1, 0),+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),+ BPF_EXIT_INSN(),+Error:+ 0: (7a) *(u64 *)(r10 -8) = 0+ 1: (bf) r2 = r10+ 2: (07) r2 += -8+ 3: (b7) r1 = 0x0+ 4: (85) call 1+ fd 0 is not pointing to valid bpf_map++Program that doesn't check return value of map_lookup_elem() before accessing+map element:+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),+ BPF_LD_MAP_FD(BPF_REG_1, 0),+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),+ BPF_ST_MEM(BPF_DW, BPF_REG_0, 0, 0),+ BPF_EXIT_INSN(),+Error:+ 0: (7a) *(u64 *)(r10 -8) = 0+ 1: (bf) r2 = r10+ 2: (07) r2 += -8+ 3: (b7) r1 = 0x0+ 4: (85) call 1+ 5: (7a) *(u64 *)(r0 +0) = 0+ R0 invalid mem access 'map_value_or_null'++Program that correctly checks map_lookup_elem() returned value for NULL, but+accesses the memory with incorrect alignment:+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),+ BPF_LD_MAP_FD(BPF_REG_1, 0),+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1),+ BPF_ST_MEM(BPF_DW, BPF_REG_0, 4, 0),+ BPF_EXIT_INSN(),+Error:+ 0: (7a) *(u64 *)(r10 -8) = 0+ 1: (bf) r2 = r10+ 2: (07) r2 += -8+ 3: (b7) r1 = 1+ 4: (85) call 1+ 5: (15) if r0 == 0x0 goto pc+1+ R0=map_ptr R10=fp+ 6: (7a) *(u64 *)(r0 +4) = 0+ misaligned access off 4 size 8++Program that correctly checks map_lookup_elem() returned value for NULL and+accesses memory with correct alignment in one side of 'if' branch, but fails+to do so in the other side of 'if' branch:+ BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),+ BPF_LD_MAP_FD(BPF_REG_1, 0),+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),+ BPF_ST_MEM(BPF_DW, BPF_REG_0, 0, 0),+ BPF_EXIT_INSN(),+ BPF_ST_MEM(BPF_DW, BPF_REG_0, 0, 1),+ BPF_EXIT_INSN(),+Error:+ 0: (7a) *(u64 *)(r10 -8) = 0+ 1: (bf) r2 = r10+ 2: (07) r2 += -8+ 3: (b7) r1 = 1+ 4: (85) call 1+ 5: (15) if r0 == 0x0 goto pc+2+ R0=map_ptr R10=fp+ 6: (7a) *(u64 *)(r0 +0) = 0+ 7: (95) exit++ from 5 to 8: R0=imm0 R10=fp+ 8: (7a) *(u64 *)(r0 +0) = 1+ R0 invalid mem access 'imm'+ Testing -------
@@ -0,0 +1,151 @@+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,but+*WITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.SeetheGNU+*GeneralPublicLicenseformoredetails.+*/+#include<linux/kernel.h>+#include<linux/types.h>+#include<linux/slab.h>+#include<linux/bpf.h>+#include<linux/filter.h>+#include<net/netlink.h>+#include<linux/file.h>++/* bpf_check() is a static code analyzer that walks eBPF program+*instructionbyinstructionandupdatesregister/stackstate.+*Allpathsofconditionalbranchesareanalyzeduntil'bpf_exit'insn.+*+*Atthefirstpassdepth-first-searchverifiesthattheBPFprogramisaDAG.+*Itrejectsthefollowingprograms:+*-largerthanBPF_MAXINSNSinsns+*-ifloopispresent(detectedviaback-edge)+*-unreachableinsnsexist(shouldn'tbeaforest.program=onefunction)+*-outofboundsormalformedjumps+*Thesecondpassisallpossiblepathdescentfromthe1stinsn.+*Conditionalbranchtargetinsnskeepalinklistofverifierstates.+*Ifthestatealreadyvisited,thispathcanbepruned.+*Ifitwasn'taDAG,suchstateprunningwouldbeincorrect,sinceitwould+*skipcycles.Sinceit'sanalyzingallpathesthroughtheprogram,+*thelengthoftheanalysisislimitedto32kinsn,whichmaybehiteven+*ifinsn_cnt<4K,buttherearetoomanybranchesthatchangestack/regs.+*Numberof'branchestobeanalyzed'islimitedto1k+*+*Onentrytoeachinstruction,eachregisterhasatype,andtheinstruction+*changesthetypesoftheregistersdependingoninstructionsemantics.+*IfinstructionisBPF_MOV64_REG(BPF_REG_1,BPF_REG_5),thentypeofR5is+*copiedtoR1.+*+*Allregistersare64-bit(evenon32-bitarch)+*R0-returnregister+*R1-R5argumentpassingregisters+*R6-R9calleesavedregisters+*R10-framepointerread-only+*+*AtthestartofBPFprogramtheregisterR1containsapointertobpf_context+*andhastypePTR_TO_CTX.+*+*MostofthetimetheregistershaveUNKNOWN_VALUEtype,which+*meanstheregisterhassomevalue,butit'snotavalidpointer.+*Verifierdoesn'tattemptotrackallarithmeticoperationsonpointers.+*Theonlyspecialcaseisthesequence:+*BPF_MOV64_REG(BPF_REG_1,BPF_REG_10),+*BPF_ALU64_IMM(BPF_ADD,BPF_REG_1,-20),+*1stinsncopiesR10(whichhasFRAME_PTR)typeintoR1+*and2ndarithmeticinstructionispatternmatchedtorecognize+*thatitwantstoconstructapointertosomeelementwithinstack.+*Soafter2ndinsn,theregisterR1hastypePTR_TO_STACK+*(and-20constantissavedforfurtherstackboundschecking).+*Meaningthatthisregisapointertostackplusknownimmediateconstant.+*+*Whenprogramisdoingloadorstoreinsnsthetypeofbaseregistercanbe:+*PTR_TO_MAP_VALUE,PTR_TO_CTX,FRAME_PTR.Thesearethreepointertypesrecognized+*bycheck_mem_access()function.+*+*PTR_TO_MAP_VALUEmeansthatthisregisterispointingto'mapelementvalue'+*andtherangeof[ptr,ptr+map'svalue_size)isaccessible.+*+*registersusedtopasspointerstofunctioncallsareverifiedagainst+*functionprototypes+*+*ARG_PTR_TO_MAP_KEYisafunctionargumentconstraint.+*Itmeansthattheregistertypepassedtothisfunctionmustbe+*PTR_TO_STACKanditwillbeusedinsidethefunctionas+*'pointertomapelementkey'+*+*Forexampletheargumentconstraintsforbpf_map_lookup_elem():+*.ret_type=RET_PTR_TO_MAP_VALUE_OR_NULL,+*.arg1_type=ARG_CONST_MAP_ID,+*.arg2_type=ARG_PTR_TO_MAP_KEY,+*+*ret_typesaysthatthisfunctionreturns'pointertomapelemvalueornull'+*1stargumentisa'constimmediate'valuewhichmustbeoneofvalidmap_ids.+*2ndargumentisapointertostack,whichwillbeusedinsidethefunctionas+*apointertomapelementkey.+*+*Onthekernelsidethehelperfunctionlookslike:+*u64bpf_map_lookup_elem(u64r1,u64r2,u64r3,u64r4,u64r5)+*{+*structbpf_map*map;+*intmap_id=r1;+*void*key=(void*)(unsignedlong)r2;+*void*value;+*+*herekernelcanaccess'key'pointersafely,knowingthat+*[key,key+map->key_size)bytesarevalidandwereinitializedon+*thestackofeBPFprogram.+*}+*+*CorrespondingeBPFprogramlookedlike:+*BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),// after this insn R2 type is FRAME_PTR+*BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-4),// after this insn R2 type is PTR_TO_STACK+*BPF_MOV64_IMM(BPF_REG_1,MAP_ID),// after this insn R1 type is CONST_ARG+*BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem),+*hereverifierlooksaprototypeofmap_lookup_elemandsees:+*.arg1_type==ARG_CONST_MAP_IDandR1->type==CONST_ARG,whichisoksofar,+*thenitgoesandfindsamapwithmap_idequaltoR1->immvalue.+*Nowverifierknowsthatthismaphaskeyofkey_sizebytes+*+*Then.arg2_type==ARG_PTR_TO_MAP_KEYandR2->type==PTR_TO_STACK,oksofar,+*Nowverifierchecksthat[R2,R2+map'skey_size)arewithinstacklimits+*andwereinitializedpriortothiscall.+*Ifit'sok,thenverifierallowsthisBPF_CALLinsnandlooksat+*.ret_typewhichisRET_PTR_TO_MAP_VALUE_OR_NULL,soitsets+*R0->type=PTR_TO_MAP_VALUE_OR_NULLwhichmeansbpf_map_lookup_elem()function+*returnsetherpointertomapvalueorNULL.+*+*WhentypePTR_TO_MAP_VALUE_OR_NULLpassesthrough'if(reg!=0)goto+off'+*insn,theregisterholdingthatpointerinthetruebranchchangesstateto+*PTR_TO_MAP_VALUEandthesameregisterchangesstatetoCONST_IMMinthefalse+*branch.Seecheck_cond_jmp_op().+*+*AfterthecallR0issettoreturntypeofthefunctionandregistersR1-R5+*aresettoNOT_INITtoindicatethattheyarenolongerreadable.+*+*load/storealignmentischecked:+*BPF_STX_MEM(BPF_DW,dest_reg,src_reg,3)+*isrejected,becauseit'smisaligned+*+*load/storetostackareboundscheckedandregisterspillistracked+*BPF_STX_MEM(BPF_B,BPF_REG_10,src_reg,0)+*isrejected,becauseit'soutofbounds+*+*load/storetomapareboundschecked:+*BPF_STX_MEM(BPF_H,dest_reg,src_reg,8)+*isok,ifdest_reg->type==PTR_TO_MAP_VALUEand+*8+sizeof(u16)<=map_info->value_size+*+*load/storetobpf_contextarecheckedagainstknownfields+*/++intbpf_check(structbpf_prog*prog,structnlattr*tb[BPF_PROG_ATTR_MAX+1])+{+intret=-EINVAL;++returnret;+}
eBPF programs are safe run-to-completion functions with load/unload
methods from userspace similar to kernel modules.
User space API:
- load eBPF program
fd = bpf_prog_load(bpf_prog_type, struct nlattr *prog, int len)
where 'prog' is a sequence of sections (TEXT, LICENSE)
TEXT - array of eBPF instructions
LICENSE - must be GPL compatible to call helper functions marked gpl_only
- unload eBPF program
close(fd)
User space example of syscall(__NR_bpf, BPF_PROG_LOAD, prog_type, ...)
follows in later patches
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
include/linux/bpf.h | 36 +++++++++
include/linux/filter.h | 9 ++-
include/uapi/linux/bpf.h | 28 +++++++
kernel/bpf/syscall.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++
net/core/filter.c | 2 +
5 files changed, 269 insertions(+), 2 deletions(-)
@@ -47,4 +47,40 @@ void bpf_register_map_type(struct bpf_map_type_list *tl);voidbpf_map_put(structbpf_map*map);structbpf_map*bpf_map_get(structfdf);+/* eBPF function prototype used by verifier to allow BPF_CALLs from eBPF programs+*toin-kernelhelperfunctionsandforadjustingimm32fieldinBPF_CALL+*instructionsafterverifying+*/+structbpf_func_proto{+u64(*func)(u64r1,u64r2,u64r3,u64r4,u64r5);+boolgpl_only;+};++structbpf_verifier_ops{+/* return eBPF function prototype for verification */+conststructbpf_func_proto*(*get_func_proto)(enumbpf_func_idfunc_id);+};++structbpf_prog_type_list{+structlist_headlist_node;+structbpf_verifier_ops*ops;+enumbpf_prog_typetype;+};++voidbpf_register_prog_type(structbpf_prog_type_list*tl);++structbpf_prog_info{+atomic_trefcnt;+boolis_gpl_compatible;+enumbpf_prog_typeprog_type;+structbpf_verifier_ops*ops;+structbpf_map**used_maps;+u32used_map_cnt;+};++structbpf_prog;++voidbpf_prog_put(structbpf_prog*prog);+structbpf_prog*bpf_prog_get(u32ufd);+#endif /* _LINUX_BPF_H */
@@ -31,11 +31,16 @@ struct sock_fprog_kern {structsk_buff;structsock;structseccomp_data;+structbpf_prog_info;structbpf_prog{u32jited:1,/* Is our filter JIT'ed? */-len:31;/* Number of filter blocks */-structsock_fprog_kern*orig_prog;/* Original BPF program */+has_info:1,/* whether 'info' is valid */+len:30;/* Number of filter blocks */+union{+structsock_fprog_kern*orig_prog;/* Original BPF program */+structbpf_prog_info*info;+};unsignedint(*bpf_func)(conststructsk_buff*skb,conststructbpf_insn*filter);union{
@@ -315,6 +317,197 @@ err_put:returnerr;}+staticLIST_HEAD(bpf_prog_types);++staticintfind_prog_type(enumbpf_prog_typetype,structbpf_prog*prog)+{+structbpf_prog_type_list*tl;++list_for_each_entry(tl,&bpf_prog_types,list_node){+if(tl->type==type){+prog->info->ops=tl->ops;+prog->info->prog_type=type;+return0;+}+}+return-EINVAL;+}++voidbpf_register_prog_type(structbpf_prog_type_list*tl)+{+list_add(&tl->list_node,&bpf_prog_types);+}++/* drop refcnt on maps used by eBPF program and free auxilary data */+staticvoidfree_bpf_prog_info(structbpf_prog_info*info)+{+inti;++for(i=0;i<info->used_map_cnt;i++)+bpf_map_put(info->used_maps[i]);++kfree(info->used_maps);+kfree(info);+}++voidbpf_prog_put(structbpf_prog*prog)+{+BUG_ON(!prog->has_info);+if(atomic_dec_and_test(&prog->info->refcnt)){+free_bpf_prog_info(prog->info);+bpf_prog_free(prog);+}+}++staticintbpf_prog_release(structinode*inode,structfile*filp)+{+structbpf_prog*prog=filp->private_data;++bpf_prog_put(prog);+return0;+}++staticconststructfile_operationsbpf_prog_fops={+.release=bpf_prog_release,+};++staticstructbpf_prog*get_prog(structfdf)+{+structbpf_prog*prog;++if(!f.file)+returnERR_PTR(-EBADF);++if(f.file->f_op!=&bpf_prog_fops){+fdput(f);+returnERR_PTR(-EINVAL);+}++prog=f.file->private_data;++returnprog;+}++/* called by sockets/tracing/seccomp before attaching program to an event+*pairswithbpf_prog_put()+*/+structbpf_prog*bpf_prog_get(u32ufd)+{+structfdf=fdget(ufd);+structbpf_prog*prog;++prog=get_prog(f);++if(IS_ERR(prog))+returnprog;++atomic_inc(&prog->info->refcnt);+fdput(f);+returnprog;+}++staticconststructnla_policyprog_policy[BPF_PROG_ATTR_MAX+1]={+[BPF_PROG_TEXT]={.type=NLA_BINARY},+[BPF_PROG_LICENSE]={.type=NLA_NUL_STRING},+};++staticintbpf_prog_load(enumbpf_prog_typetype,structnlattr__user*uattr,+intlen)+{+structnlattr*tb[BPF_PROG_ATTR_MAX+1];+structbpf_prog*prog;+structnlattr*attr;+size_tinsn_len;+interr;+boolis_gpl;++if(len<=0||len>BPF_PROG_MAX_ATTR_SIZE)+return-EINVAL;++attr=kmalloc(len,GFP_USER);+if(!attr)+return-ENOMEM;++/* copy eBPF program from user space */+err=-EFAULT;+if(copy_from_user(attr,uattr,len)!=0)+gotofree_attr;++/* perform basic validation */+err=nla_parse(tb,BPF_PROG_ATTR_MAX,attr,len,prog_policy);+if(err<0)+gotofree_attr;++err=-EINVAL;+/* look for mandatory license string */+if(!tb[BPF_PROG_LICENSE])+gotofree_attr;++/* eBPF programs must be GPL compatible to use GPL-ed functions */+is_gpl=license_is_gpl_compatible(nla_data(tb[BPF_PROG_LICENSE]));++/* look for mandatory array of eBPF instructions */+if(!tb[BPF_PROG_TEXT])+gotofree_attr;++insn_len=nla_len(tb[BPF_PROG_TEXT]);+if(insn_len%sizeof(structbpf_insn)!=0||insn_len<=0)+gotofree_attr;++/* plain bpf_prog allocation */+err=-ENOMEM;+prog=kmalloc(bpf_prog_size(insn_len),GFP_USER);+if(!prog)+gotofree_attr;++prog->len=insn_len/sizeof(structbpf_insn);+memcpy(prog->insns,nla_data(tb[BPF_PROG_TEXT]),insn_len);+prog->orig_prog=NULL;+prog->jited=0;+prog->has_info=0;++/* allocate eBPF related auxilary data */+prog->info=kzalloc(sizeof(structbpf_prog_info),GFP_USER);+if(!prog->info)+gotofree_prog;+prog->has_info=1;+atomic_set(&prog->info->refcnt,1);+prog->info->is_gpl_compatible=is_gpl;++/* find program type: socket_filter vs tracing_filter */+err=find_prog_type(type,prog);+if(err<0)+gotofree_prog_info;++/* run eBPF verifier */+/* err = bpf_check(prog, tb); */++if(err<0)+gotofree_prog_info;++/* eBPF program is ready to be JITed */+bpf_prog_select_runtime(prog);++err=anon_inode_getfd("bpf-prog",&bpf_prog_fops,prog,O_RDWR|O_CLOEXEC);++if(err<0)+/* failed to allocate fd */+gotofree_prog_info;++/* user supplied eBPF prog attributes are no longer needed */+kfree(attr);++returnerr;++free_prog_info:+free_bpf_prog_info(prog->info);+free_prog:+bpf_prog_free(prog);+free_attr:+kfree(attr);+returnerr;+}+SYSCALL_DEFINE5(bpf,int,cmd,unsignedlong,arg2,unsignedlong,arg3,unsignedlong,arg4,unsignedlong,arg5){
'maps' is a generic storage of different types for sharing data between kernel
and userspace.
The maps are accessed from user space via BPF syscall, which has commands:
- create a map with given type and attributes
fd = bpf_map_create(map_type, struct nlattr *attr, int len)
returns fd or negative error
- lookup key in a given map referenced by fd
err = bpf_map_lookup_elem(int fd, void *key, void *value)
returns zero and stores found elem into value or negative error
- create or update key/value pair in a given map
err = bpf_map_update_elem(int fd, void *key, void *value)
returns zero or negative error
- find and delete element by key in a given map
err = bpf_map_delete_elem(int fd, void *key)
- iterate map elements (based on input key return next_key)
err = bpf_map_get_next_key(int fd, void *key, void *next_key)
- close(fd) deletes the map
Signed-off-by: Alexei Starovoitov <redacted>
---
include/linux/bpf.h | 8 ++
include/uapi/linux/bpf.h | 25 ++++++
kernel/bpf/syscall.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 231 insertions(+)
@@ -319,6 +319,31 @@ enum bpf_cmd {*mapisdeletedwhenfdisclosed*/BPF_MAP_CREATE,++/* lookup key in a given map+*err=bpf_map_lookup_elem(intfd,void*key,void*value)+*returnszeroandstoresfoundelemintovalue+*ornegativeerror+*/+BPF_MAP_LOOKUP_ELEM,++/* create or update key/value pair in a given map+*err=bpf_map_update_elem(intfd,void*key,void*value)+*returnszeroornegativeerror+*/+BPF_MAP_UPDATE_ELEM,++/* find and delete elem by key in a given map+*err=bpf_map_delete_elem(intfd,void*key)+*returnszeroornegativeerror+*/+BPF_MAP_DELETE_ELEM,++/* lookup key in a given map and return next key+*err=bpf_map_get_elem(intfd,void*key,void*next_key)+*returnszeroandstoresnextkeyornegativeerror+*/+BPF_MAP_GET_NEXT_KEY,};enumbpf_map_attributes{
@@ -131,6 +132,189 @@ free_attr:returnerr;}+/* if error is returned, fd is released.+*Onsuccesscallershouldcompletefdaccesswithmatchingfdput()+*/+structbpf_map*bpf_map_get(structfdf)+{+structbpf_map*map;++if(!f.file)+returnERR_PTR(-EBADF);++if(f.file->f_op!=&bpf_map_fops){+fdput(f);+returnERR_PTR(-EINVAL);+}++map=f.file->private_data;++returnmap;+}++staticintmap_lookup_elem(intufd,void__user*ukey,void__user*uvalue)+{+structfdf=fdget(ufd);+structbpf_map*map;+void*key,*value;+interr;++map=bpf_map_get(f);+if(IS_ERR(map))+returnPTR_ERR(map);++err=-ENOMEM;+key=kmalloc(map->key_size,GFP_USER);+if(!key)+gotoerr_put;++err=-EFAULT;+if(copy_from_user(key,ukey,map->key_size)!=0)+gotofree_key;++err=-ESRCH;+rcu_read_lock();+value=map->ops->map_lookup_elem(map,key);+if(!value)+gotoerr_unlock;++err=-EFAULT;+if(copy_to_user(uvalue,value,map->value_size)!=0)+gotoerr_unlock;++err=0;++err_unlock:+rcu_read_unlock();+free_key:+kfree(key);+err_put:+fdput(f);+returnerr;+}++staticintmap_update_elem(intufd,void__user*ukey,void__user*uvalue)+{+structfdf=fdget(ufd);+structbpf_map*map;+void*key,*value;+interr;++map=bpf_map_get(f);+if(IS_ERR(map))+returnPTR_ERR(map);++err=-ENOMEM;+key=kmalloc(map->key_size,GFP_USER);+if(!key)+gotoerr_put;++err=-EFAULT;+if(copy_from_user(key,ukey,map->key_size)!=0)+gotofree_key;++err=-ENOMEM;+value=kmalloc(map->value_size,GFP_USER);+if(!value)+gotofree_key;++err=-EFAULT;+if(copy_from_user(value,uvalue,map->value_size)!=0)+gotofree_value;++/* eBPF program that use maps are running under rcu_read_lock(),+*thereforeallmapaccessorsrelyonthisfact,sodothesamehere+*/+rcu_read_lock();+err=map->ops->map_update_elem(map,key,value);+rcu_read_unlock();++free_value:+kfree(value);+free_key:+kfree(key);+err_put:+fdput(f);+returnerr;+}++staticintmap_delete_elem(intufd,void__user*ukey)+{+structfdf=fdget(ufd);+structbpf_map*map;+void*key;+interr;++map=bpf_map_get(f);+if(IS_ERR(map))+returnPTR_ERR(map);++err=-ENOMEM;+key=kmalloc(map->key_size,GFP_USER);+if(!key)+gotoerr_put;++err=-EFAULT;+if(copy_from_user(key,ukey,map->key_size)!=0)+gotofree_key;++rcu_read_lock();+err=map->ops->map_delete_elem(map,key);+rcu_read_unlock();++free_key:+kfree(key);+err_put:+fdput(f);+returnerr;+}++staticintmap_get_next_key(intufd,void__user*ukey,void__user*unext_key)+{+structfdf=fdget(ufd);+structbpf_map*map;+void*key,*next_key;+interr;++map=bpf_map_get(f);+if(IS_ERR(map))+returnPTR_ERR(map);++err=-ENOMEM;+key=kmalloc(map->key_size,GFP_USER);+if(!key)+gotoerr_put;++err=-EFAULT;+if(copy_from_user(key,ukey,map->key_size)!=0)+gotofree_key;++err=-ENOMEM;+next_key=kmalloc(map->key_size,GFP_USER);+if(!next_key)+gotofree_key;++rcu_read_lock();+err=map->ops->map_get_next_key(map,key,next_key);+rcu_read_unlock();+if(err)+gotofree_next_key;++err=-EFAULT;+if(copy_to_user(unext_key,next_key,map->key_size)!=0)+gotofree_next_key;++err=0;++free_next_key:+kfree(next_key);+free_key:+kfree(key);+err_put:+fdput(f);+returnerr;+}+SYSCALL_DEFINE5(bpf,int,cmd,unsignedlong,arg2,unsignedlong,arg3,unsignedlong,arg4,unsignedlong,arg5){
@@ -325,6 +325,7 @@ 316 common renameat2 sys_renameat2 317 common seccomp sys_seccomp 318 common getrandom sys_getrandom+319 common bpf sys_bpf # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -870,5 +870,6 @@ asmlinkage long sys_seccomp(unsigned int op, unsigned int flags,constchar__user*uargs);asmlinkagelongsys_getrandom(char__user*buf,size_tcount,unsignedintflags);-+asmlinkagelongsys_bpf(intcmd,unsignedlongarg2,unsignedlongarg3,+unsignedlongarg4,unsignedlongarg5);#endif
eBPF can be used from user space.
uapi/linux/bpf.h: eBPF instruction set definition
linux/filter.h: the rest
This patch only moves macro definitions, but practically it freezes existing
eBPF instruction set, though new instructions can still be added in the future.
These eBPF definitions cannot go into uapi/linux/filter.h, since the names
may conflict with existing applications.
Signed-off-by: Alexei Starovoitov <redacted>
---
include/linux/filter.h | 305 +------------------------------------------
include/uapi/linux/Kbuild | 1 +
include/uapi/linux/bpf.h | 314 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 316 insertions(+), 304 deletions(-)
create mode 100644 include/uapi/linux/bpf.h
From: David Laight <hidden> Date: 2014-08-13 08:54:29
From: Of Alexei Starovoitov
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
There is nothing to stop any JIT software detecting the instruction pair.
David
From: Daniel Borkmann <hidden> Date: 2014-08-13 09:18:10
On 08/13/2014 09:57 AM, Alexei Starovoitov wrote:
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM
which loads 32-bit immediate value into a register.
x64 JITs it as single 'movabsq %rax, imm64'
arm64 may JIT as sequence of four 'movk x0, #imm16, lsl #shift' insn
Note that old eBPF programs are binary compatible with new interpreter.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
For follow-ups on this series, can you put the actual motivation
for this change from the cover letter into this commit log as it
otherwise doesn't say anything clearly why it is needed. Code and
test case looks good to me.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 16:08:48
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
This might be unnecessarily difficult for fancy static analysis tools
to reason about. Would it make sense to assign two different codes
for this? For example, insn[0].code = code_for_load_low,
insns[1].code = code_for_load_high, along with a verifier check that
they come in matched pairs and that code_for_load_high isn't a jump
target?
(Something else that I find confusing about eBPF: the instruction
mnemonics are very strange. Have you considered giving them real
names? For example, load.imm.low instead of BPF_LD | BPF_DW | BPF_IMM
is easier to read and pronounce.)
--Andy
On Wed, Aug 13, 2014 at 1:52 AM, David Laight [off-list ref] wrote:
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
that was what I used before in pred_tree_walker->ebpf patch
(4 existing instructions (2 movs, shift, or) to load 'pred' pointer)
It's slower in interpreter than single instruction.
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
size of instruction is not important. 99% of instructions are 8 byte long
and one is 16 byte. Big deal. It doesn't affect interpreter performance,
easy for verifier and was straightforward to do in LLVM as well.
There is nothing to stop any JIT software detecting the instruction pair.
well, it's actually very complicated to detect a sequence of
instructions that compute single 64-bit value.
Patch #11 detects and patches pseudo BPF_LD_IMM64 in
a single 'for' loop (see replace_map_fd_with_map_ptr), because
it's _single_ instruction. Any sequence of insns would require
building control and data flow graphs for verifier and JIT.
If you remember I resisted initially when Chema proposed
'load 64-bit immediate' equivalent, since back then the use cases
didn't require it. With maps done via FDs, the need has arisen.
On Wed, Aug 13, 2014 at 2:17 AM, Daniel Borkmann [off-list ref] wrote:
On 08/13/2014 09:57 AM, Alexei Starovoitov wrote:
quoted
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single
instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM
which loads 32-bit immediate value into a register.
x64 JITs it as single 'movabsq %rax, imm64'
arm64 may JIT as sequence of four 'movk x0, #imm16, lsl #shift' insn
Note that old eBPF programs are binary compatible with new interpreter.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
For follow-ups on this series, can you put the actual motivation
for this change from the cover letter into this commit log as it
otherwise doesn't say anything clearly why it is needed. Code and
test case looks good to me.
ok. As you saw the full explanation is long, so I opted for 'it_does_this'
commit log. In the next rev will add more reasons to this log. Sure.
From: Daniel Borkmann <hidden> Date: 2014-08-13 17:40:26
On 08/13/2014 07:34 PM, Alexei Starovoitov wrote:
On Wed, Aug 13, 2014 at 2:17 AM, Daniel Borkmann [off-list ref] wrote:
quoted
On 08/13/2014 09:57 AM, Alexei Starovoitov wrote:
quoted
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single
instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
Classic BPF has similar instruction: BPF_LD | BPF_W | BPF_IMM
which loads 32-bit immediate value into a register.
x64 JITs it as single 'movabsq %rax, imm64'
arm64 may JIT as sequence of four 'movk x0, #imm16, lsl #shift' insn
Note that old eBPF programs are binary compatible with new interpreter.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
For follow-ups on this series, can you put the actual motivation
for this change from the cover letter into this commit log as it
otherwise doesn't say anything clearly why it is needed. Code and
test case looks good to me.
ok. As you saw the full explanation is long, so I opted for 'it_does_this'
commit log. In the next rev will add more reasons to this log. Sure.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 17:41:21
On Wed, Aug 13, 2014 at 10:30 AM, Alexei Starovoitov [off-list ref] wrote:
On Wed, Aug 13, 2014 at 1:52 AM, David Laight [off-list ref] wrote:
quoted
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
that was what I used before in pred_tree_walker->ebpf patch
(4 existing instructions (2 movs, shift, or) to load 'pred' pointer)
It's slower in interpreter than single instruction.
quoted
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
size of instruction is not important. 99% of instructions are 8 byte long
and one is 16 byte. Big deal. It doesn't affect interpreter performance,
easy for verifier and was straightforward to do in LLVM as well.
quoted
There is nothing to stop any JIT software detecting the instruction pair.
well, it's actually very complicated to detect a sequence of
instructions that compute single 64-bit value.
Patch #11 detects and patches pseudo BPF_LD_IMM64 in
a single 'for' loop (see replace_map_fd_with_map_ptr), because
it's _single_ instruction. Any sequence of insns would require
building control and data flow graphs for verifier and JIT.
If you remember I resisted initially when Chema proposed
'load 64-bit immediate' equivalent, since back then the use cases
didn't require it. With maps done via FDs, the need has arisen.
But don't you need some kind of detection anyway to handle the case
where something jumps to the middle of the "load 64-bit immediate"? I
think it would be fine to require a particular sequence to load 64-bit
immediates if you want the JIT to optimize it well, though.
--Andy
On Wed, Aug 13, 2014 at 9:08 AM, Andy Lutomirski [off-list ref] wrote:
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
This might be unnecessarily difficult for fancy static analysis tools
to reason about. Would it make sense to assign two different codes
for this? For example, insn[0].code = code_for_load_low,
insns[1].code = code_for_load_high, along with a verifier check that
they come in matched pairs and that code_for_load_high isn't a jump
target?
see my reply to David for the same thing. Short answer is that
sequence of instructions (even if it is a pair of instructions like this)
is very hard to detect in verifier and JITs.
As soon as we give compiler two instructions instead of one,
compiler may optimize them in a fancy ways. Like two loads of
64-bit immediate with upper 32-bit the same, may came out as
4 instructions: load_high, load_low, load_low, mov.
Or in some cases as single load_low, etc.
load 64-bit imm has to stay as single instruction to be verifiable
and patch-able easily.
One can argue: force compiler to emit load_low and load_hi
always together, but then that's exactly what I have. It's a single insn.
(Something else that I find confusing about eBPF: the instruction
mnemonics are very strange. Have you considered giving them real
names? For example, load.imm.low instead of BPF_LD | BPF_DW | BPF_IMM
is easier to read and pronounce.)
BPF_LD | BPF_DW | BPF_IMM is not really a name. It's macro
for cases when instructions are generated from inside the kernel.
Instructions mnemonics are not defined yet.
llvm emits assembler code like:
bpf_prog2:
ldw r1, 16(r1)
std -8(r10), r1
mov r1, 1
std -16(r10), r1
ld_64 r1, 1
mov r2, r10
addi r2, -8
call 4
jeqi r0, 0 goto .LBB1_2
ldd r1, 0(r0)
addi r1, 1
std 0(r0), r1
.LBB1_3:
mov r0, 0
ret
...
I'm open to change assembler/disassembler mnemonics.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 18:36:10
On Wed, Aug 13, 2014 at 10:44 AM, Alexei Starovoitov [off-list ref] wrote:
On Wed, Aug 13, 2014 at 9:08 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
add BPF_LD_IMM64 instruction to load 64-bit immediate value into register.
All previous instructions were 8-byte. This is first 16-byte instruction.
Two consecutive 'struct bpf_insn' blocks are interpreted as single instruction:
insn[0/1].code = BPF_LD | BPF_DW | BPF_IMM
insn[0/1].dst_reg = destination register
insn[0].imm = lower 32-bit
insn[1].imm = upper 32-bit
This might be unnecessarily difficult for fancy static analysis tools
to reason about. Would it make sense to assign two different codes
for this? For example, insn[0].code = code_for_load_low,
insns[1].code = code_for_load_high, along with a verifier check that
they come in matched pairs and that code_for_load_high isn't a jump
target?
see my reply to David for the same thing. Short answer is that
sequence of instructions (even if it is a pair of instructions like this)
is very hard to detect in verifier and JITs.
As soon as we give compiler two instructions instead of one,
compiler may optimize them in a fancy ways. Like two loads of
64-bit immediate with upper 32-bit the same, may came out as
4 instructions: load_high, load_low, load_low, mov.
Or in some cases as single load_low, etc.
load 64-bit imm has to stay as single instruction to be verifiable
and patch-able easily.
One can argue: force compiler to emit load_low and load_hi
always together, but then that's exactly what I have. It's a single insn.
The compiler can still think of it as a single insn, though, but some
future compiler might not. In any case, I think that, if you use the
same code for high and for low, you need logic in the JIT that's at
least as complicated. For example, what happens if you have two
consecutive 64-bit immediate loads to the same register? Now you have
four consecutive 8-byte insn words that differ only in their immediate
values, and you need to split them correctly.
quoted
(Something else that I find confusing about eBPF: the instruction
mnemonics are very strange. Have you considered giving them real
names? For example, load.imm.low instead of BPF_LD | BPF_DW | BPF_IMM
is easier to read and pronounce.)
BPF_LD | BPF_DW | BPF_IMM is not really a name. It's macro
for cases when instructions are generated from inside the kernel.
Instructions mnemonics are not defined yet.
llvm emits assembler code like:
bpf_prog2:
ldw r1, 16(r1)
std -8(r10), r1
mov r1, 1
std -16(r10), r1
ld_64 r1, 1
mov r2, r10
addi r2, -8
call 4
jeqi r0, 0 goto .LBB1_2
ldd r1, 0(r0)
addi r1, 1
std 0(r0), r1
.LBB1_3:
mov r0, 0
ret
...
I'm open to change assembler/disassembler mnemonics.
Ah, ok. I didn't realize that there were mnemonics at all.
--Andy
--
Andy Lutomirski
AMA Capital Management, LLC
On Wed, Aug 13, 2014 at 11:35 AM, Andy Lutomirski [off-list ref] wrote:
The compiler can still think of it as a single insn, though, but some
future compiler might not.
I think that would be very dangerous.
compiler (user space) and kernel interpreter must have the same
understanding of ISA.
In any case, I think that, if you use the
same code for high and for low, you need logic in the JIT that's at
least as complicated.
why do you think so? Handling of pseudo BPF_LD_IMM64 is done
in single patch #11 which is one of the smallest...
For example, what happens if you have two
consecutive 64-bit immediate loads to the same register? Now you have
four consecutive 8-byte insn words that differ only in their immediate
values, and you need to split them correctly.
I don't need to do anything special in this case.
Two 16-byte instructions back to back is not a problem.
Interpreter or JIT don't care whether they move the same or different
immediates into the same or different register. Interpreter and JITs
are dumb on purpose.
when verifier sees two back to back ld_imm64, the 2nd will simply
override the value loaded by first one. It's not any different than
two back to back 'mov dst_reg, imm32' instructions.
From: "H. Peter Anvin" <hpa@zytor.com> Date: 2014-08-13 21:17:15
On 08/13/2014 02:02 PM, Alexei Starovoitov wrote:
On Wed, Aug 13, 2014 at 11:35 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The compiler can still think of it as a single insn, though, but some
future compiler might not.
I think that would be very dangerous.
compiler (user space) and kernel interpreter must have the same
understanding of ISA.
Only at the point of the interface layer. The compiler can treat it as
a single instruction internally, the JIT can do peephole optimization,
but as long as the instruction stream at the boundary matches the
official ISA spec everything is fine.
-hpa
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 21:17:48
On Wed, Aug 13, 2014 at 2:02 PM, Alexei Starovoitov [off-list ref] wrote:
On Wed, Aug 13, 2014 at 11:35 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The compiler can still think of it as a single insn, though, but some
future compiler might not.
I think that would be very dangerous.
compiler (user space) and kernel interpreter must have the same
understanding of ISA.
quoted
In any case, I think that, if you use the
same code for high and for low, you need logic in the JIT that's at
least as complicated.
why do you think so? Handling of pseudo BPF_LD_IMM64 is done
in single patch #11 which is one of the smallest...
quoted
For example, what happens if you have two
consecutive 64-bit immediate loads to the same register? Now you have
four consecutive 8-byte insn words that differ only in their immediate
values, and you need to split them correctly.
I don't need to do anything special in this case.
Two 16-byte instructions back to back is not a problem.
Interpreter or JIT don't care whether they move the same or different
immediates into the same or different register. Interpreter and JITs
are dumb on purpose.
when verifier sees two back to back ld_imm64, the 2nd will simply
override the value loaded by first one. It's not any different than
two back to back 'mov dst_reg, imm32' instructions.
But this patch makes the JIT code (and any interpreter) weirdly
stateful. You have:
+ case BPF_LD | BPF_IMM | BPF_DW:
+ /* movabsq %rax, imm64 */
+ EMIT2(add_1mod(0x48, dst_reg), add_1reg(0xB8, dst_reg));
+ EMIT(insn->imm, 4);
+ insn++;
+ i++;
+ EMIT(insn->imm, 4);
+ break;
If you have more than two BPF_LD | BPF_IMM | BPF_DW instructions in a
row, then the way in which they pair up depends on where you start.
I think it would be a lot clearer if you made these be "load low" and
"load high", with JIT code like:
+ case BPF_LOAD_LOW:
+ /* movabsq %rax, imm64 */
+ if (next insn is BPF_LOAD_HIGH) {
+ EMIT2(add_1mod(0x48, dst_reg),
add_1reg(0xB8, dst_reg));
+ EMIT(insn->imm, 4);
+ insn++;
+ i++;
+ EMIT(insn->imm, 4);
+ } else {
+ emit a real load low;
+ }
+ break;
(and you'd have to deal with whether load low by itself is illegal,
zero extends, sign extends, or preserves high bits).
Alternatively, and possibly better, you could have a real encoding for
multiword instructions. Reserve a bit in the opcode to mark a
continuation of the previous instruction, and do:
+ case BPF_LD | BPF_IMM | BPF_DW:
+ assert(insn[1] in bounds && insn[1].code == BPF_CONT);
+ /* movabsq %rax, imm64 */
+ EMIT2(add_1mod(0x48, dst_reg), add_1reg(0xB8, dst_reg));
+ EMIT(insn->imm, 4);
+ insn++;
+ i++;
+ EMIT(insn->imm, 4);
+ break;
This has a nice benefit for future-proofing: it gives you 119 bits of
payload for 16-byte instructions.
On the other hand, a u8 for the opcode is kind of small, and killing
half of that space like this is probably bad. Maybe reserve two high
bits, with:
0: normal opcode or start of a multiword sequence
1: continuation of a multiword sequence
2, 3: reserved for future longer opcode numbers (e.g. 2 could indicate
that "code" is actually 16 bits)
--Andy
--
Andy Lutomirski
AMA Capital Management, LLC
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 21:23:54
On Wed, Aug 13, 2014 at 2:21 PM, H. Peter Anvin [off-list ref] wrote:
One thing about this that may be a serious concern: allowing the user to
control 8 contiguous bytes of kernel memory may be a security hazard.
I'm confused. What kind of memory? I can control a lot more than 8
bytes of stack very easily.
Or are you concerned about 8 contiguous bytes of *executable* memory?
--Andy
From: "H. Peter Anvin" <hpa@zytor.com> Date: 2014-08-13 21:27:59
On 08/13/2014 02:23 PM, Andy Lutomirski wrote:
On Wed, Aug 13, 2014 at 2:21 PM, H. Peter Anvin [off-list ref] wrote:
quoted
One thing about this that may be a serious concern: allowing the user to
control 8 contiguous bytes of kernel memory may be a security hazard.
I'm confused. What kind of memory? I can control a lot more than 8
bytes of stack very easily.
Or are you concerned about 8 contiguous bytes of *executable* memory?
Yes. Useful for some kinds of ROP custom gadgets.
-hpa
On Wed, Aug 13, 2014 at 2:17 PM, Andy Lutomirski [off-list ref] wrote:
On Wed, Aug 13, 2014 at 2:02 PM, Alexei Starovoitov [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 11:35 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The compiler can still think of it as a single insn, though, but some
future compiler might not.
I think that would be very dangerous.
compiler (user space) and kernel interpreter must have the same
understanding of ISA.
quoted
In any case, I think that, if you use the
same code for high and for low, you need logic in the JIT that's at
least as complicated.
why do you think so? Handling of pseudo BPF_LD_IMM64 is done
in single patch #11 which is one of the smallest...
quoted
For example, what happens if you have two
consecutive 64-bit immediate loads to the same register? Now you have
four consecutive 8-byte insn words that differ only in their immediate
values, and you need to split them correctly.
I don't need to do anything special in this case.
Two 16-byte instructions back to back is not a problem.
Interpreter or JIT don't care whether they move the same or different
immediates into the same or different register. Interpreter and JITs
are dumb on purpose.
when verifier sees two back to back ld_imm64, the 2nd will simply
override the value loaded by first one. It's not any different than
two back to back 'mov dst_reg, imm32' instructions.
But this patch makes the JIT code (and any interpreter) weirdly
stateful. You have:
+ case BPF_LD | BPF_IMM | BPF_DW:
+ /* movabsq %rax, imm64 */
+ EMIT2(add_1mod(0x48, dst_reg), add_1reg(0xB8, dst_reg));
+ EMIT(insn->imm, 4);
+ insn++;
+ i++;
+ EMIT(insn->imm, 4);
+ break;
If you have more than two BPF_LD | BPF_IMM | BPF_DW instructions in a
row, then the way in which they pair up depends on where you start.
For JIT it's not a problem, since it's doing a linear scan. So it always
starts at instruction boundary.
But thinking about it further you're right that it's a bug in verifier.
I've tried it and indeed depending on type of branch verifier doesn't
catch a case of two 16-byte instructions back to back and jump
goes into 2nd half of 1st insn. I need to fix that.
I think it would be a lot clearer if you made these be "load low" and
"load high", with JIT code like:
+ case BPF_LOAD_LOW:
+ /* movabsq %rax, imm64 */
+ if (next insn is BPF_LOAD_HIGH) {
such 'if' will be costly in interpreter. I want to avoid it.
(and you'd have to deal with whether load low by itself is illegal,
zero extends, sign extends, or preserves high bits).
I don't need an instruction that loads low 32-bit. It already exists.
It's called 'mov'.
I'm going to try encoding:
insn[0].code = LD | IMM | DW
insn[1].code = 0
zero is invalid opcode, so it's your 'continuation'.
and it is still single 16-byte instructions without any interpreter overhead.
This has a nice benefit for future-proofing: it gives you 119 bits of
payload for 16-byte instructions.
It's already future proofed. We can add 24 byte instructions and so on
just as well. There is no point to reserve 119 bits when no one is using
them.
On the other hand, a u8 for the opcode is kind of small, and killing
half of that space like this is probably bad. Maybe reserve two high
bits, with:
That's an overkill. We use ~80 opcodes out of 256.
There is plenty of room. I see no reason to switch to 16-bit opcodes
until we get even close to half of u8 space.
It feels that we're starting to bikeshed.
On Wed, Aug 13, 2014 at 2:27 PM, H. Peter Anvin [off-list ref] wrote:
On 08/13/2014 02:23 PM, Andy Lutomirski wrote:
quoted
On Wed, Aug 13, 2014 at 2:21 PM, H. Peter Anvin [off-list ref] wrote:
quoted
One thing about this that may be a serious concern: allowing the user to
control 8 contiguous bytes of kernel memory may be a security hazard.
I'm confused. What kind of memory? I can control a lot more than 8
bytes of stack very easily.
Or are you concerned about 8 contiguous bytes of *executable* memory?
Yes. Useful for some kinds of ROP custom gadgets.
I don't get it. What is ROP ?
What is the concern about 8 bytes ?
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 21:39:13
On Wed, Aug 13, 2014 at 2:37 PM, Alexei Starovoitov [off-list ref] wrote:
I don't need an instruction that loads low 32-bit. It already exists.
It's called 'mov'.
I'm going to try encoding:
insn[0].code = LD | IMM | DW
insn[1].code = 0
zero is invalid opcode, so it's your 'continuation'.
and it is still single 16-byte instructions without any interpreter overhead.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 21:41:58
On Wed, Aug 13, 2014 at 2:27 PM, H. Peter Anvin [off-list ref] wrote:
On 08/13/2014 02:23 PM, Andy Lutomirski wrote:
quoted
On Wed, Aug 13, 2014 at 2:21 PM, H. Peter Anvin [off-list ref] wrote:
quoted
One thing about this that may be a serious concern: allowing the user to
control 8 contiguous bytes of kernel memory may be a security hazard.
I'm confused. What kind of memory? I can control a lot more than 8
bytes of stack very easily.
Or are you concerned about 8 contiguous bytes of *executable* memory?
Yes. Useful for some kinds of ROP custom gadgets.
Hmm.
I think this is moot on non-SMEP machines. And I'm not entirely
convinced that it's worth worrying about in general, especially if we
take some care to randomize the location of the JIT mapping.
But yes, gadgets like jumps relative to gs or something along those
lines could make for interesting ROP tools. But someone will probably
figure out how to turn JIT output into a NOP slide + ROP gadget
regardless, at least on x86.
--Andy
On Wed, Aug 13, 2014 at 2:41 PM, Andy Lutomirski [off-list ref] wrote:
I think this is moot on non-SMEP machines. And I'm not entirely
convinced that it's worth worrying about in general, especially if we
take some care to randomize the location of the JIT mapping.
On Wed, Aug 13, 2014 at 2:38 PM, Alexei Starovoitov [off-list ref] wrote:
On Wed, Aug 13, 2014 at 2:27 PM, H. Peter Anvin [off-list ref] wrote:
quoted
On 08/13/2014 02:23 PM, Andy Lutomirski wrote:
quoted
On Wed, Aug 13, 2014 at 2:21 PM, H. Peter Anvin [off-list ref] wrote:
quoted
One thing about this that may be a serious concern: allowing the user to
control 8 contiguous bytes of kernel memory may be a security hazard.
I'm confused. What kind of memory? I can control a lot more than 8
bytes of stack very easily.
Or are you concerned about 8 contiguous bytes of *executable* memory?
Yes. Useful for some kinds of ROP custom gadgets.
I don't get it. What is ROP ?
What is the concern about 8 bytes ?
looked it up. too many abbreviations now days.
x64 jit spraying was fixed by Eric some time ago, so JIT emitting
movabsq doesn't increase attack surface. various movs of 32-bit
immediates can be used for 'custom gadget' just as well.
Worst case JIT won't be enabled.
In classic BPF we allow junk to be stored in used fields of
'struct sock_filter' and so far that wasn't a problem.
eBPF is more paranoid regarding verification.
From: David Miller <davem@davemloft.net> Date: 2014-08-13 23:25:19
From: David Laight <David.Laight-ZS65k/vG3HxXrIkS9f7CXA@public.gmane.org>
Date: Wed, 13 Aug 2014 08:52:30 +0000
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
There is nothing to stop any JIT software detecting the instruction pair.
The opposite argument is that JITs can expand the IMM64 load into whatever
sequence of instructions is most optimal.
My only real gripe with IMM64 loads is that it's not mainly for
loading an immediate, it's for loading a pointer. And this
distinction is important for some JITs.
For example, on sparc64 all symbol based addresses are actually 32-bit
because of the code model we use to compile the kernel and all modules.
So if we knew this is a pointer load and it's to a symbol in a kernel
or module image, we could do a 32-bit load.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 23:35:22
On Wed, Aug 13, 2014 at 4:25 PM, David Miller [off-list ref] wrote:
From: David Laight <redacted>
Date: Wed, 13 Aug 2014 08:52:30 +0000
quoted
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
There is nothing to stop any JIT software detecting the instruction pair.
The opposite argument is that JITs can expand the IMM64 load into whatever
sequence of instructions is most optimal.
My only real gripe with IMM64 loads is that it's not mainly for
loading an immediate, it's for loading a pointer. And this
distinction is important for some JITs.
For example, on sparc64 all symbol based addresses are actually 32-bit
because of the code model we use to compile the kernel and all modules.
So if we knew this is a pointer load and it's to a symbol in a kernel
or module image, we could do a 32-bit load.
This is true for x86_64 as well, I think.
(Almost. For x86_64 we have a choice between a sign-extended load of
a value in the top 2GB of the address space and lea reg,offset(%rip).)
--Andy
On Wed, Aug 13, 2014 at 4:34 PM, Andy Lutomirski [off-list ref] wrote:
On Wed, Aug 13, 2014 at 4:25 PM, David Miller [off-list ref] wrote:
quoted
From: David Laight <David.Laight-ZS65k/vG3HxXrIkS9f7CXA@public.gmane.org>
Date: Wed, 13 Aug 2014 08:52:30 +0000
quoted
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
There is nothing to stop any JIT software detecting the instruction pair.
The opposite argument is that JITs can expand the IMM64 load into whatever
sequence of instructions is most optimal.
My only real gripe with IMM64 loads is that it's not mainly for
loading an immediate, it's for loading a pointer. And this
distinction is important for some JITs.
For example, on sparc64 all symbol based addresses are actually 32-bit
because of the code model we use to compile the kernel and all modules.
So if we knew this is a pointer load and it's to a symbol in a kernel
or module image, we could do a 32-bit load.
This is true for x86_64 as well, I think.
(Almost. For x86_64 we have a choice between a sign-extended load of
a value in the top 2GB of the address space and lea reg,offset(%rip).)
That would be an interesting optimization. I did movabsq just
because it was straightforward. JITs can play interesting tricks here.
Since it's really a constant value, there is no difference whether
it's a pointer or a constant. If JIT can use $rip trick on x64 or reduce
number of sethi insns on sparc, it should try to do it regardless of
how value in dst_reg will be used later on by the program.
JITs can also allocate some read-only area for constants and
do a relative load from there. Not sure that it will be faster though.
JITs can get more complex and smarter as time goes by. They can
even randomly do some ld_imm64 via movabsq and some via a
sequence of mov, shift, or. That will through away JIT spraying attacks.
If JITed code itself is random, that would be nice defense.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-13 23:53:26
On Wed, Aug 13, 2014 at 4:46 PM, Alexei Starovoitov [off-list ref] wrote:
On Wed, Aug 13, 2014 at 4:34 PM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 4:25 PM, David Miller [off-list ref] wrote:
quoted
From: David Laight <redacted>
Date: Wed, 13 Aug 2014 08:52:30 +0000
quoted
From: Of Alexei Starovoitov
quoted
one more RFC...
Major difference vs previous set is a new 'load 64-bit immediate' eBPF insn.
Which is first 16-byte instruction. It shows how eBPF ISA can be extended
while maintaining backward compatibility, but mainly it cleans up eBPF
program access to maps and improves run-time performance.
Wouldn't it be more sensible to follow the scheme used by a lot of cpus
and add a 'load high' instruction (follow with 'add' or 'or').
It still takes 16 bytes to load a 64bit immediate value, but the instruction
size remains constant.
There is nothing to stop any JIT software detecting the instruction pair.
The opposite argument is that JITs can expand the IMM64 load into whatever
sequence of instructions is most optimal.
My only real gripe with IMM64 loads is that it's not mainly for
loading an immediate, it's for loading a pointer. And this
distinction is important for some JITs.
For example, on sparc64 all symbol based addresses are actually 32-bit
because of the code model we use to compile the kernel and all modules.
So if we knew this is a pointer load and it's to a symbol in a kernel
or module image, we could do a 32-bit load.
This is true for x86_64 as well, I think.
(Almost. For x86_64 we have a choice between a sign-extended load of
a value in the top 2GB of the address space and lea reg,offset(%rip).)
That would be an interesting optimization. I did movabsq just
because it was straightforward. JITs can play interesting tricks here.
Since it's really a constant value, there is no difference whether
it's a pointer or a constant. If JIT can use $rip trick on x64 or reduce
number of sethi insns on sparc, it should try to do it regardless of
how value in dst_reg will be used later on by the program.
JITs can also allocate some read-only area for constants and
do a relative load from there. Not sure that it will be faster though.
JITs can get more complex and smarter as time goes by. They can
even randomly do some ld_imm64 via movabsq and some via a
sequence of mov, shift, or. That will through away JIT spraying attacks.
If JITed code itself is random, that would be nice defense.
You can be even fancier on x86_64: if the JIT code ends up being
allocated withing 2GB of the maps, then you can access kernel code
using absolute addresses and the maps using rip-relative addresses.
Depending on exactly what's going on, though, the best option may be
to use x86's fancy addressing modes for calls and loads. That will be
harder.
--Andy
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
Tracing use case got some improvements as well. Now eBPF programs can be
attached to tracepoint, syscall, kprobe and C examples are more usable:
ex1_kern.c - demonstrate how programs can walk in-kernel data structures
ex2_kern.c - in-kernel event accounting and user space histograms
See patch #25
This is great, thanks! I've been using this new support, and
successfully ported an an older tool of mine (bitesize) to eBPF. I was
using the block:block_rq_issue tracepoint, and performing a custom
in-kernel histogram, like in the ex2_kern.c example, for I/O size.
I also did some quick overhead testing and found eBPF with JIT to be
relatively fast. (I'd share numbers but it's platform specific.) The
syscall tracepoints were a bit slower than hoped, for what I think is
a well known issue.
Are there thoughts in general for how this might be used for embedded
devices, where installing clang/llvm might be prohibitive? Compile on
another system and move the binaries over? thanks,
Brendan
Minor suggestion: since this is sample code, I'd always print the bpf
log after this this printf() error message:
printf("%s", bpf_log_buf);
Which has helped me debug my eBPF programs, as will be the case for
anyone hacking on the examples. Or have a function for logdie(), if
the log buffer may be populated with useful messages from other error
paths as well.
Brendan
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
+/* For tracing filters save first six arguments of tracepoint events.
+ * On 64-bit architectures argN fields will match one to one to arguments passed
+ * to tracepoint events.
+ * On 32-bit architectures u64 arguments to events will be seen into two
+ * consecutive argN, argN+1 fields. Pointers, u32, u16, u8, bool types will
+ * match one to one
+ */
+struct bpf_context {
+ unsigned long arg1;
+ unsigned long arg2;
+ unsigned long arg3;
+ unsigned long arg4;
+ unsigned long arg5;
+ unsigned long arg6;
+ unsigned long ret;
+};
While this works, the argN+1 shift for 32-bit is a gotcha to learn.
Lets say arg1 was 64-bit, and my program only examined arg2. I'd need
two programs, one for 64-bit (using arg2) and 32-bit (arg3). If there
was a way not to shift arguments, I could have one program for both.
Eg, additional arg1hi, arg2hi, ... for the higher order u32s.
Brendan
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
this example has two probes in C that use two different maps.
1st probe is the similar to dropmon.c. It attaches to kfree_skb tracepoint and
count number of packet drops at different locations
2nd probe attaches to kprobe/sys_write and computes a histogram of different
write sizes
Usage:
$ sudo ex2
Should see:
writing bpf-5 -> /sys/kernel/debug/tracing/events/skb/kfree_skb/filter
writing bpf-8 -> /sys/kernel/debug/tracing/events/kprobes/sys_write/filter
location 0xffffffff816efc67 count 1
location 0xffffffff815d8030 count 1
location 0xffffffff816efc67 count 3
location 0xffffffff815d8030 count 4
location 0xffffffff816efc67 count 9
syscall write() stats
byte_size : count distribution
1 -> 1 : 3141 |**** |
2 -> 3 : 2 | |
4 -> 7 : 14 | |
8 -> 15 : 3268 |***** |
16 -> 31 : 732 | |
32 -> 63 : 20042 |************************************* |
64 -> 127 : 12154 |********************** |
128 -> 255 : 2215 |*** |
256 -> 511 : 9 | |
512 -> 1023 : 0 | |
1024 -> 2047 : 1 | |
This is pretty awesome.
Given that this is tracing two tracepoints at once, I'd like to see a
similar example where time is stored on the first tracepoint,
retrieved on the second for a delta calculation, then presented with a
similar histogram as seen above.
Brendan
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
maps can have different types: hash, bloom filter, radix-tree, etc.
The map is defined by:
. type
. max number of elements
. key size in bytes
. value size in bytes
Can values be strings or byte arrays? How would user-level bpf read
them? The two types of uses I'm thinking are:
A. Constructing a custom string in kernel-context, and using that as
the value. Eg, a truncated filename, or a dotted quad IP address, or
the raw contents of a packet.
B. I have a pointer to an existing buffer or string, eg a filename,
that will likely be around for some time (>1s). Instead of the value
storing the string, it could just be a ptr, so long as user-level bpf
has a way to read it.
Also, can keys be strings? I'd ask about multiple keys, but if they
can be a string, I can delimit in the key (eg, "PID:filename").
Thanks,
Brendan
--
http://www.brendangregg.com
Minor suggestion: since this is sample code, I'd always print the bpf
log after this this printf() error message:
printf("%s", bpf_log_buf);
Which has helped me debug my eBPF programs, as will be the case for
anyone hacking on the examples.
Good point. Will do in V5.
Or have a function for logdie(), if
the log buffer may be populated with useful messages from other error
paths as well.
This log buffer is an optional buffer that eBPF verifier is using to
store its messages. Mainly for humans to understand why verifier
rejected the program. It's also used by verifier testsuite to check
that reject reason actually matches the test intent.
On Thu, Aug 14, 2014 at 2:20 PM, Brendan Gregg
[off-list ref] wrote:
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
quoted
+/* For tracing filters save first six arguments of tracepoint events.
+ * On 64-bit architectures argN fields will match one to one to arguments passed
+ * to tracepoint events.
+ * On 32-bit architectures u64 arguments to events will be seen into two
+ * consecutive argN, argN+1 fields. Pointers, u32, u16, u8, bool types will
+ * match one to one
+ */
+struct bpf_context {
+ unsigned long arg1;
+ unsigned long arg2;
+ unsigned long arg3;
+ unsigned long arg4;
+ unsigned long arg5;
+ unsigned long arg6;
+ unsigned long ret;
+};
While this works, the argN+1 shift for 32-bit is a gotcha to learn.
Lets say arg1 was 64-bit, and my program only examined arg2. I'd need
two programs, one for 64-bit (using arg2) and 32-bit (arg3). If there
correct.
I've picked 'long' type for these tracepoint 'arguments' to match
what is going on at assembler level.
32-bit archs are passing 64-bit values in two consecutive registers
or two stack slots. So it's partially exposing architectural details.
I've tried to use u64 here, but it complicated tracepoint+ebpf patch
a lot, since I need per-architecture support for moving C arguments
into u64 variables and hacking tracepoint event definitions in a nasty
ways. This 'long' type approach is the least intrusive I could find.
Also out of 1842 total tracepoint fields, only 144 fields are 64-bit,
so rarely one would need to deal with u64. Most of the tracepoint
arguments are either longs, ints or pointers, which fits this approach
the best.
In general the eBPF design approach is to keep kernel bits as simple
as possible and move complexity to user space.
In this case some higher language than C for writing scripts can
hide this oddity.
On Thu, Aug 14, 2014 at 3:13 PM, Brendan Gregg
[off-list ref] wrote:
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
this example has two probes in C that use two different maps.
1st probe is the similar to dropmon.c. It attaches to kfree_skb tracepoint and
count number of packet drops at different locations
2nd probe attaches to kprobe/sys_write and computes a histogram of different
write sizes
Usage:
$ sudo ex2
Should see:
writing bpf-5 -> /sys/kernel/debug/tracing/events/skb/kfree_skb/filter
writing bpf-8 -> /sys/kernel/debug/tracing/events/kprobes/sys_write/filter
location 0xffffffff816efc67 count 1
location 0xffffffff815d8030 count 1
location 0xffffffff816efc67 count 3
location 0xffffffff815d8030 count 4
location 0xffffffff816efc67 count 9
syscall write() stats
byte_size : count distribution
1 -> 1 : 3141 |**** |
2 -> 3 : 2 | |
4 -> 7 : 14 | |
8 -> 15 : 3268 |***** |
16 -> 31 : 732 | |
32 -> 63 : 20042 |************************************* |
64 -> 127 : 12154 |********************** |
128 -> 255 : 2215 |*** |
256 -> 511 : 9 | |
512 -> 1023 : 0 | |
1024 -> 2047 : 1 | |
This is pretty awesome.
Given that this is tracing two tracepoints at once, I'd like to see a
similar example where time is stored on the first tracepoint,
retrieved on the second for a delta calculation, then presented with a
similar histogram as seen above.
Very good point. The time related helpers are missing. In V5 I'm
thinking to add something like bpf_ktime_get_ns().
To associate begin and end events I think bpf_gettid() would be
needed, but that doesn't feel generic enough for helper function,
so I'm leaning toward 'bpf_get_current()' helper that will return
'current' task pointer. eBPF program can use this pointer for
correlation of events or can go exploring task fields with
bpf_fetch_() helpers...
Thank you very much for trying things out and for your feedback!
On Thu, Aug 14, 2014 at 3:28 PM, Brendan Gregg
[off-list ref] wrote:
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
quoted
maps can have different types: hash, bloom filter, radix-tree, etc.
The map is defined by:
. type
. max number of elements
. key size in bytes
. value size in bytes
Can values be strings or byte arrays? How would user-level bpf read
them? The two types of uses I'm thinking are:
A. Constructing a custom string in kernel-context, and using that as
the value. Eg, a truncated filename, or a dotted quad IP address, or
the raw contents of a packet.
B. I have a pointer to an existing buffer or string, eg a filename,
that will likely be around for some time (>1s). Instead of the value
storing the string, it could just be a ptr, so long as user-level bpf
has a way to read it.
Also, can keys be strings? I'd ask about multiple keys, but if they
can be a string, I can delimit in the key (eg, "PID:filename").
Both map keys and values are opaque byte arrays. eBPF program
can decide to store strings in there. Or concatenate multiple
strings as long as sizes are bounded.
High level scripting languages are dazzling with native strings
support, but I'm trying to stay away from it in the kernel.
Scripting languages should be able to convert string operations
into low level eBPF primitives which are being worked on.
So far I've been able to use ids and pointers and concatenations
of binary things as keys and values, and have user space interpret
them. I agree that having a script that does map[probe_name()]++
is definitely more human readable than storing probe ip into
ebpf map and converting addresses to names in userspace.
I'm hoping that the urge to make cool scripting language will push
somebody to have a dtrace/ktap/stap language compiler into eBPF :)
That will also address your concern of embedded setup where
full llvm is too big, but dtrace_into_ebpf compiler may be just right.
At the same time people who care about last bit of performance
will be using C and llvm or ebpf assembler directly.
Anyway will share string related ebpf helpers soon (not in V5 though)
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 17:20:50
On Thu, Aug 14, 2014 at 11:08 PM, Alexei Starovoitov [off-list ref] wrote:
On Thu, Aug 14, 2014 at 2:20 PM, Brendan Gregg
[off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
[...]
quoted
+/* For tracing filters save first six arguments of tracepoint events.
+ * On 64-bit architectures argN fields will match one to one to arguments passed
+ * to tracepoint events.
+ * On 32-bit architectures u64 arguments to events will be seen into two
+ * consecutive argN, argN+1 fields. Pointers, u32, u16, u8, bool types will
+ * match one to one
+ */
+struct bpf_context {
+ unsigned long arg1;
+ unsigned long arg2;
+ unsigned long arg3;
+ unsigned long arg4;
+ unsigned long arg5;
+ unsigned long arg6;
+ unsigned long ret;
+};
While this works, the argN+1 shift for 32-bit is a gotcha to learn.
Lets say arg1 was 64-bit, and my program only examined arg2. I'd need
two programs, one for 64-bit (using arg2) and 32-bit (arg3). If there
correct.
I've picked 'long' type for these tracepoint 'arguments' to match
what is going on at assembler level.
32-bit archs are passing 64-bit values in two consecutive registers
or two stack slots. So it's partially exposing architectural details.
I've tried to use u64 here, but it complicated tracepoint+ebpf patch
a lot, since I need per-architecture support for moving C arguments
into u64 variables and hacking tracepoint event definitions in a nasty
ways. This 'long' type approach is the least intrusive I could find.
Also out of 1842 total tracepoint fields, only 144 fields are 64-bit,
so rarely one would need to deal with u64. Most of the tracepoint
arguments are either longs, ints or pointers, which fits this approach
the best.
In general the eBPF design approach is to keep kernel bits as simple
as possible and move complexity to user space.
In this case some higher language than C for writing scripts can
hide this oddity.
The downside of this approach is that compat support might be
difficult or impossible.
--Andy
--
Andy Lutomirski
AMA Capital Management, LLC
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 17:26:10
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
I didn't follow all the code flow leading to parsing the "bpf_123"
string, but if it works the way I imagine it does, it's a security
problem. In general, write(2) should never do anything that involves
any security-relevant context of the caller.
Ideally, you would look up fd 123 in the file table of whomever called
open. If that's difficult to implement efficiently, then it would be
nice to have some check that the callers of write(2) and open(2) are
the same task and that exec wasn't called in between.
This isn't a very severe security issue because you need privilege to
open the thing in the first place, but it would still be nice to
address.
--Andy
On Fri, Aug 15, 2014 at 10:20 AM, Andy Lutomirski [off-list ref] wrote:
The downside of this approach is that compat support might be
difficult or impossible.
Would do you mean by compat? 32-bit programs on 64-bit kernels?
There is no such concept for eBPF. All eBPF programs are always
operating on 64-bit registers.
On Fri, Aug 15, 2014 at 10:25 AM, Andy Lutomirski [off-list ref] wrote:
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
I didn't follow all the code flow leading to parsing the "bpf_123"
string, but if it works the way I imagine it does, it's a security
problem. In general, write(2) should never do anything that involves
any security-relevant context of the caller.
Ideally, you would look up fd 123 in the file table of whomever called
open. If that's difficult to implement efficiently, then it would be
nice to have some check that the callers of write(2) and open(2) are
the same task and that exec wasn't called in between.
This isn't a very severe security issue because you need privilege to
open the thing in the first place, but it would still be nice to
address.
hmm. you need to be root to open the events anyway.
pretty much the whole tracing for root only, since any kernel data
structures can be printed, stored into maps and so on.
So I don't quite follow your security concern here.
Even say root opens a tracepoint and does exec() of another
app that uploads ebpf program, gets program_fd and does
write into tracepoint fd. The root app that did this open() is
doing exec() on purpose. It's not like it's exec-ing something
it doesn't know about.
Remember, FDs was your idea in the first place ;)
I had global ids and everything root initially.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 18:51:13
On Aug 15, 2014 10:36 AM, "Alexei Starovoitov" [off-list ref] wrote:
On Fri, Aug 15, 2014 at 10:20 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The downside of this approach is that compat support might be
difficult or impossible.
Would do you mean by compat? 32-bit programs on 64-bit kernels?
There is no such concept for eBPF. All eBPF programs are always
operating on 64-bit registers.
Doesn't the eBPF program need to know sizeof(long) to read these
fields correctly? Or am I misunderstanding what the code does?
--Andy
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 18:53:41
On Fri, Aug 15, 2014 at 10:51 AM, Alexei Starovoitov [off-list ref] wrote:
On Fri, Aug 15, 2014 at 10:25 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
I didn't follow all the code flow leading to parsing the "bpf_123"
string, but if it works the way I imagine it does, it's a security
problem. In general, write(2) should never do anything that involves
any security-relevant context of the caller.
Ideally, you would look up fd 123 in the file table of whomever called
open. If that's difficult to implement efficiently, then it would be
nice to have some check that the callers of write(2) and open(2) are
the same task and that exec wasn't called in between.
This isn't a very severe security issue because you need privilege to
open the thing in the first place, but it would still be nice to
address.
hmm. you need to be root to open the events anyway.
pretty much the whole tracing for root only, since any kernel data
structures can be printed, stored into maps and so on.
So I don't quite follow your security concern here.
Even say root opens a tracepoint and does exec() of another
app that uploads ebpf program, gets program_fd and does
write into tracepoint fd. The root app that did this open() is
doing exec() on purpose. It's not like it's exec-ing something
it doesn't know about.
As long as everyone who can debugfs/tracing/whatever has all
privileges, then this is fine.
If not, then it's a minor capability or MAC bypass. Suppose you only
have one capability or, more realistically, limited MAC permissions.
You can still open the tracing file, pass it to an unwitting program
with elevated permission (e.g. using selinux's entrypoint mechanism),
and trick that program into writing bpf_123.
Admittedly, it's unlikely that fd 123 will be an *eBPF* fd, but the
attack is possible.
I don't think that fixing this should be a prerequisite for merging,
since the risk is so small. Nonetheless, it would be nice. (This
family of attacks has lead to several root vulnerabilities in the
past.)
--Andy
Remember, FDs was your idea in the first place ;)
I had global ids and everything root initially.
On Fri, Aug 15, 2014 at 11:50 AM, Andy Lutomirski [off-list ref] wrote:
On Aug 15, 2014 10:36 AM, "Alexei Starovoitov" [off-list ref] wrote:
quoted
On Fri, Aug 15, 2014 at 10:20 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The downside of this approach is that compat support might be
difficult or impossible.
Would do you mean by compat? 32-bit programs on 64-bit kernels?
There is no such concept for eBPF. All eBPF programs are always
operating on 64-bit registers.
Doesn't the eBPF program need to know sizeof(long) to read these
fields correctly? Or am I misunderstanding what the code does?
correct. eBPF program would be using 8-byte read on 64-bit kernel
and 4-byte read on 32-bit kernel. Same with access to ptrace fields
and pretty much all other fields in the kernel. The program will be
different on different kernels.
Say, this bpf_context struct doesn't exist at all. The programs would
still need to be different to walk in-kernel data structures...
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 19:03:15
On Fri, Aug 15, 2014 at 11:56 AM, Alexei Starovoitov [off-list ref] wrote:
On Fri, Aug 15, 2014 at 11:50 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Aug 15, 2014 10:36 AM, "Alexei Starovoitov" [off-list ref] wrote:
quoted
On Fri, Aug 15, 2014 at 10:20 AM, Andy Lutomirski [off-list ref] wrote:
quoted
The downside of this approach is that compat support might be
difficult or impossible.
Would do you mean by compat? 32-bit programs on 64-bit kernels?
There is no such concept for eBPF. All eBPF programs are always
operating on 64-bit registers.
Doesn't the eBPF program need to know sizeof(long) to read these
fields correctly? Or am I misunderstanding what the code does?
correct. eBPF program would be using 8-byte read on 64-bit kernel
and 4-byte read on 32-bit kernel. Same with access to ptrace fields
and pretty much all other fields in the kernel. The program will be
different on different kernels.
Say, this bpf_context struct doesn't exist at all. The programs would
still need to be different to walk in-kernel data structures...
Hmm. I guess this isn't so bad.
What's the actual difficulty with using u64? ISTM that, if the clang
front-end can't deal with u64, there's a bigger problem. Or is it
something else I don't understand.
--Andy
On Fri, Aug 15, 2014 at 11:53 AM, Andy Lutomirski [off-list ref] wrote:
On Fri, Aug 15, 2014 at 10:51 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
On Fri, Aug 15, 2014 at 10:25 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
I didn't follow all the code flow leading to parsing the "bpf_123"
string, but if it works the way I imagine it does, it's a security
problem. In general, write(2) should never do anything that involves
any security-relevant context of the caller.
Ideally, you would look up fd 123 in the file table of whomever called
open. If that's difficult to implement efficiently, then it would be
nice to have some check that the callers of write(2) and open(2) are
the same task and that exec wasn't called in between.
This isn't a very severe security issue because you need privilege to
open the thing in the first place, but it would still be nice to
address.
hmm. you need to be root to open the events anyway.
pretty much the whole tracing for root only, since any kernel data
structures can be printed, stored into maps and so on.
So I don't quite follow your security concern here.
Even say root opens a tracepoint and does exec() of another
app that uploads ebpf program, gets program_fd and does
write into tracepoint fd. The root app that did this open() is
doing exec() on purpose. It's not like it's exec-ing something
it doesn't know about.
As long as everyone who can debugfs/tracing/whatever has all
privileges, then this is fine.
If not, then it's a minor capability or MAC bypass. Suppose you only
have one capability or, more realistically, limited MAC permissions.
Hard to think of MAC abbreviation other than in networking way... ;)
MAC bypass... kinda sounds like L3 networking without L2... ;)
You can still open the tracing file, pass it to an unwitting program
with elevated permission (e.g. using selinux's entrypoint mechanism),
and trick that program into writing bpf_123.
hmm, but to open tracing file you'd need to be root already...
otherwise yeah, if non-root could open it and pass it, then it
would be nasty.
Admittedly, it's unlikely that fd 123 will be an *eBPF* fd, but the
attack is possible.
I don't think that fixing this should be a prerequisite for merging,
since the risk is so small. Nonetheless, it would be nice. (This
family of attacks has lead to several root vulnerabilities in the
past.)
Ok. I think keeping a track of pid between open and write is kinda
ugly. Should we add some new CAP flag and check it for all file
ops? Another option is to conditionally make open() of tracing
files as cloexec...
On Fri, Aug 15, 2014 at 12:02 PM, Andy Lutomirski [off-list ref] wrote:
quoted
correct. eBPF program would be using 8-byte read on 64-bit kernel
and 4-byte read on 32-bit kernel. Same with access to ptrace fields
and pretty much all other fields in the kernel. The program will be
different on different kernels.
Say, this bpf_context struct doesn't exist at all. The programs would
still need to be different to walk in-kernel data structures...
Hmm. I guess this isn't so bad.
What's the actual difficulty with using u64? ISTM that, if the clang
front-end can't deal with u64, there's a bigger problem. Or is it
something else I don't understand.
clang/llvm has no problem with u64 :)
This bpf_context struct for tracing is trying to answer the question:
'what's the most convenient way to access tracepoint arguments
from a script'.
When kernel code has something like:
trace_kfree_skb(skb, net_tx_action);
the script needs to be able to access this 'skb' and 'net_tx_action'
values through _single_ data structure.
In this proposal they are ctx->arg1 and ctx->arg2.
I've considered having different bpf_context's for every event, but
the complexity explodes. I need to hack all event definitions and so on.
imo it's better to move complexity to userspace, so program author
or high level language abstracts these details.
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 19:19:17
On Fri, Aug 15, 2014 at 12:16 PM, Alexei Starovoitov [off-list ref] wrote:
On Fri, Aug 15, 2014 at 12:02 PM, Andy Lutomirski [off-list ref] wrote:
quoted
quoted
correct. eBPF program would be using 8-byte read on 64-bit kernel
and 4-byte read on 32-bit kernel. Same with access to ptrace fields
and pretty much all other fields in the kernel. The program will be
different on different kernels.
Say, this bpf_context struct doesn't exist at all. The programs would
still need to be different to walk in-kernel data structures...
Hmm. I guess this isn't so bad.
What's the actual difficulty with using u64? ISTM that, if the clang
front-end can't deal with u64, there's a bigger problem. Or is it
something else I don't understand.
clang/llvm has no problem with u64 :)
This bpf_context struct for tracing is trying to answer the question:
'what's the most convenient way to access tracepoint arguments
from a script'.
When kernel code has something like:
trace_kfree_skb(skb, net_tx_action);
the script needs to be able to access this 'skb' and 'net_tx_action'
values through _single_ data structure.
In this proposal they are ctx->arg1 and ctx->arg2.
I've considered having different bpf_context's for every event, but
the complexity explodes. I need to hack all event definitions and so on.
imo it's better to move complexity to userspace, so program author
or high level language abstracts these details.
I still don't understand why making them long instead of u64 is
helpful, though. I feel like I'm missing obvious here.
--
Andy Lutomirski
AMA Capital Management, LLC
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-08-15 19:20:28
On Fri, Aug 15, 2014 at 12:07 PM, Alexei Starovoitov [off-list ref] wrote:
On Fri, Aug 15, 2014 at 11:53 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Fri, Aug 15, 2014 at 10:51 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
On Fri, Aug 15, 2014 at 10:25 AM, Andy Lutomirski [off-list ref] wrote:
quoted
On Wed, Aug 13, 2014 at 12:57 AM, Alexei Starovoitov [off-list ref] wrote:
quoted
User interface:
fd = open("/sys/kernel/debug/tracing/__event__/filter")
write(fd, "bpf_123")
I didn't follow all the code flow leading to parsing the "bpf_123"
string, but if it works the way I imagine it does, it's a security
problem. In general, write(2) should never do anything that involves
any security-relevant context of the caller.
Ideally, you would look up fd 123 in the file table of whomever called
open. If that's difficult to implement efficiently, then it would be
nice to have some check that the callers of write(2) and open(2) are
the same task and that exec wasn't called in between.
This isn't a very severe security issue because you need privilege to
open the thing in the first place, but it would still be nice to
address.
hmm. you need to be root to open the events anyway.
pretty much the whole tracing for root only, since any kernel data
structures can be printed, stored into maps and so on.
So I don't quite follow your security concern here.
Even say root opens a tracepoint and does exec() of another
app that uploads ebpf program, gets program_fd and does
write into tracepoint fd. The root app that did this open() is
doing exec() on purpose. It's not like it's exec-ing something
it doesn't know about.
As long as everyone who can debugfs/tracing/whatever has all
privileges, then this is fine.
If not, then it's a minor capability or MAC bypass. Suppose you only
have one capability or, more realistically, limited MAC permissions.
Hard to think of MAC abbreviation other than in networking way... ;)
MAC bypass... kinda sounds like L3 networking without L2... ;)
quoted
You can still open the tracing file, pass it to an unwitting program
with elevated permission (e.g. using selinux's entrypoint mechanism),
and trick that program into writing bpf_123.
hmm, but to open tracing file you'd need to be root already...
otherwise yeah, if non-root could open it and pass it, then it
would be nasty.
quoted
Admittedly, it's unlikely that fd 123 will be an *eBPF* fd, but the
attack is possible.
I don't think that fixing this should be a prerequisite for merging,
since the risk is so small. Nonetheless, it would be nice. (This
family of attacks has lead to several root vulnerabilities in the
past.)
Ok. I think keeping a track of pid between open and write is kinda
ugly.
Agreed.
TBH, I would just add a comment to the open implementation saying
that, if unprivileged or less privileged open is allowed, then this
needs to be fixed.
Should we add some new CAP flag and check it for all file
ops? Another option is to conditionally make open() of tracing
files as cloexec...
That won't help. The same attack can be done with SCM_RIGHTS, and
cloexec can be cleared.
--
Andy Lutomirski
AMA Capital Management, LLC
On Fri, Aug 15, 2014 at 12:20 PM, Andy Lutomirski [off-list ref] wrote:
quoted
quoted
I don't think that fixing this should be a prerequisite for merging,
since the risk is so small. Nonetheless, it would be nice. (This
family of attacks has lead to several root vulnerabilities in the
past.)
Ok. I think keeping a track of pid between open and write is kinda
ugly.
Agreed.
TBH, I would just add a comment to the open implementation saying
that, if unprivileged or less privileged open is allowed, then this
needs to be fixed.
ok. will do.
quoted
Should we add some new CAP flag and check it for all file
ops? Another option is to conditionally make open() of tracing
files as cloexec...
That won't help. The same attack can be done with SCM_RIGHTS, and
cloexec can be cleared.
ouch, can we then make ebpf FDs and may be debugfs FDs
not passable at all? Otherwise it feels that generality and
flexibility of FDs is becoming a burden.