1992 lines · 47.7 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/btf_ids.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;	spinlock_t lock;}; #define SOCK_CREATE_FLAG_MASK				\	(BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) /* This mutex is used to *  - protect race between prog/link attach/detach and link prog update, and *  - protect race between releasing and accessing map in bpf_link. * A single global mutex lock is used since it is expected contention is low. */static DEFINE_MUTEX(sockmap_mutex); static int sock_map_prog_update(struct bpf_map *map, struct bpf_prog *prog,				struct bpf_prog *old, struct bpf_link *link,				u32 which);static struct sk_psock_progs *sock_map_progs(struct bpf_map *map); static struct bpf_map *sock_map_alloc(union bpf_attr *attr){	struct bpf_stab *stab; 	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 = bpf_map_area_alloc(sizeof(*stab), NUMA_NO_NODE);	if (!stab)		return ERR_PTR(-ENOMEM); 	bpf_map_init_from_attr(&stab->map, attr);	spin_lock_init(&stab->lock); 	stab->sks = bpf_map_area_alloc((u64) stab->map.max_entries *				       sizeof(struct sock *),				       stab->map.numa_node);	if (!stab->sks) {		bpf_map_area_free(stab);		return ERR_PTR(-ENOMEM);	} 	return &stab->map;} int sock_map_get_from_fd(const union bpf_attr *attr, struct bpf_prog *prog){	struct bpf_map *map;	int ret; 	if (attr->attach_flags || attr->replace_bpf_fd)		return -EINVAL; 	CLASS(fd, f)(attr->target_fd);	map = __bpf_map_get(f);	if (IS_ERR(map))		return PTR_ERR(map);	mutex_lock(&sockmap_mutex);	ret = sock_map_prog_update(map, prog, NULL, NULL, attr->attach_type);	mutex_unlock(&sockmap_mutex);	return ret;} int sock_map_prog_detach(const union bpf_attr *attr, enum bpf_prog_type ptype){	struct bpf_prog *prog;	struct bpf_map *map;	int ret; 	if (attr->attach_flags || attr->replace_bpf_fd)		return -EINVAL; 	CLASS(fd, f)(attr->target_fd);	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))		return PTR_ERR(prog); 	if (prog->type != ptype) {		ret = -EINVAL;		goto put_prog;	} 	mutex_lock(&sockmap_mutex);	ret = sock_map_prog_update(map, NULL, prog, NULL, attr->attach_type);	mutex_unlock(&sockmap_mutex);put_prog:	bpf_prog_put(prog);	return ret;} static void sock_map_sk_acquire(struct sock *sk)	__acquires(&sk->sk_lock.slock){	lock_sock(sk);	rcu_read_lock();} static void sock_map_sk_release(struct sock *sk)	__releases(&sk->sk_lock.slock){	rcu_read_unlock();	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){	bool strp_stop = false, verdict_stop = false;	struct sk_psock_link *link, *tmp; 	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 sk_psock_progs *progs = sock_map_progs(map); 			if (psock->saved_data_ready && progs->stream_parser)				strp_stop = true;			if (psock->saved_data_ready && progs->stream_verdict)				verdict_stop = true;			if (psock->saved_data_ready && progs->skb_verdict)				verdict_stop = true;			list_del(&link->list);			sk_psock_free_link(link);			break;		}	}	spin_unlock_bh(&psock->link_lock);	if (strp_stop || verdict_stop) {		write_lock_bh(&sk->sk_callback_lock);		if (strp_stop)			sk_psock_stop_strp(sk, psock);		if (verdict_stop)			sk_psock_stop_verdict(sk, psock); 		if (psock->psock_update_sk_prot)			psock->psock_update_sk_prot(sk, psock, false);		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){	if (!sk->sk_prot->psock_update_sk_prot)		return -EINVAL;	psock->psock_update_sk_prot = sk->sk_prot->psock_update_sk_prot;	return sk->sk_prot->psock_update_sk_prot(sk, psock, false);} 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 sock *sk){	struct sk_psock_progs *progs = sock_map_progs(map);	struct bpf_prog *stream_verdict = NULL;	struct bpf_prog *stream_parser = NULL;	struct bpf_prog *skb_verdict = NULL;	struct bpf_prog *msg_parser = NULL;	struct sk_psock *psock;	int ret; 	stream_verdict = READ_ONCE(progs->stream_verdict);	if (stream_verdict) {		stream_verdict = bpf_prog_inc_not_zero(stream_verdict);		if (IS_ERR(stream_verdict))			return PTR_ERR(stream_verdict);	} 	stream_parser = READ_ONCE(progs->stream_parser);	if (stream_parser) {		stream_parser = bpf_prog_inc_not_zero(stream_parser);		if (IS_ERR(stream_parser)) {			ret = PTR_ERR(stream_parser);			goto out_put_stream_verdict;		}	} 	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_put_stream_parser;		}	} 	skb_verdict = READ_ONCE(progs->skb_verdict);	if (skb_verdict) {		skb_verdict = bpf_prog_inc_not_zero(skb_verdict);		if (IS_ERR(skb_verdict)) {			ret = PTR_ERR(skb_verdict);			goto out_put_msg_parser;		}	} 	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)) ||		    (stream_parser  && READ_ONCE(psock->progs.stream_parser)) ||		    (skb_verdict && READ_ONCE(psock->progs.skb_verdict)) ||		    (skb_verdict && READ_ONCE(psock->progs.stream_verdict)) ||		    (stream_verdict && READ_ONCE(psock->progs.skb_verdict)) ||		    (stream_verdict && READ_ONCE(psock->progs.stream_verdict))) {			sk_psock_put(sk, psock);			ret = -EBUSY;			goto out_progs;		}	} else {		psock = sk_psock_init(sk, map->numa_node);		if (IS_ERR(psock)) {			ret = PTR_ERR(psock);			goto out_progs;		}	} 	if (msg_parser)		psock_set_prog(&psock->progs.msg_parser, msg_parser);	if (stream_parser)		psock_set_prog(&psock->progs.stream_parser, stream_parser);	if (stream_verdict)		psock_set_prog(&psock->progs.stream_verdict, stream_verdict);	if (skb_verdict)		psock_set_prog(&psock->progs.skb_verdict, skb_verdict); 	/* msg_* and stream_* programs references tracked in psock after this	 * point. Reference dec and cleanup will occur through psock destructor	 */	ret = sock_map_init_proto(sk, psock);	if (ret < 0) {		sk_psock_put(sk, psock);		goto out;	} 	write_lock_bh(&sk->sk_callback_lock);	if (stream_parser && stream_verdict && !psock->saved_data_ready) {		if (sk_is_tcp(sk))			ret = sk_psock_init_strp(sk, psock);		else			ret = -EOPNOTSUPP;		if (ret) {			write_unlock_bh(&sk->sk_callback_lock);			sk_psock_put(sk, psock);			goto out;		}		sk_psock_start_strp(sk, psock);	} else if (!stream_parser && stream_verdict && !psock->saved_data_ready) {		sk_psock_start_verdict(sk,psock);	} else if (!stream_verdict && skb_verdict && !psock->saved_data_ready) {		sk_psock_start_verdict(sk, psock);	}	write_unlock_bh(&sk->sk_callback_lock);	return 0;out_progs:	if (skb_verdict)		bpf_prog_put(skb_verdict);out_put_msg_parser:	if (msg_parser)		bpf_prog_put(msg_parser);out_put_stream_parser:	if (stream_parser)		bpf_prog_put(stream_parser);out_put_stream_verdict:	if (stream_verdict)		bpf_prog_put(stream_verdict);out:	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) {			sock_hold(sk);			lock_sock(sk);			rcu_read_lock();			sock_map_unref(sk, psk);			rcu_read_unlock();			release_sock(sk);			sock_put(sk);		}	} 	/* wait for psock readers accessing its map link */	synchronize_rcu(); 	bpf_map_area_free(stab->sks);	bpf_map_area_free(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)		return NULL;	if (sk_is_refcounted(sk))		sock_hold(sk);	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 = NULL;	int err = 0; 	spin_lock_bh(&stab->lock);	if (!sk_test || sk_test == *psk)		sk = xchg(psk, NULL); 	if (likely(sk))		sock_map_unref(sk, psk);	else		err = -EINVAL; 	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 long 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 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; 	link = sk_psock_init_link();	if (!link)		return -ENOMEM; 	ret = sock_map_link(map, sk);	if (ret < 0)		goto out_free; 	psock = sk_psock(sk);	WARN_ON_ONCE(!psock); 	spin_lock_bh(&stab->lock);	osk = stab->sks[idx];	if (osk && flags == BPF_NOEXIST) {		ret = -EEXIST;		goto out_unlock;

Lines 1–500 of 1992.