2017-11-18 19:09:20 +03:00
/* SPDX-License-Identifier: LGPL-2.1+ */
2016-10-18 18:57:10 +03:00
# include <fcntl.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
# include "alloc-util.h"
# include "bpf-program.h"
# include "fd-util.h"
# include "log.h"
# include "missing.h"
2018-02-20 21:28:24 +03:00
# include "path-util.h"
2018-02-20 21:19:57 +03:00
# include "util.h"
2016-10-18 18:57:10 +03:00
int bpf_program_new ( uint32_t prog_type , BPFProgram * * ret ) {
_cleanup_ ( bpf_program_unrefp ) BPFProgram * p = NULL ;
p = new0 ( BPFProgram , 1 ) ;
if ( ! p )
return log_oom ( ) ;
2018-02-20 21:28:24 +03:00
p - > n_ref = 1 ;
2016-10-18 18:57:10 +03:00
p - > prog_type = prog_type ;
p - > kernel_fd = - 1 ;
2018-04-05 08:26:26 +03:00
* ret = TAKE_PTR ( p ) ;
2016-10-18 18:57:10 +03:00
return 0 ;
}
2018-08-27 08:01:46 +03:00
static BPFProgram * bpf_program_free ( BPFProgram * p ) {
assert ( p ) ;
2018-02-20 21:28:24 +03:00
/* Unfortunately, the kernel currently doesn't implicitly detach BPF programs from their cgroups when the last
* fd to the BPF program is closed . This has nasty side - effects since this means that abnormally terminated
* programs that attached one of their BPF programs to a cgroup will leave this programs pinned for good with
* zero chance of recovery , until the cgroup is removed . This is particularly problematic if the cgroup in
* question is the root cgroup ( or any other cgroup belonging to a service that cannot be restarted during
* operation , such as dbus ) , as the memory for the BPF program can only be reclaimed through a reboot . To
* counter this , we track closely to which cgroup a program was attached to and will detach it on our own
* whenever we close the BPF fd . */
( void ) bpf_program_cgroup_detach ( p ) ;
2016-10-18 18:57:10 +03:00
safe_close ( p - > kernel_fd ) ;
free ( p - > instructions ) ;
2018-02-20 21:28:24 +03:00
free ( p - > attached_path ) ;
2016-10-18 18:57:10 +03:00
return mfree ( p ) ;
}
2018-08-27 08:01:46 +03:00
DEFINE_TRIVIAL_REF_UNREF_FUNC ( BPFProgram , bpf_program , bpf_program_free ) ;
2016-10-18 18:57:10 +03:00
int bpf_program_add_instructions ( BPFProgram * p , const struct bpf_insn * instructions , size_t count ) {
assert ( p ) ;
2018-02-20 21:19:57 +03:00
if ( p - > kernel_fd > = 0 ) /* don't allow modification after we uploaded things to the kernel */
return - EBUSY ;
2016-10-18 18:57:10 +03:00
if ( ! GREEDY_REALLOC ( p - > instructions , p - > allocated , p - > n_instructions + count ) )
return - ENOMEM ;
memcpy ( p - > instructions + p - > n_instructions , instructions , sizeof ( struct bpf_insn ) * count ) ;
p - > n_instructions + = count ;
return 0 ;
}
int bpf_program_load_kernel ( BPFProgram * p , char * log_buf , size_t log_size ) {
union bpf_attr attr ;
assert ( p ) ;
2018-02-20 21:19:57 +03:00
if ( p - > kernel_fd > = 0 ) { /* make this idempotent */
memzero ( log_buf , log_size ) ;
return 0 ;
}
2016-10-18 18:57:10 +03:00
attr = ( union bpf_attr ) {
. prog_type = p - > prog_type ,
. insns = PTR_TO_UINT64 ( p - > instructions ) ,
. insn_cnt = p - > n_instructions ,
. license = PTR_TO_UINT64 ( " GPL " ) ,
. log_buf = PTR_TO_UINT64 ( log_buf ) ,
. log_level = ! ! log_buf ,
. log_size = log_size ,
} ;
p - > kernel_fd = bpf ( BPF_PROG_LOAD , & attr , sizeof ( attr ) ) ;
if ( p - > kernel_fd < 0 )
return - errno ;
return 0 ;
}
2017-09-21 21:38:07 +03:00
int bpf_program_cgroup_attach ( BPFProgram * p , int type , const char * path , uint32_t flags ) {
2018-02-20 21:28:24 +03:00
_cleanup_free_ char * copy = NULL ;
2016-10-18 18:57:10 +03:00
_cleanup_close_ int fd = - 1 ;
union bpf_attr attr ;
2018-02-20 21:28:24 +03:00
int r ;
2016-10-18 18:57:10 +03:00
assert ( p ) ;
assert ( type > = 0 ) ;
assert ( path ) ;
2018-02-20 21:28:24 +03:00
if ( ! IN_SET ( flags , 0 , BPF_F_ALLOW_OVERRIDE , BPF_F_ALLOW_MULTI ) )
return - EINVAL ;
/* We need to track which cgroup the program is attached to, and we can only track one attachment, hence let's
* refuse this early . */
if ( p - > attached_path ) {
if ( ! path_equal ( p - > attached_path , path ) )
return - EBUSY ;
if ( p - > attached_type ! = type )
return - EBUSY ;
if ( p - > attached_flags ! = flags )
return - EBUSY ;
/* Here's a shortcut: if we previously attached this program already, then we don't have to do so
* again . Well , with one exception : if we are in BPF_F_ALLOW_OVERRIDE mode then someone else might have
* replaced our program since the last time , hence let ' s reattach it again , just to be safe . In flags
* = = 0 mode this is not an issue since nobody else can replace our program in that case , and in flags
* = = BPF_F_ALLOW_MULTI mode any other ' s program would be installed in addition to ours hence ours
* would remain in effect . */
if ( flags ! = BPF_F_ALLOW_OVERRIDE )
return 0 ;
}
/* Ensure we have a kernel object for this. */
r = bpf_program_load_kernel ( p , NULL , 0 ) ;
if ( r < 0 )
return r ;
copy = strdup ( path ) ;
if ( ! copy )
return - ENOMEM ;
2016-10-18 18:57:10 +03:00
fd = open ( path , O_DIRECTORY | O_RDONLY | O_CLOEXEC ) ;
if ( fd < 0 )
return - errno ;
attr = ( union bpf_attr ) {
. attach_type = type ,
. target_fd = fd ,
. attach_bpf_fd = p - > kernel_fd ,
2017-09-21 21:38:07 +03:00
. attach_flags = flags ,
2016-10-18 18:57:10 +03:00
} ;
if ( bpf ( BPF_PROG_ATTACH , & attr , sizeof ( attr ) ) < 0 )
return - errno ;
2018-02-20 21:28:24 +03:00
free_and_replace ( p - > attached_path , copy ) ;
p - > attached_type = type ;
p - > attached_flags = flags ;
2016-10-18 18:57:10 +03:00
return 0 ;
}
2018-02-20 21:28:24 +03:00
int bpf_program_cgroup_detach ( BPFProgram * p ) {
2016-10-18 18:57:10 +03:00
_cleanup_close_ int fd = - 1 ;
2018-02-20 21:28:24 +03:00
assert ( p ) ;
2016-10-18 18:57:10 +03:00
2018-02-20 21:28:24 +03:00
if ( ! p - > attached_path )
return - EUNATCH ;
2018-02-16 16:58:12 +03:00
2018-02-20 21:28:24 +03:00
fd = open ( p - > attached_path , O_DIRECTORY | O_RDONLY | O_CLOEXEC ) ;
if ( fd < 0 ) {
if ( errno ! = ENOENT )
return - errno ;
2016-10-18 18:57:10 +03:00
2018-02-20 21:28:24 +03:00
/* If the cgroup does not exist anymore, then we don't have to explicitly detach, it got detached
* implicitly by the removal , hence don ' t complain */
2016-10-18 18:57:10 +03:00
2018-02-20 21:28:24 +03:00
} else {
union bpf_attr attr ;
attr = ( union bpf_attr ) {
. attach_type = p - > attached_type ,
. target_fd = fd ,
. attach_bpf_fd = p - > kernel_fd ,
} ;
if ( bpf ( BPF_PROG_DETACH , & attr , sizeof ( attr ) ) < 0 )
return - errno ;
}
p - > attached_path = mfree ( p - > attached_path ) ;
2016-10-18 18:57:10 +03:00
return 0 ;
}
int bpf_map_new ( enum bpf_map_type type , size_t key_size , size_t value_size , size_t max_entries , uint32_t flags ) {
union bpf_attr attr = {
. map_type = type ,
. key_size = key_size ,
. value_size = value_size ,
. max_entries = max_entries ,
. map_flags = flags ,
} ;
int fd ;
fd = bpf ( BPF_MAP_CREATE , & attr , sizeof ( attr ) ) ;
if ( fd < 0 )
return - errno ;
return fd ;
}
int bpf_map_update_element ( int fd , const void * key , void * value ) {
union bpf_attr attr = {
. map_fd = fd ,
. key = PTR_TO_UINT64 ( key ) ,
. value = PTR_TO_UINT64 ( value ) ,
} ;
if ( bpf ( BPF_MAP_UPDATE_ELEM , & attr , sizeof ( attr ) ) < 0 )
return - errno ;
return 0 ;
}
int bpf_map_lookup_element ( int fd , const void * key , void * value ) {
union bpf_attr attr = {
. map_fd = fd ,
. key = PTR_TO_UINT64 ( key ) ,
. value = PTR_TO_UINT64 ( value ) ,
} ;
if ( bpf ( BPF_MAP_LOOKUP_ELEM , & attr , sizeof ( attr ) ) < 0 )
return - errno ;
return 0 ;
}