Add two new helper functions: bpf_dynptr_trim and bpf_dynptr_advance. bpf_dynptr_trim decreases the size of a dynptr by the specified number of bytes (offset remains the same). bpf_dynptr_advance advances the offset of the dynptr by the specified number of bytes (size decreases correspondingly). One example where trimming / advancing the dynptr may useful is for hashing. If the dynptr points to a larger struct, it is possible to hash an individual field within the struct through dynptrs by using bpf_dynptr_advance+trim. Signed-off-by: Joanne Koong <joannelkoong@xxxxxxxxx> --- include/uapi/linux/bpf.h | 18 +++++++++ kernel/bpf/helpers.c | 67 ++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 18 +++++++++ 3 files changed, 103 insertions(+) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index a9bb98365031..c2d915601484 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5537,6 +5537,22 @@ union bpf_attr { * *flags* is currently unused, it must be 0 for now. * Return * 0 on success, -EINVAL if flags is not 0. + * + * long bpf_dynptr_advance(struct bpf_dynptr *ptr, u32 len) + * Description + * Advance a dynptr's internal offset by *len* bytes. + * Return + * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if *len* + * exceeds the bounds of the dynptr. + * + * long bpf_dynptr_trim(struct bpf_dynptr *ptr, u32 len) + * Description + * Trim the size of memory pointed to by the dynptr by *len* bytes. + * + * The offset is unmodified. + * Return + * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if + * trying to trim more bytes than the size of the dynptr. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5753,6 +5769,8 @@ union bpf_attr { FN(cgrp_storage_delete, 211, ##ctx) \ FN(dynptr_from_skb, 212, ##ctx) \ FN(dynptr_from_xdp, 213, ##ctx) \ + FN(dynptr_advance, 214, ##ctx) \ + FN(dynptr_trim, 215, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 3a9c8814aaf6..fa3989047ff6 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1429,6 +1429,13 @@ u32 bpf_dynptr_get_size(struct bpf_dynptr_kern *ptr) return ptr->size & DYNPTR_SIZE_MASK; } +static void bpf_dynptr_set_size(struct bpf_dynptr_kern *ptr, u32 new_size) +{ + u32 metadata = ptr->size & ~DYNPTR_SIZE_MASK; + + ptr->size = new_size | metadata; +} + int bpf_dynptr_check_size(u32 size) { return size > DYNPTR_MAX_SIZE ? -E2BIG : 0; @@ -1640,6 +1647,62 @@ static const struct bpf_func_proto bpf_dynptr_data_proto = { .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, }; +/* For dynptrs, the offset may only be advanced and the size may only be decremented */ +static int bpf_dynptr_adjust(struct bpf_dynptr_kern *ptr, u32 off_inc, u32 sz_dec) +{ + u32 size; + + if (!ptr->data) + return -EINVAL; + + size = bpf_dynptr_get_size(ptr); + + if (sz_dec > size) + return -ERANGE; + + if (off_inc) { + u32 new_off; + + if (off_inc > size) + return -ERANGE; + + if (check_add_overflow(ptr->offset, off_inc, &new_off)) + return -ERANGE; + + ptr->offset = new_off; + } + + bpf_dynptr_set_size(ptr, size - sz_dec); + + return 0; +} + +BPF_CALL_2(bpf_dynptr_advance, struct bpf_dynptr_kern *, ptr, u32, len) +{ + return bpf_dynptr_adjust(ptr, len, len); +} + +static const struct bpf_func_proto bpf_dynptr_advance_proto = { + .func = bpf_dynptr_advance, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, + .arg2_type = ARG_ANYTHING, +}; + +BPF_CALL_2(bpf_dynptr_trim, struct bpf_dynptr_kern *, ptr, u32, len) +{ + return bpf_dynptr_adjust(ptr, 0, len); +} + +static const struct bpf_func_proto bpf_dynptr_trim_proto = { + .func = bpf_dynptr_trim, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, + .arg2_type = ARG_ANYTHING, +}; + const struct bpf_func_proto bpf_get_current_task_proto __weak; const struct bpf_func_proto bpf_get_current_task_btf_proto __weak; const struct bpf_func_proto bpf_probe_read_user_proto __weak; @@ -1744,6 +1807,10 @@ bpf_base_func_proto(enum bpf_func_id func_id) return &bpf_dynptr_write_proto; case BPF_FUNC_dynptr_data: return &bpf_dynptr_data_proto; + case BPF_FUNC_dynptr_advance: + return &bpf_dynptr_advance_proto; + case BPF_FUNC_dynptr_trim: + return &bpf_dynptr_trim_proto; #ifdef CONFIG_CGROUPS case BPF_FUNC_cgrp_storage_get: return &bpf_cgrp_storage_get_proto; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index a9bb98365031..c2d915601484 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5537,6 +5537,22 @@ union bpf_attr { * *flags* is currently unused, it must be 0 for now. * Return * 0 on success, -EINVAL if flags is not 0. + * + * long bpf_dynptr_advance(struct bpf_dynptr *ptr, u32 len) + * Description + * Advance a dynptr's internal offset by *len* bytes. + * Return + * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if *len* + * exceeds the bounds of the dynptr. + * + * long bpf_dynptr_trim(struct bpf_dynptr *ptr, u32 len) + * Description + * Trim the size of memory pointed to by the dynptr by *len* bytes. + * + * The offset is unmodified. + * Return + * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if + * trying to trim more bytes than the size of the dynptr. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5753,6 +5769,8 @@ union bpf_attr { FN(cgrp_storage_delete, 211, ##ctx) \ FN(dynptr_from_skb, 212, ##ctx) \ FN(dynptr_from_xdp, 213, ##ctx) \ + FN(dynptr_advance, 214, ##ctx) \ + FN(dynptr_trim, 215, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't -- 2.30.2