452 lines · 11.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* Copyright (c) 2023 Isovalent */ #include <linux/bpf.h>#include <linux/bpf_mprog.h> static int bpf_mprog_link(struct bpf_tuple *tuple,			  u32 id_or_fd, u32 flags,			  enum bpf_prog_type type){	struct bpf_link *link = ERR_PTR(-EINVAL);	bool id = flags & BPF_F_ID; 	if (id)		link = bpf_link_by_id(id_or_fd);	else if (id_or_fd)		link = bpf_link_get_from_fd(id_or_fd);	if (IS_ERR(link))		return PTR_ERR(link);	if (type && link->prog->type != type) {		bpf_link_put(link);		return -EINVAL;	} 	tuple->link = link;	tuple->prog = link->prog;	return 0;} static int bpf_mprog_prog(struct bpf_tuple *tuple,			  u32 id_or_fd, u32 flags,			  enum bpf_prog_type type){	struct bpf_prog *prog = ERR_PTR(-EINVAL);	bool id = flags & BPF_F_ID; 	if (id)		prog = bpf_prog_by_id(id_or_fd);	else if (id_or_fd)		prog = bpf_prog_get(id_or_fd);	if (IS_ERR(prog))		return PTR_ERR(prog);	if (type && prog->type != type) {		bpf_prog_put(prog);		return -EINVAL;	} 	tuple->link = NULL;	tuple->prog = prog;	return 0;} static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,				    u32 id_or_fd, u32 flags,				    enum bpf_prog_type type){	bool link = flags & BPF_F_LINK;	bool id = flags & BPF_F_ID; 	memset(tuple, 0, sizeof(*tuple));	if (link)		return bpf_mprog_link(tuple, id_or_fd, flags, type);	/* If no relevant flag is set and no id_or_fd was passed, then	 * tuple link/prog is just NULLed. This is the case when before/	 * after selects first/last position without passing fd.	 */	if (!id && !id_or_fd)		return 0;	return bpf_mprog_prog(tuple, id_or_fd, flags, type);} static void bpf_mprog_tuple_put(struct bpf_tuple *tuple){	if (tuple->link)		bpf_link_put(tuple->link);	else if (tuple->prog)		bpf_prog_put(tuple->prog);} /* The bpf_mprog_{replace,delete}() operate on exact idx position with the * one exception that for deletion we support delete from front/back. In * case of front idx is -1, in case of back idx is bpf_mprog_total(entry). * Adjustment to first and last entry is trivial. The bpf_mprog_insert() * we have to deal with the following cases: * * idx + before: * * Insert P4 before P3: idx for old array is 1, idx for new array is 2, * hence we adjust target idx for the new array, so that memmove copies * P1 and P2 to the new entry, and we insert P4 into idx 2. Inserting * before P1 would have old idx -1 and new idx 0. * * +--+--+--+     +--+--+--+--+     +--+--+--+--+ * |P1|P2|P3| ==> |P1|P2|  |P3| ==> |P1|P2|P4|P3| * +--+--+--+     +--+--+--+--+     +--+--+--+--+ * * idx + after: * * Insert P4 after P2: idx for old array is 2, idx for new array is 2. * Again, memmove copies P1 and P2 to the new entry, and we insert P4 * into idx 2. Inserting after P3 would have both old/new idx at 4 aka * bpf_mprog_total(entry). * * +--+--+--+     +--+--+--+--+     +--+--+--+--+ * |P1|P2|P3| ==> |P1|P2|  |P3| ==> |P1|P2|P4|P3| * +--+--+--+     +--+--+--+--+     +--+--+--+--+ */static int bpf_mprog_replace(struct bpf_mprog_entry *entry,			     struct bpf_mprog_entry **entry_new,			     struct bpf_tuple *ntuple, int idx){	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp;	struct bpf_prog *oprog; 	bpf_mprog_read(entry, idx, &fp, &cp);	oprog = READ_ONCE(fp->prog);	bpf_mprog_write(fp, cp, ntuple);	if (!ntuple->link) {		WARN_ON_ONCE(cp->link);		bpf_prog_put(oprog);	}	*entry_new = entry;	return 0;} static int bpf_mprog_insert(struct bpf_mprog_entry *entry,			    struct bpf_mprog_entry **entry_new,			    struct bpf_tuple *ntuple, int idx, u32 flags){	int total = bpf_mprog_total(entry);	struct bpf_mprog_entry *peer;	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp; 	peer = bpf_mprog_peer(entry);	bpf_mprog_entry_copy(peer, entry);	if (idx == total)		goto insert;	else if (flags & BPF_F_BEFORE)		idx += 1;	bpf_mprog_entry_grow(peer, idx);insert:	bpf_mprog_read(peer, idx, &fp, &cp);	bpf_mprog_write(fp, cp, ntuple);	bpf_mprog_inc(peer);	*entry_new = peer;	return 0;} static int bpf_mprog_delete(struct bpf_mprog_entry *entry,			    struct bpf_mprog_entry **entry_new,			    struct bpf_tuple *dtuple, int idx){	int total = bpf_mprog_total(entry);	struct bpf_mprog_entry *peer; 	peer = bpf_mprog_peer(entry);	bpf_mprog_entry_copy(peer, entry);	if (idx == -1)		idx = 0;	else if (idx == total)		idx = total - 1;	bpf_mprog_entry_shrink(peer, idx);	bpf_mprog_dec(peer);	bpf_mprog_mark_for_release(peer, dtuple);	*entry_new = peer;	return 0;} /* In bpf_mprog_pos_*() we evaluate the target position for the BPF * program/link that needs to be replaced, inserted or deleted for * each "rule" independently. If all rules agree on that position * or existing element, then enact replacement, addition or deletion. * If this is not the case, then the request cannot be satisfied and * we bail out with an error. */static int bpf_mprog_pos_exact(struct bpf_mprog_entry *entry,			       struct bpf_tuple *tuple){	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp;	int i; 	for (i = 0; i < bpf_mprog_total(entry); i++) {		bpf_mprog_read(entry, i, &fp, &cp);		if (tuple->prog == READ_ONCE(fp->prog))			return tuple->link == cp->link ? i : -EBUSY;	}	return -ENOENT;} static int bpf_mprog_pos_before(struct bpf_mprog_entry *entry,				struct bpf_tuple *tuple){	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp;	int i; 	for (i = 0; i < bpf_mprog_total(entry); i++) {		bpf_mprog_read(entry, i, &fp, &cp);		if (tuple->prog == READ_ONCE(fp->prog) &&		    (!tuple->link || tuple->link == cp->link))			return i - 1;	}	return tuple->prog ? -ENOENT : -1;} static int bpf_mprog_pos_after(struct bpf_mprog_entry *entry,			       struct bpf_tuple *tuple){	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp;	int i; 	for (i = 0; i < bpf_mprog_total(entry); i++) {		bpf_mprog_read(entry, i, &fp, &cp);		if (tuple->prog == READ_ONCE(fp->prog) &&		    (!tuple->link || tuple->link == cp->link))			return i + 1;	}	return tuple->prog ? -ENOENT : bpf_mprog_total(entry);} int bpf_mprog_attach(struct bpf_mprog_entry *entry,		     struct bpf_mprog_entry **entry_new,		     struct bpf_prog *prog_new, struct bpf_link *link,		     struct bpf_prog *prog_old,		     u32 flags, u32 id_or_fd, u64 revision){	struct bpf_tuple rtuple, ntuple = {		.prog = prog_new,		.link = link,	}, otuple = {		.prog = prog_old,		.link = link,	};	int ret, idx = -ERANGE, tidx; 	if (revision && revision != bpf_mprog_revision(entry))		return -ESTALE;	if (bpf_mprog_exists(entry, prog_new))		return -EEXIST;	ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,				       flags & ~BPF_F_REPLACE,				       prog_new->type);	if (ret)		return ret;	if (flags & BPF_F_REPLACE) {		tidx = bpf_mprog_pos_exact(entry, &otuple);		if (tidx < 0) {			ret = tidx;			goto out;		}		idx = tidx;	} else if (bpf_mprog_total(entry) == bpf_mprog_max()) {		ret = -ERANGE;		goto out;	}	if (flags & BPF_F_BEFORE) {		tidx = bpf_mprog_pos_before(entry, &rtuple);		if (tidx < -1 || (idx >= -1 && tidx != idx)) {			ret = tidx < -1 ? tidx : -ERANGE;			goto out;		}		idx = tidx;	}	if (flags & BPF_F_AFTER) {		tidx = bpf_mprog_pos_after(entry, &rtuple);		if (tidx < -1 || (idx >= -1 && tidx != idx)) {			ret = tidx < 0 ? tidx : -ERANGE;			goto out;		}		idx = tidx;	}	if (idx < -1) {		if (rtuple.prog || flags) {			ret = -EINVAL;			goto out;		}		idx = bpf_mprog_total(entry);		flags = BPF_F_AFTER;	}	if (idx >= bpf_mprog_max()) {		ret = -ERANGE;		goto out;	}	if (flags & BPF_F_REPLACE)		ret = bpf_mprog_replace(entry, entry_new, &ntuple, idx);	else		ret = bpf_mprog_insert(entry, entry_new, &ntuple, idx, flags);out:	bpf_mprog_tuple_put(&rtuple);	return ret;} static int bpf_mprog_fetch(struct bpf_mprog_entry *entry,			   struct bpf_tuple *tuple, int idx){	int total = bpf_mprog_total(entry);	struct bpf_mprog_cp *cp;	struct bpf_mprog_fp *fp;	struct bpf_prog *prog;	struct bpf_link *link; 	if (idx == -1)		idx = 0;	else if (idx == total)		idx = total - 1;	bpf_mprog_read(entry, idx, &fp, &cp);	prog = READ_ONCE(fp->prog);	link = cp->link;	/* The deletion request can either be without filled tuple in which	 * case it gets populated here based on idx, or with filled tuple	 * where the only thing we end up doing is the WARN_ON_ONCE() assert.	 * If we hit a BPF link at the given index, it must not be removed	 * from opts path.	 */	if (link && !tuple->link)		return -EBUSY;	WARN_ON_ONCE(tuple->prog && tuple->prog != prog);	WARN_ON_ONCE(tuple->link && tuple->link != link);	tuple->prog = prog;	tuple->link = link;	return 0;} int bpf_mprog_detach(struct bpf_mprog_entry *entry,		     struct bpf_mprog_entry **entry_new,		     struct bpf_prog *prog, struct bpf_link *link,		     u32 flags, u32 id_or_fd, u64 revision){	struct bpf_tuple rtuple, dtuple = {		.prog = prog,		.link = link,	};	int ret, idx = -ERANGE, tidx; 	if (flags & BPF_F_REPLACE)		return -EINVAL;	if (revision && revision != bpf_mprog_revision(entry))		return -ESTALE;	if (!bpf_mprog_total(entry))		return -ENOENT;	ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,				       prog ? prog->type :				       BPF_PROG_TYPE_UNSPEC);	if (ret)		return ret;	if (dtuple.prog) {		tidx = bpf_mprog_pos_exact(entry, &dtuple);		if (tidx < 0) {			ret = tidx;			goto out;		}		idx = tidx;	}	if (flags & BPF_F_BEFORE) {		tidx = bpf_mprog_pos_before(entry, &rtuple);		if (tidx < -1 || (idx >= -1 && tidx != idx)) {			ret = tidx < -1 ? tidx : -ERANGE;			goto out;		}		idx = tidx;	}	if (flags & BPF_F_AFTER) {		tidx = bpf_mprog_pos_after(entry, &rtuple);		if (tidx < -1 || (idx >= -1 && tidx != idx)) {			ret = tidx < 0 ? tidx : -ERANGE;			goto out;		}		idx = tidx;	}	if (idx < -1) {		if (rtuple.prog || flags) {			ret = -EINVAL;			goto out;		}		idx = bpf_mprog_total(entry);		flags = BPF_F_AFTER;	}	if (idx >= bpf_mprog_max()) {		ret = -ERANGE;		goto out;	}	ret = bpf_mprog_fetch(entry, &dtuple, idx);	if (ret)		goto out;	ret = bpf_mprog_delete(entry, entry_new, &dtuple, idx);out:	bpf_mprog_tuple_put(&rtuple);	return ret;} int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr __user *uattr,		    struct bpf_mprog_entry *entry){	u32 __user *uprog_flags, *ulink_flags;	u32 __user *uprog_id, *ulink_id;	struct bpf_mprog_fp *fp;	struct bpf_mprog_cp *cp;	struct bpf_prog *prog;	const u32 flags = 0;	u32 id, count = 0;	u64 revision = 1;	int i, ret = 0; 	if (attr->query.query_flags || attr->query.attach_flags)		return -EINVAL;	if (entry) {		revision = bpf_mprog_revision(entry);		count = bpf_mprog_total(entry);	}	if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags)))		return -EFAULT;	if (copy_to_user(&uattr->query.revision, &revision, sizeof(revision)))		return -EFAULT;	if (copy_to_user(&uattr->query.count, &count, sizeof(count)))		return -EFAULT;	uprog_id = u64_to_user_ptr(attr->query.prog_ids);	uprog_flags = u64_to_user_ptr(attr->query.prog_attach_flags);	ulink_id = u64_to_user_ptr(attr->query.link_ids);	ulink_flags = u64_to_user_ptr(attr->query.link_attach_flags);	if (attr->query.count == 0 || !uprog_id || !count)		return 0;	if (attr->query.count < count) {		count = attr->query.count;		ret = -ENOSPC;	}	for (i = 0; i < bpf_mprog_max(); i++) {		bpf_mprog_read(entry, i, &fp, &cp);		prog = READ_ONCE(fp->prog);		if (!prog)			break;		id = prog->aux->id;		if (copy_to_user(uprog_id + i, &id, sizeof(id)))			return -EFAULT;		if (uprog_flags &&		    copy_to_user(uprog_flags + i, &flags, sizeof(flags)))			return -EFAULT;		id = cp->link ? cp->link->id : 0;		if (ulink_id &&		    copy_to_user(ulink_id + i, &id, sizeof(id)))			return -EFAULT;		if (ulink_flags &&		    copy_to_user(ulink_flags + i, &flags, sizeof(flags)))			return -EFAULT;		if (i + 1 == count)			break;	}	return ret;}