1342 lines · 30.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* Copyright (c) 2017 - 2018 Covalent IO, Inc. http://covalent.io */ #include <linux/bpf.h>#include <linux/filter.h>#include <linux/errno.h>#include <linux/file.h>#include <linux/net.h>#include <linux/workqueue.h>#include <linux/skmsg.h>#include <linux/list.h>#include <linux/jhash.h>#include <linux/sock_diag.h>#include <net/udp.h> struct bpf_stab {	struct bpf_map map;	struct sock **sks;	struct sk_psock_progs progs;	raw_spinlock_t lock;}; #define SOCK_CREATE_FLAG_MASK				\	(BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) static struct bpf_map *sock_map_alloc(union bpf_attr *attr){	struct bpf_stab *stab;	u64 cost;	int err; 	if (!capable(CAP_NET_ADMIN))		return ERR_PTR(-EPERM);	if (attr->max_entries == 0 ||	    attr->key_size    != 4 ||	    (attr->value_size != sizeof(u32) &&	     attr->value_size != sizeof(u64)) ||	    attr->map_flags & ~SOCK_CREATE_FLAG_MASK)		return ERR_PTR(-EINVAL); 	stab = kzalloc(sizeof(*stab), GFP_USER);	if (!stab)		return ERR_PTR(-ENOMEM); 	bpf_map_init_from_attr(&stab->map, attr);	raw_spin_lock_init(&stab->lock); 	/* Make sure page count doesn't overflow. */	cost = (u64) stab->map.max_entries * sizeof(struct sock *);	err = bpf_map_charge_init(&stab->map.memory, cost);	if (err)		goto free_stab; 	stab->sks = bpf_map_area_alloc(stab->map.max_entries *				       sizeof(struct sock *),				       stab->map.numa_node);	if (stab->sks)		return &stab->map;	err = -ENOMEM;	bpf_map_charge_finish(&stab->map.memory);free_stab:	kfree(stab);	return ERR_PTR(err);} int sock_map_get_from_fd(const union bpf_attr *attr, struct bpf_prog *prog){	u32 ufd = attr->target_fd;	struct bpf_map *map;	struct fd f;	int ret; 	if (attr->attach_flags || attr->replace_bpf_fd)		return -EINVAL; 	f = fdget(ufd);	map = __bpf_map_get(f);	if (IS_ERR(map))		return PTR_ERR(map);	ret = sock_map_prog_update(map, prog, NULL, attr->attach_type);	fdput(f);	return ret;} int sock_map_prog_detach(const union bpf_attr *attr, enum bpf_prog_type ptype){	u32 ufd = attr->target_fd;	struct bpf_prog *prog;	struct bpf_map *map;	struct fd f;	int ret; 	if (attr->attach_flags || attr->replace_bpf_fd)		return -EINVAL; 	f = fdget(ufd);	map = __bpf_map_get(f);	if (IS_ERR(map))		return PTR_ERR(map); 	prog = bpf_prog_get(attr->attach_bpf_fd);	if (IS_ERR(prog)) {		ret = PTR_ERR(prog);		goto put_map;	} 	if (prog->type != ptype) {		ret = -EINVAL;		goto put_prog;	} 	ret = sock_map_prog_update(map, NULL, prog, attr->attach_type);put_prog:	bpf_prog_put(prog);put_map:	fdput(f);	return ret;} static void sock_map_sk_acquire(struct sock *sk)	__acquires(&sk->sk_lock.slock){	lock_sock(sk);	preempt_disable();	rcu_read_lock();} static void sock_map_sk_release(struct sock *sk)	__releases(&sk->sk_lock.slock){	rcu_read_unlock();	preempt_enable();	release_sock(sk);} static void sock_map_add_link(struct sk_psock *psock,			      struct sk_psock_link *link,			      struct bpf_map *map, void *link_raw){	link->link_raw = link_raw;	link->map = map;	spin_lock_bh(&psock->link_lock);	list_add_tail(&link->list, &psock->link);	spin_unlock_bh(&psock->link_lock);} static void sock_map_del_link(struct sock *sk,			      struct sk_psock *psock, void *link_raw){	struct sk_psock_link *link, *tmp;	bool strp_stop = false; 	spin_lock_bh(&psock->link_lock);	list_for_each_entry_safe(link, tmp, &psock->link, list) {		if (link->link_raw == link_raw) {			struct bpf_map *map = link->map;			struct bpf_stab *stab = container_of(map, struct bpf_stab,							     map);			if (psock->parser.enabled && stab->progs.skb_parser)				strp_stop = true;			list_del(&link->list);			sk_psock_free_link(link);		}	}	spin_unlock_bh(&psock->link_lock);	if (strp_stop) {		write_lock_bh(&sk->sk_callback_lock);		sk_psock_stop_strp(sk, psock);		write_unlock_bh(&sk->sk_callback_lock);	}} static void sock_map_unref(struct sock *sk, void *link_raw){	struct sk_psock *psock = sk_psock(sk); 	if (likely(psock)) {		sock_map_del_link(sk, psock, link_raw);		sk_psock_put(sk, psock);	}} static int sock_map_init_proto(struct sock *sk, struct sk_psock *psock){	struct proto *prot; 	sock_owned_by_me(sk); 	switch (sk->sk_type) {	case SOCK_STREAM:		prot = tcp_bpf_get_proto(sk, psock);		break; 	case SOCK_DGRAM:		prot = udp_bpf_get_proto(sk, psock);		break; 	default:		return -EINVAL;	} 	if (IS_ERR(prot))		return PTR_ERR(prot); 	sk_psock_update_proto(sk, psock, prot);	return 0;} static struct sk_psock *sock_map_psock_get_checked(struct sock *sk){	struct sk_psock *psock; 	rcu_read_lock();	psock = sk_psock(sk);	if (psock) {		if (sk->sk_prot->close != sock_map_close) {			psock = ERR_PTR(-EBUSY);			goto out;		} 		if (!refcount_inc_not_zero(&psock->refcnt))			psock = ERR_PTR(-EBUSY);	}out:	rcu_read_unlock();	return psock;} static int sock_map_link(struct bpf_map *map, struct sk_psock_progs *progs,			 struct sock *sk){	struct bpf_prog *msg_parser, *skb_parser, *skb_verdict;	struct sk_psock *psock;	bool skb_progs;	int ret; 	skb_verdict = READ_ONCE(progs->skb_verdict);	skb_parser = READ_ONCE(progs->skb_parser);	skb_progs = skb_parser && skb_verdict;	if (skb_progs) {		skb_verdict = bpf_prog_inc_not_zero(skb_verdict);		if (IS_ERR(skb_verdict))			return PTR_ERR(skb_verdict);		skb_parser = bpf_prog_inc_not_zero(skb_parser);		if (IS_ERR(skb_parser)) {			bpf_prog_put(skb_verdict);			return PTR_ERR(skb_parser);		}	} 	msg_parser = READ_ONCE(progs->msg_parser);	if (msg_parser) {		msg_parser = bpf_prog_inc_not_zero(msg_parser);		if (IS_ERR(msg_parser)) {			ret = PTR_ERR(msg_parser);			goto out;		}	} 	psock = sock_map_psock_get_checked(sk);	if (IS_ERR(psock)) {		ret = PTR_ERR(psock);		goto out_progs;	} 	if (psock) {		if ((msg_parser && READ_ONCE(psock->progs.msg_parser)) ||		    (skb_progs  && READ_ONCE(psock->progs.skb_parser))) {			sk_psock_put(sk, psock);			ret = -EBUSY;			goto out_progs;		}	} else {		psock = sk_psock_init(sk, map->numa_node);		if (!psock) {			ret = -ENOMEM;			goto out_progs;		}	} 	if (msg_parser)		psock_set_prog(&psock->progs.msg_parser, msg_parser); 	ret = sock_map_init_proto(sk, psock);	if (ret < 0)		goto out_drop; 	write_lock_bh(&sk->sk_callback_lock);	if (skb_progs && !psock->parser.enabled) {		ret = sk_psock_init_strp(sk, psock);		if (ret) {			write_unlock_bh(&sk->sk_callback_lock);			goto out_drop;		}		psock_set_prog(&psock->progs.skb_verdict, skb_verdict);		psock_set_prog(&psock->progs.skb_parser, skb_parser);		sk_psock_start_strp(sk, psock);	}	write_unlock_bh(&sk->sk_callback_lock);	return 0;out_drop:	sk_psock_put(sk, psock);out_progs:	if (msg_parser)		bpf_prog_put(msg_parser);out:	if (skb_progs) {		bpf_prog_put(skb_verdict);		bpf_prog_put(skb_parser);	}	return ret;} static int sock_map_link_no_progs(struct bpf_map *map, struct sock *sk){	struct sk_psock *psock;	int ret; 	psock = sock_map_psock_get_checked(sk);	if (IS_ERR(psock))		return PTR_ERR(psock); 	if (!psock) {		psock = sk_psock_init(sk, map->numa_node);		if (!psock)			return -ENOMEM;	} 	ret = sock_map_init_proto(sk, psock);	if (ret < 0)		sk_psock_put(sk, psock);	return ret;} static void sock_map_free(struct bpf_map *map){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map);	int i; 	/* After the sync no updates or deletes will be in-flight so it	 * is safe to walk map and remove entries without risking a race	 * in EEXIST update case.	 */	synchronize_rcu();	for (i = 0; i < stab->map.max_entries; i++) {		struct sock **psk = &stab->sks[i];		struct sock *sk; 		sk = xchg(psk, NULL);		if (sk) {			lock_sock(sk);			rcu_read_lock();			sock_map_unref(sk, psk);			rcu_read_unlock();			release_sock(sk);		}	} 	/* wait for psock readers accessing its map link */	synchronize_rcu(); 	bpf_map_area_free(stab->sks);	kfree(stab);} static void sock_map_release_progs(struct bpf_map *map){	psock_progs_drop(&container_of(map, struct bpf_stab, map)->progs);} static struct sock *__sock_map_lookup_elem(struct bpf_map *map, u32 key){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map); 	WARN_ON_ONCE(!rcu_read_lock_held()); 	if (unlikely(key >= map->max_entries))		return NULL;	return READ_ONCE(stab->sks[key]);} static void *sock_map_lookup(struct bpf_map *map, void *key){	struct sock *sk; 	sk = __sock_map_lookup_elem(map, *(u32 *)key);	if (!sk || !sk_fullsock(sk))		return NULL;	if (sk_is_refcounted(sk) && !refcount_inc_not_zero(&sk->sk_refcnt))		return NULL;	return sk;} static void *sock_map_lookup_sys(struct bpf_map *map, void *key){	struct sock *sk; 	if (map->value_size != sizeof(u64))		return ERR_PTR(-ENOSPC); 	sk = __sock_map_lookup_elem(map, *(u32 *)key);	if (!sk)		return ERR_PTR(-ENOENT); 	sock_gen_cookie(sk);	return &sk->sk_cookie;} static int __sock_map_delete(struct bpf_stab *stab, struct sock *sk_test,			     struct sock **psk){	struct sock *sk;	int err = 0; 	raw_spin_lock_bh(&stab->lock);	sk = *psk;	if (!sk_test || sk_test == sk)		sk = xchg(psk, NULL); 	if (likely(sk))		sock_map_unref(sk, psk);	else		err = -EINVAL; 	raw_spin_unlock_bh(&stab->lock);	return err;} static void sock_map_delete_from_link(struct bpf_map *map, struct sock *sk,				      void *link_raw){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map); 	__sock_map_delete(stab, sk, link_raw);} static int sock_map_delete_elem(struct bpf_map *map, void *key){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map);	u32 i = *(u32 *)key;	struct sock **psk; 	if (unlikely(i >= map->max_entries))		return -EINVAL; 	psk = &stab->sks[i];	return __sock_map_delete(stab, NULL, psk);} static int sock_map_get_next_key(struct bpf_map *map, void *key, void *next){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map);	u32 i = key ? *(u32 *)key : U32_MAX;	u32 *key_next = next; 	if (i == stab->map.max_entries - 1)		return -ENOENT;	if (i >= stab->map.max_entries)		*key_next = 0;	else		*key_next = i + 1;	return 0;} static bool sock_map_redirect_allowed(const struct sock *sk); static int sock_map_update_common(struct bpf_map *map, u32 idx,				  struct sock *sk, u64 flags){	struct bpf_stab *stab = container_of(map, struct bpf_stab, map);	struct sk_psock_link *link;	struct sk_psock *psock;	struct sock *osk;	int ret; 	WARN_ON_ONCE(!rcu_read_lock_held());	if (unlikely(flags > BPF_EXIST))		return -EINVAL;	if (unlikely(idx >= map->max_entries))		return -E2BIG;	if (inet_csk_has_ulp(sk))		return -EINVAL; 	link = sk_psock_init_link();	if (!link)		return -ENOMEM; 	/* Only sockets we can redirect into/from in BPF need to hold	 * refs to parser/verdict progs and have their sk_data_ready	 * and sk_write_space callbacks overridden.	 */	if (sock_map_redirect_allowed(sk))		ret = sock_map_link(map, &stab->progs, sk);	else		ret = sock_map_link_no_progs(map, sk);	if (ret < 0)		goto out_free; 	psock = sk_psock(sk);	WARN_ON_ONCE(!psock);

Lines 1–500 of 1342.