|  | // 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; | 
|  | } |