From patchwork Wed Dec 7 20:55:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067666 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 77004C63705 for ; Wed, 7 Dec 2022 21:19:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229668AbiLGVTg (ORCPT ); Wed, 7 Dec 2022 16:19:36 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53004 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229652AbiLGVTf (ORCPT ); Wed, 7 Dec 2022 16:19:35 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9A0CC81DA6 for ; Wed, 7 Dec 2022 13:19:34 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id C61A71355387; Wed, 7 Dec 2022 12:56:44 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 1/6] bpf: Add bpf_dynptr_trim and bpf_dynptr_advance Date: Wed, 7 Dec 2022 12:55:32 -0800 Message-Id: <20221207205537.860248-2-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net 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 --- 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 From patchwork Wed Dec 7 20:55:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067662 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A7DAEC63705 for ; Wed, 7 Dec 2022 21:17:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229507AbiLGVRJ (ORCPT ); Wed, 7 Dec 2022 16:17:09 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51802 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229628AbiLGVQ6 (ORCPT ); Wed, 7 Dec 2022 16:16:58 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4E8121CFF2 for ; Wed, 7 Dec 2022 13:16:57 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id AE0D21355392; Wed, 7 Dec 2022 12:56:46 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 2/6] bpf: Add bpf_dynptr_is_null and bpf_dynptr_is_rdonly Date: Wed, 7 Dec 2022 12:55:33 -0800 Message-Id: <20221207205537.860248-3-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net Add two new helper functions: bpf_dynptr_is_null and bpf_dynptr_is_rdonly. bpf_dynptr_is_null returns true if the dynptr is null / invalid (determined by whether ptr->data is NULL), else false if the dynptr is a valid dynptr. bpf_dynptr_is_rdonly returns true if the dynptr is read-only, else false if the dynptr is read-writable. Signed-off-by: Joanne Koong --- include/uapi/linux/bpf.h | 20 ++++++++++++++++++ kernel/bpf/helpers.c | 37 +++++++++++++++++++++++++++++++--- scripts/bpf_doc.py | 3 +++ tools/include/uapi/linux/bpf.h | 20 ++++++++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index c2d915601484..80582bc00bf4 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5553,6 +5553,24 @@ union bpf_attr { * Return * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if * trying to trim more bytes than the size of the dynptr. + * + * bool bpf_dynptr_is_null(struct bpf_dynptr *ptr) + * Description + * Determine whether a dynptr is null / invalid. + * + * *ptr* must be an initialized dynptr. + * Return + * True if the dynptr is null, else false. + * + * bool bpf_dynptr_is_rdonly(struct bpf_dynptr *ptr) + * Description + * Determine whether a dynptr is read-only. + * + * *ptr* must be an initialized dynptr. If *ptr* + * is a null dynptr, this will return false. + * Return + * True if the dynptr is read-only and a valid dynptr, + * else false. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5771,6 +5789,8 @@ union bpf_attr { FN(dynptr_from_xdp, 213, ##ctx) \ FN(dynptr_advance, 214, ##ctx) \ FN(dynptr_trim, 215, ##ctx) \ + FN(dynptr_is_null, 216, ##ctx) \ + FN(dynptr_is_rdonly, 217, ##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 fa3989047ff6..cd9e1a2972fe 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1404,7 +1404,7 @@ static const struct bpf_func_proto bpf_kptr_xchg_proto = { #define DYNPTR_SIZE_MASK 0xFFFFFF #define DYNPTR_RDONLY_BIT BIT(31) -static bool bpf_dynptr_is_rdonly(struct bpf_dynptr_kern *ptr) +static bool __bpf_dynptr_is_rdonly(struct bpf_dynptr_kern *ptr) { return ptr->size & DYNPTR_RDONLY_BIT; } @@ -1547,7 +1547,7 @@ BPF_CALL_5(bpf_dynptr_write, struct bpf_dynptr_kern *, dst, u32, offset, void *, enum bpf_dynptr_type type; int err; - if (!dst->data || bpf_dynptr_is_rdonly(dst)) + if (!dst->data || __bpf_dynptr_is_rdonly(dst)) return -EINVAL; err = bpf_dynptr_check_off_len(dst, offset, len); @@ -1605,7 +1605,7 @@ BPF_CALL_3(bpf_dynptr_data, struct bpf_dynptr_kern *, ptr, u32, offset, u32, len switch (type) { case BPF_DYNPTR_TYPE_LOCAL: case BPF_DYNPTR_TYPE_RINGBUF: - if (bpf_dynptr_is_rdonly(ptr)) + if (__bpf_dynptr_is_rdonly(ptr)) return 0; data = ptr->data; @@ -1703,6 +1703,33 @@ static const struct bpf_func_proto bpf_dynptr_trim_proto = { .arg2_type = ARG_ANYTHING, }; +BPF_CALL_1(bpf_dynptr_is_null, struct bpf_dynptr_kern *, ptr) +{ + return !ptr->data; +} + +static const struct bpf_func_proto bpf_dynptr_is_null_proto = { + .func = bpf_dynptr_is_null, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, +}; + +BPF_CALL_1(bpf_dynptr_is_rdonly, struct bpf_dynptr_kern *, ptr) +{ + if (!ptr->data) + return false; + + return __bpf_dynptr_is_rdonly(ptr); +} + +static const struct bpf_func_proto bpf_dynptr_is_rdonly_proto = { + .func = bpf_dynptr_is_rdonly, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, +}; + 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; @@ -1811,6 +1838,10 @@ bpf_base_func_proto(enum bpf_func_id func_id) return &bpf_dynptr_advance_proto; case BPF_FUNC_dynptr_trim: return &bpf_dynptr_trim_proto; + case BPF_FUNC_dynptr_is_null: + return &bpf_dynptr_is_null_proto; + case BPF_FUNC_dynptr_is_rdonly: + return &bpf_dynptr_is_rdonly_proto; #ifdef CONFIG_CGROUPS case BPF_FUNC_cgrp_storage_get: return &bpf_cgrp_storage_get_proto; diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py index fdb0aff8cb5a..c20cf141e787 100755 --- a/scripts/bpf_doc.py +++ b/scripts/bpf_doc.py @@ -710,6 +710,7 @@ class PrinterHelpers(Printer): 'int', 'long', 'unsigned long', + 'bool', '__be16', '__be32', @@ -781,6 +782,8 @@ class PrinterHelpers(Printer): header = '''\ /* This is auto-generated file. See bpf_doc.py for details. */ +#include + /* Forward declarations of BPF structs */''' print(header) diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index c2d915601484..80582bc00bf4 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5553,6 +5553,24 @@ union bpf_attr { * Return * 0 on success, -EINVAL if the dynptr is invalid, -ERANGE if * trying to trim more bytes than the size of the dynptr. + * + * bool bpf_dynptr_is_null(struct bpf_dynptr *ptr) + * Description + * Determine whether a dynptr is null / invalid. + * + * *ptr* must be an initialized dynptr. + * Return + * True if the dynptr is null, else false. + * + * bool bpf_dynptr_is_rdonly(struct bpf_dynptr *ptr) + * Description + * Determine whether a dynptr is read-only. + * + * *ptr* must be an initialized dynptr. If *ptr* + * is a null dynptr, this will return false. + * Return + * True if the dynptr is read-only and a valid dynptr, + * else false. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5771,6 +5789,8 @@ union bpf_attr { FN(dynptr_from_xdp, 213, ##ctx) \ FN(dynptr_advance, 214, ##ctx) \ FN(dynptr_trim, 215, ##ctx) \ + FN(dynptr_is_null, 216, ##ctx) \ + FN(dynptr_is_rdonly, 217, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't From patchwork Wed Dec 7 20:55:34 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067667 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A6FE4C63705 for ; Wed, 7 Dec 2022 21:20:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229705AbiLGVUU (ORCPT ); Wed, 7 Dec 2022 16:20:20 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53326 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229635AbiLGVUT (ORCPT ); Wed, 7 Dec 2022 16:20:19 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E83014E402 for ; Wed, 7 Dec 2022 13:20:18 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id 44DD21355394; Wed, 7 Dec 2022 12:56:47 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 3/6] bpf: Add bpf_dynptr_get_size and bpf_dynptr_get_offset Date: Wed, 7 Dec 2022 12:55:34 -0800 Message-Id: <20221207205537.860248-4-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net Add two new helper functions: bpf_dynptr_get_size and bpf_dynptr_get_offset. bpf_dynptr_get_size returns the number of usable bytes in a dynptr and bpf_dynptr_get_offset returns the current offset into the dynptr. Signed-off-by: Joanne Koong --- include/linux/bpf.h | 2 +- include/uapi/linux/bpf.h | 25 +++++++++++++++++++++ kernel/bpf/helpers.c | 40 +++++++++++++++++++++++++++++++--- kernel/trace/bpf_trace.c | 4 ++-- tools/include/uapi/linux/bpf.h | 25 +++++++++++++++++++++ 5 files changed, 90 insertions(+), 6 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 5628256de3e5..753444e1478c 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1118,7 +1118,7 @@ enum bpf_dynptr_type { }; int bpf_dynptr_check_size(u32 size); -u32 bpf_dynptr_get_size(struct bpf_dynptr_kern *ptr); +u32 __bpf_dynptr_get_size(struct bpf_dynptr_kern *ptr); #ifdef CONFIG_BPF_JIT int bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr); diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 80582bc00bf4..5ad52d481cde 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5571,6 +5571,29 @@ union bpf_attr { * Return * True if the dynptr is read-only and a valid dynptr, * else false. + * + * long bpf_dynptr_get_size(struct bpf_dynptr *ptr) + * Description + * Get the size of *ptr*. + * + * Size refers to the number of usable bytes. For example, + * if *ptr* was initialized with 100 bytes and its + * offset was advanced by 40 bytes, then the size will be + * 60 bytes. + * + * *ptr* must be an initialized dynptr. + * Return + * The size of the dynptr on success, -EINVAL if the dynptr is + * invalid. + * + * long bpf_dynptr_get_offset(struct bpf_dynptr *ptr) + * Description + * Get the offset of the dynptr. + * + * *ptr* must be an initialized dynptr. + * Return + * The offset of the dynptr on success, -EINVAL if the dynptr is + * invalid. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5791,6 +5814,8 @@ union bpf_attr { FN(dynptr_trim, 215, ##ctx) \ FN(dynptr_is_null, 216, ##ctx) \ FN(dynptr_is_rdonly, 217, ##ctx) \ + FN(dynptr_get_size, 218, ##ctx) \ + FN(dynptr_get_offset, 219, ##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 cd9e1a2972fe..0164d7e4b5a6 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1424,7 +1424,7 @@ static enum bpf_dynptr_type bpf_dynptr_get_type(const struct bpf_dynptr_kern *pt return (ptr->size & ~(DYNPTR_RDONLY_BIT)) >> DYNPTR_TYPE_SHIFT; } -u32 bpf_dynptr_get_size(struct bpf_dynptr_kern *ptr) +u32 __bpf_dynptr_get_size(struct bpf_dynptr_kern *ptr) { return ptr->size & DYNPTR_SIZE_MASK; } @@ -1457,7 +1457,7 @@ void bpf_dynptr_set_null(struct bpf_dynptr_kern *ptr) static int bpf_dynptr_check_off_len(struct bpf_dynptr_kern *ptr, u32 offset, u32 len) { - u32 size = bpf_dynptr_get_size(ptr); + u32 size = __bpf_dynptr_get_size(ptr); if (len > size || offset > size - len) return -E2BIG; @@ -1655,7 +1655,7 @@ static int bpf_dynptr_adjust(struct bpf_dynptr_kern *ptr, u32 off_inc, u32 sz_de if (!ptr->data) return -EINVAL; - size = bpf_dynptr_get_size(ptr); + size = __bpf_dynptr_get_size(ptr); if (sz_dec > size) return -ERANGE; @@ -1730,6 +1730,36 @@ static const struct bpf_func_proto bpf_dynptr_is_rdonly_proto = { .arg1_type = ARG_PTR_TO_DYNPTR, }; +BPF_CALL_1(bpf_dynptr_get_size, struct bpf_dynptr_kern *, ptr) +{ + if (!ptr->data) + return -EINVAL; + + return __bpf_dynptr_get_size(ptr); +} + +static const struct bpf_func_proto bpf_dynptr_get_size_proto = { + .func = bpf_dynptr_get_size, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, +}; + +BPF_CALL_1(bpf_dynptr_get_offset, struct bpf_dynptr_kern *, ptr) +{ + if (!ptr->data) + return -EINVAL; + + return ptr->offset; +} + +static const struct bpf_func_proto bpf_dynptr_get_offset_proto = { + .func = bpf_dynptr_get_offset, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, +}; + 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; @@ -1842,6 +1872,10 @@ bpf_base_func_proto(enum bpf_func_id func_id) return &bpf_dynptr_is_null_proto; case BPF_FUNC_dynptr_is_rdonly: return &bpf_dynptr_is_rdonly_proto; + case BPF_FUNC_dynptr_get_size: + return &bpf_dynptr_get_size_proto; + case BPF_FUNC_dynptr_get_offset: + return &bpf_dynptr_get_offset_proto; #ifdef CONFIG_CGROUPS case BPF_FUNC_cgrp_storage_get: return &bpf_cgrp_storage_get_proto; diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 3bbd3f0c810c..e057570b4e2c 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -1349,9 +1349,9 @@ int bpf_verify_pkcs7_signature(struct bpf_dynptr_kern *data_ptr, } return verify_pkcs7_signature(data_ptr->data, - bpf_dynptr_get_size(data_ptr), + __bpf_dynptr_get_size(data_ptr), sig_ptr->data, - bpf_dynptr_get_size(sig_ptr), + __bpf_dynptr_get_size(sig_ptr), trusted_keyring->key, VERIFYING_UNSPECIFIED_SIGNATURE, NULL, NULL); diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 80582bc00bf4..5ad52d481cde 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5571,6 +5571,29 @@ union bpf_attr { * Return * True if the dynptr is read-only and a valid dynptr, * else false. + * + * long bpf_dynptr_get_size(struct bpf_dynptr *ptr) + * Description + * Get the size of *ptr*. + * + * Size refers to the number of usable bytes. For example, + * if *ptr* was initialized with 100 bytes and its + * offset was advanced by 40 bytes, then the size will be + * 60 bytes. + * + * *ptr* must be an initialized dynptr. + * Return + * The size of the dynptr on success, -EINVAL if the dynptr is + * invalid. + * + * long bpf_dynptr_get_offset(struct bpf_dynptr *ptr) + * Description + * Get the offset of the dynptr. + * + * *ptr* must be an initialized dynptr. + * Return + * The offset of the dynptr on success, -EINVAL if the dynptr is + * invalid. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5791,6 +5814,8 @@ union bpf_attr { FN(dynptr_trim, 215, ##ctx) \ FN(dynptr_is_null, 216, ##ctx) \ FN(dynptr_is_rdonly, 217, ##ctx) \ + FN(dynptr_get_size, 218, ##ctx) \ + FN(dynptr_get_offset, 219, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't From patchwork Wed Dec 7 20:55:35 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067664 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 80D04C4708D for ; Wed, 7 Dec 2022 21:17:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229522AbiLGVRs (ORCPT ); Wed, 7 Dec 2022 16:17:48 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52586 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229635AbiLGVRr (ORCPT ); Wed, 7 Dec 2022 16:17:47 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F1AD727FF3 for ; Wed, 7 Dec 2022 13:17:45 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id 14EFA135539B; Wed, 7 Dec 2022 12:56:48 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 4/6] bpf: Add bpf_dynptr_clone Date: Wed, 7 Dec 2022 12:55:35 -0800 Message-Id: <20221207205537.860248-5-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net Add a new helper, bpf_dynptr_clone, which clones a dynptr. The cloned dynptr will point to the same data as its parent dynptr, with the same type, offset, size and read-only properties. Any writes to a dynptr will be reflected across all instances (by 'instance', this means any dynptrs that point to the same underlying data). Please note that data slice and dynptr invalidations will affect all instances as well. For example, if bpf_dynptr_write() is called on an skb-type dynptr, all data slices of dynptr instances to that skb will be invalidated as well (eg data slices of any clones, parents, grandparents, ...). Another example is if a ringbuf dynptr is submitted, any instance of that dynptr will be invalidated. Changing the view of the dynptr (eg advancing the offset or trimming the size) will only affect that dynptr and not affect any other instances. One example use case where cloning may be helpful is for hashing or iterating through dynptr data. Cloning will allow the user to maintain the original view of the dynptr for future use, while also allowing views to smaller subsets of the data after the offset is advanced or the size is trimmed. Signed-off-by: Joanne Koong --- include/uapi/linux/bpf.h | 26 +++++++ kernel/bpf/helpers.c | 34 ++++++++ kernel/bpf/verifier.c | 138 +++++++++++++++++++++------------ tools/include/uapi/linux/bpf.h | 26 +++++++ 4 files changed, 173 insertions(+), 51 deletions(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 5ad52d481cde..f9387c5aba2b 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5594,6 +5594,31 @@ union bpf_attr { * Return * The offset of the dynptr on success, -EINVAL if the dynptr is * invalid. + * + * long bpf_dynptr_clone(struct bpf_dynptr *ptr, struct bpf_dynptr *clone, u32 offset) + * Description + * Clone an initialized dynptr *ptr*. After this call, both *ptr* + * and *clone* will point to the same underlying data. If non-zero, + * *offset* specifies how many bytes to advance the cloned dynptr by. + * + * *clone* must be an uninitialized dynptr. + * + * Any data slice or dynptr invalidations will apply equally for + * both dynptrs after this call. For example, if ptr1 is a + * ringbuf-type dynptr with multiple data slices that is cloned to + * ptr2, if ptr2 discards the ringbuf sample, then ptr2, ptr2's + * data slices, ptr1, and ptr1's data slices will all be + * invalidated. + * + * This is convenient for getting different "views" to the same + * data. For instance, if one wishes to hash only a particular + * section of data, one can clone the dynptr, advance it to a + * specified offset and trim it to a specified size, pass it + * to the hash function, and discard it after hashing, without + * losing access to the original view of the dynptr. + * Return + * 0 on success, -EINVAL if the dynptr to clone is invalid, -ERANGE + * if attempting to clone the dynptr at an out of range offset. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5816,6 +5841,7 @@ union bpf_attr { FN(dynptr_is_rdonly, 217, ##ctx) \ FN(dynptr_get_size, 218, ##ctx) \ FN(dynptr_get_offset, 219, ##ctx) \ + FN(dynptr_clone, 220, ##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 0164d7e4b5a6..0c2cfb4ed33c 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1760,6 +1760,38 @@ static const struct bpf_func_proto bpf_dynptr_get_offset_proto = { .arg1_type = ARG_PTR_TO_DYNPTR, }; +BPF_CALL_3(bpf_dynptr_clone, struct bpf_dynptr_kern *, ptr, + struct bpf_dynptr_kern *, clone, u32, offset) +{ + int err = -EINVAL; + + if (!ptr->data) + goto error; + + memcpy(clone, ptr, sizeof(*clone)); + + if (offset) { + err = bpf_dynptr_adjust(clone, offset, offset); + if (err) + goto error; + } + + return 0; + +error: + bpf_dynptr_set_null(clone); + return err; +} + +static const struct bpf_func_proto bpf_dynptr_clone_proto = { + .func = bpf_dynptr_clone, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, + .arg2_type = ARG_PTR_TO_DYNPTR | MEM_UNINIT, + .arg3_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; @@ -1876,6 +1908,8 @@ bpf_base_func_proto(enum bpf_func_id func_id) return &bpf_dynptr_get_size_proto; case BPF_FUNC_dynptr_get_offset: return &bpf_dynptr_get_offset_proto; + case BPF_FUNC_dynptr_clone: + return &bpf_dynptr_clone_proto; #ifdef CONFIG_CGROUPS case BPF_FUNC_cgrp_storage_get: return &bpf_cgrp_storage_get_proto; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 4d81d159254b..3f617f7040b7 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -719,17 +719,53 @@ static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type) } } +static bool arg_type_is_dynptr(enum bpf_arg_type type) +{ + return base_type(type) == ARG_PTR_TO_DYNPTR; +} + static bool dynptr_type_refcounted(enum bpf_dynptr_type type) { return type == BPF_DYNPTR_TYPE_RINGBUF; } -static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg, - enum bpf_arg_type arg_type, int insn_idx) +static struct bpf_reg_state *get_dynptr_arg_reg(const struct bpf_func_proto *fn, + struct bpf_reg_state *regs) +{ + enum bpf_arg_type t; + int i; + + for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) { + t = fn->arg_type[i]; + if (arg_type_is_dynptr(t) && !(t & MEM_UNINIT)) + return ®s[BPF_REG_1 + i]; + } + + return NULL; +} + +static enum bpf_dynptr_type stack_slot_get_dynptr_info(struct bpf_verifier_env *env, + struct bpf_reg_state *reg, + int *ref_obj_id) +{ + struct bpf_func_state *state = func(env, reg); + int spi = get_spi(reg->off); + + if (ref_obj_id) + *ref_obj_id = state->stack[spi].spilled_ptr.id; + + return state->stack[spi].spilled_ptr.dynptr.type; +} + +static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, + const struct bpf_func_proto *fn, + struct bpf_reg_state *reg, + enum bpf_arg_type arg_type, + int insn_idx, int func_id) { struct bpf_func_state *state = func(env, reg); enum bpf_dynptr_type type; - int spi, i, id; + int spi, i, id = 0; spi = get_spi(reg->off); @@ -741,7 +777,21 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_ state->stack[spi - 1].slot_type[i] = STACK_DYNPTR; } - type = arg_to_dynptr_type(arg_type); + if (func_id == BPF_FUNC_dynptr_clone) { + /* find the type and id of the dynptr we're cloning and + * assign it to the clone + */ + struct bpf_reg_state *parent_state = get_dynptr_arg_reg(fn, state->regs); + + if (!parent_state) { + verbose(env, "verifier internal error: no parent dynptr in bpf_dynptr_clone()\n"); + return -EFAULT; + } + type = stack_slot_get_dynptr_info(env, parent_state, &id); + } else { + type = arg_to_dynptr_type(arg_type); + } + if (type == BPF_DYNPTR_TYPE_INVALID) return -EINVAL; @@ -751,9 +801,11 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_ if (dynptr_type_refcounted(type)) { /* The id is used to track proper releasing */ - id = acquire_reference_state(env, insn_idx); - if (id < 0) - return id; + if (!id) { + id = acquire_reference_state(env, insn_idx); + if (id < 0) + return id; + } state->stack[spi].spilled_ptr.id = id; state->stack[spi - 1].spilled_ptr.id = id; @@ -762,6 +814,17 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_ return 0; } +static void invalidate_dynptr(struct bpf_func_state *state, int spi) +{ + int i; + + state->stack[spi].spilled_ptr.id = 0; + for (i = 0; i < BPF_REG_SIZE; i++) + state->stack[spi].slot_type[i] = STACK_INVALID; + state->stack[spi].spilled_ptr.dynptr.first_slot = false; + state->stack[spi].spilled_ptr.dynptr.type = 0; +} + static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg) { struct bpf_func_state *state = func(env, reg); @@ -772,22 +835,25 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS)) return -EINVAL; - for (i = 0; i < BPF_REG_SIZE; i++) { - state->stack[spi].slot_type[i] = STACK_INVALID; - state->stack[spi - 1].slot_type[i] = STACK_INVALID; - } - - /* Invalidate any slices associated with this dynptr */ if (dynptr_type_refcounted(state->stack[spi].spilled_ptr.dynptr.type)) { + int id = state->stack[spi].spilled_ptr.id; + + /* If the dynptr is refcounted, we need to invalidate two things: + * 1) any dynptrs with a matching id + * 2) any slices associated with the dynptr id + */ + release_reference(env, state->stack[spi].spilled_ptr.id); - state->stack[spi].spilled_ptr.id = 0; - state->stack[spi - 1].spilled_ptr.id = 0; + for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) { + if (state->stack[i].slot_type[0] == STACK_DYNPTR && + state->stack[i].spilled_ptr.id == id) + invalidate_dynptr(state, i); + } + } else { + invalidate_dynptr(state, spi); + invalidate_dynptr(state, spi - 1); } - state->stack[spi].spilled_ptr.dynptr.first_slot = false; - state->stack[spi].spilled_ptr.dynptr.type = 0; - state->stack[spi - 1].spilled_ptr.dynptr.type = 0; - return 0; } @@ -5862,11 +5928,6 @@ static bool arg_type_is_release(enum bpf_arg_type type) return type & OBJ_RELEASE; } -static bool arg_type_is_dynptr(enum bpf_arg_type type) -{ - return base_type(type) == ARG_PTR_TO_DYNPTR; -} - static int int_ptr_type_to_size(enum bpf_arg_type type) { if (type == ARG_PTR_TO_INT) @@ -6176,31 +6237,6 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env, return __check_ptr_off_reg(env, reg, regno, fixed_off_ok); } -static struct bpf_reg_state *get_dynptr_arg_reg(const struct bpf_func_proto *fn, - struct bpf_reg_state *regs) -{ - int i; - - for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) - if (arg_type_is_dynptr(fn->arg_type[i])) - return ®s[BPF_REG_1 + i]; - - return NULL; -} - -static enum bpf_dynptr_type stack_slot_get_dynptr_info(struct bpf_verifier_env *env, - struct bpf_reg_state *reg, - int *ref_obj_id) -{ - struct bpf_func_state *state = func(env, reg); - int spi = get_spi(reg->off); - - if (ref_obj_id) - *ref_obj_id = state->stack[spi].spilled_ptr.id; - - return state->stack[spi].spilled_ptr.dynptr.type; -} - static int check_func_arg(struct bpf_verifier_env *env, u32 arg, struct bpf_call_arg_meta *meta, const struct bpf_func_proto *fn) @@ -7697,9 +7733,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn return err; } - err = mark_stack_slots_dynptr(env, ®s[meta.uninit_dynptr_regno], + err = mark_stack_slots_dynptr(env, fn, ®s[meta.uninit_dynptr_regno], fn->arg_type[meta.uninit_dynptr_regno - BPF_REG_1], - insn_idx); + insn_idx, func_id); if (err) return err; } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 5ad52d481cde..f9387c5aba2b 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5594,6 +5594,31 @@ union bpf_attr { * Return * The offset of the dynptr on success, -EINVAL if the dynptr is * invalid. + * + * long bpf_dynptr_clone(struct bpf_dynptr *ptr, struct bpf_dynptr *clone, u32 offset) + * Description + * Clone an initialized dynptr *ptr*. After this call, both *ptr* + * and *clone* will point to the same underlying data. If non-zero, + * *offset* specifies how many bytes to advance the cloned dynptr by. + * + * *clone* must be an uninitialized dynptr. + * + * Any data slice or dynptr invalidations will apply equally for + * both dynptrs after this call. For example, if ptr1 is a + * ringbuf-type dynptr with multiple data slices that is cloned to + * ptr2, if ptr2 discards the ringbuf sample, then ptr2, ptr2's + * data slices, ptr1, and ptr1's data slices will all be + * invalidated. + * + * This is convenient for getting different "views" to the same + * data. For instance, if one wishes to hash only a particular + * section of data, one can clone the dynptr, advance it to a + * specified offset and trim it to a specified size, pass it + * to the hash function, and discard it after hashing, without + * losing access to the original view of the dynptr. + * Return + * 0 on success, -EINVAL if the dynptr to clone is invalid, -ERANGE + * if attempting to clone the dynptr at an out of range offset. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5816,6 +5841,7 @@ union bpf_attr { FN(dynptr_is_rdonly, 217, ##ctx) \ FN(dynptr_get_size, 218, ##ctx) \ FN(dynptr_get_offset, 219, ##ctx) \ + FN(dynptr_clone, 220, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't From patchwork Wed Dec 7 20:55:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067668 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6B5CAC4708D for ; Wed, 7 Dec 2022 21:25:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229772AbiLGVZn (ORCPT ); Wed, 7 Dec 2022 16:25:43 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55520 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229501AbiLGVZm (ORCPT ); Wed, 7 Dec 2022 16:25:42 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 18F3AB9C for ; Wed, 7 Dec 2022 13:25:41 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id D31B313553A4; Wed, 7 Dec 2022 12:56:48 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 5/6] bpf: Add bpf_dynptr_iterator Date: Wed, 7 Dec 2022 12:55:36 -0800 Message-Id: <20221207205537.860248-6-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net Add a new helper function, bpf_dynptr_iterator: long bpf_dynptr_iterator(struct bpf_dynptr *ptr, void *callback_fn, void *callback_ctx, u64 flags) where callback_fn is defined as: long (*callback_fn)(struct bpf_dynptr *ptr, void *ctx) and callback_fn returns the number of bytes to advance the dynptr by (or an error code in the case of error). The iteration will stop if the callback_fn returns 0 or an error or tries to advance by more bytes than available. Signed-off-by: Joanne Koong --- include/uapi/linux/bpf.h | 25 +++++++++ kernel/bpf/helpers.c | 42 +++++++++++++++ kernel/bpf/verifier.c | 93 ++++++++++++++++++++++++++++------ tools/include/uapi/linux/bpf.h | 25 +++++++++ 4 files changed, 170 insertions(+), 15 deletions(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index f9387c5aba2b..11c7e1e52f4d 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5619,6 +5619,30 @@ union bpf_attr { * Return * 0 on success, -EINVAL if the dynptr to clone is invalid, -ERANGE * if attempting to clone the dynptr at an out of range offset. + * + * long bpf_dynptr_iterator(struct bpf_dynptr *ptr, void *callback_fn, void *callback_ctx, u64 flags) + * Description + * Iterate through the dynptr data, calling **callback_fn** on each + * iteration with **callback_ctx** as the context parameter. + * The **callback_fn** should be a static function and + * the **callback_ctx** should be a pointer to the stack. + * Currently **flags** is unused and must be 0. + * + * int (\*callback_fn)(struct bpf_dynptr \*ptr, void \*ctx); + * + * where **callback_fn** returns the number of bytes to advance + * the callback dynptr by or an error. The iteration will stop if + * **callback_fn** returns 0 or an error or tries to advance by more + * bytes than the remaining size. + * + * Please note that **ptr** will remain untouched (eg offset and + * size will not be modified) though the data pointed to by **ptr** + * may have been modified. Please also note that you cannot release + * a dynptr within the callback function. + * Return + * 0 on success, -EINVAL if the dynptr is invalid or **flags** is not 0, + * -ERANGE if attempting to iterate more bytes than available, or other + * error code if **callback_fn** returns an error. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5842,6 +5866,7 @@ union bpf_attr { FN(dynptr_get_size, 218, ##ctx) \ FN(dynptr_get_offset, 219, ##ctx) \ FN(dynptr_clone, 220, ##ctx) \ + FN(dynptr_iterator, 221, ##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 0c2cfb4ed33c..0e612007601e 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1792,6 +1792,46 @@ static const struct bpf_func_proto bpf_dynptr_clone_proto = { .arg3_type = ARG_ANYTHING, }; +BPF_CALL_4(bpf_dynptr_iterator, struct bpf_dynptr_kern *, ptr, void *, callback_fn, + void *, callback_ctx, u64, flags) +{ + bpf_callback_t callback = (bpf_callback_t)callback_fn; + struct bpf_dynptr_kern ptr_copy; + int nr_bytes, err; + + if (flags) + return -EINVAL; + + err = ____bpf_dynptr_clone(ptr, &ptr_copy, 0); + if (err) + return err; + + while (ptr_copy.size > 0) { + nr_bytes = callback((uintptr_t)&ptr_copy, (uintptr_t)callback_ctx, 0, 0, 0); + if (nr_bytes <= 0) + return nr_bytes; + + if (nr_bytes > U32_MAX) + return -ERANGE; + + err = bpf_dynptr_adjust(&ptr_copy, nr_bytes, nr_bytes); + if (err) + return err; + } + + return 0; +} + +static const struct bpf_func_proto bpf_dynptr_iterator_proto = { + .func = bpf_dynptr_iterator, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_DYNPTR, + .arg2_type = ARG_PTR_TO_FUNC, + .arg3_type = ARG_PTR_TO_STACK_OR_NULL, + .arg4_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; @@ -1910,6 +1950,8 @@ bpf_base_func_proto(enum bpf_func_id func_id) return &bpf_dynptr_get_offset_proto; case BPF_FUNC_dynptr_clone: return &bpf_dynptr_clone_proto; + case BPF_FUNC_dynptr_iterator: + return &bpf_dynptr_iterator_proto; #ifdef CONFIG_CGROUPS case BPF_FUNC_cgrp_storage_get: return &bpf_cgrp_storage_get_proto; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 3f617f7040b7..8abdc392a48e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -524,7 +524,8 @@ static bool is_callback_calling_function(enum bpf_func_id func_id) func_id == BPF_FUNC_timer_set_callback || func_id == BPF_FUNC_find_vma || func_id == BPF_FUNC_loop || - func_id == BPF_FUNC_user_ringbuf_drain; + func_id == BPF_FUNC_user_ringbuf_drain || + func_id == BPF_FUNC_dynptr_iterator; } static bool is_storage_get_function(enum bpf_func_id func_id) @@ -703,6 +704,19 @@ static void mark_verifier_state_scratched(struct bpf_verifier_env *env) env->scratched_stack_slots = ~0ULL; } +static enum bpf_dynptr_type stack_slot_get_dynptr_info(struct bpf_verifier_env *env, + struct bpf_reg_state *reg, + int *ref_obj_id) +{ + struct bpf_func_state *state = func(env, reg); + int spi = get_spi(reg->off); + + if (ref_obj_id) + *ref_obj_id = state->stack[spi].spilled_ptr.id; + + return state->stack[spi].spilled_ptr.dynptr.type; +} + static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type) { switch (arg_type & DYNPTR_TYPE_FLAG_MASK) { @@ -719,6 +733,25 @@ static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type) } } +static enum bpf_type_flag dynptr_flag_type(struct bpf_verifier_env *env, + struct bpf_reg_state *state) +{ + enum bpf_dynptr_type type = stack_slot_get_dynptr_info(env, state, NULL); + + switch (type) { + case BPF_DYNPTR_TYPE_LOCAL: + return DYNPTR_TYPE_LOCAL; + case BPF_DYNPTR_TYPE_RINGBUF: + return DYNPTR_TYPE_RINGBUF; + case BPF_DYNPTR_TYPE_SKB: + return DYNPTR_TYPE_SKB; + case BPF_DYNPTR_TYPE_XDP: + return DYNPTR_TYPE_XDP; + default: + return 0; + } +} + static bool arg_type_is_dynptr(enum bpf_arg_type type) { return base_type(type) == ARG_PTR_TO_DYNPTR; @@ -744,19 +777,6 @@ static struct bpf_reg_state *get_dynptr_arg_reg(const struct bpf_func_proto *fn, return NULL; } -static enum bpf_dynptr_type stack_slot_get_dynptr_info(struct bpf_verifier_env *env, - struct bpf_reg_state *reg, - int *ref_obj_id) -{ - struct bpf_func_state *state = func(env, reg); - int spi = get_spi(reg->off); - - if (ref_obj_id) - *ref_obj_id = state->stack[spi].spilled_ptr.id; - - return state->stack[spi].spilled_ptr.dynptr.type; -} - static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, const struct bpf_func_proto *fn, struct bpf_reg_state *reg, @@ -6053,6 +6073,9 @@ static const struct bpf_reg_types dynptr_types = { .types = { PTR_TO_STACK, PTR_TO_DYNPTR | DYNPTR_TYPE_LOCAL, + PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF, + PTR_TO_DYNPTR | DYNPTR_TYPE_SKB, + PTR_TO_DYNPTR | DYNPTR_TYPE_XDP, } }; @@ -6440,8 +6463,13 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, * assumption is that if it is, that a helper function * initialized the dynptr on behalf of the BPF program. */ - if (base_type(reg->type) == PTR_TO_DYNPTR) + if (base_type(reg->type) == PTR_TO_DYNPTR) { + if (arg_type & MEM_UNINIT) { + verbose(env, "PTR_TO_DYNPTR is already an initialized dynptr\n"); + return -EINVAL; + } break; + } if (arg_type & MEM_UNINIT) { if (!is_dynptr_reg_valid_uninit(env, reg)) { verbose(env, "Dynptr has to be an uninitialized dynptr\n"); @@ -7342,6 +7370,37 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env, return 0; } +static int set_dynptr_iterator_callback_state(struct bpf_verifier_env *env, + struct bpf_func_state *caller, + struct bpf_func_state *callee, + int insn_idx) +{ + /* bpf_dynptr_iterator(struct bpf_dynptr *ptr, void *callback_fn, + * void *callback_ctx, u64 flags); + * + * callback_fn(struct bpf_dynptr *ptr, void *callback_ctx); + */ + + enum bpf_type_flag dynptr_flag = + dynptr_flag_type(env, &caller->regs[BPF_REG_1]); + + if (dynptr_flag == 0) + return -EFAULT; + + callee->regs[BPF_REG_1].type = PTR_TO_DYNPTR | dynptr_flag; + __mark_reg_known_zero(&callee->regs[BPF_REG_1]); + callee->regs[BPF_REG_2] = caller->regs[BPF_REG_3]; + callee->callback_ret_range = tnum_range(0, U32_MAX); + + /* unused */ + __mark_reg_not_init(env, &callee->regs[BPF_REG_3]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_4]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_5]); + + callee->in_callback_fn = true; + return 0; +} + static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) { struct bpf_verifier_state *state = env->cur_state; @@ -7857,6 +7916,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn err = __check_func_call(env, insn, insn_idx_p, meta.subprogno, set_user_ringbuf_callback_state); break; + case BPF_FUNC_dynptr_iterator: + err = __check_func_call(env, insn, insn_idx_p, meta.subprogno, + set_dynptr_iterator_callback_state); + break; } if (err) diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index f9387c5aba2b..11c7e1e52f4d 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5619,6 +5619,30 @@ union bpf_attr { * Return * 0 on success, -EINVAL if the dynptr to clone is invalid, -ERANGE * if attempting to clone the dynptr at an out of range offset. + * + * long bpf_dynptr_iterator(struct bpf_dynptr *ptr, void *callback_fn, void *callback_ctx, u64 flags) + * Description + * Iterate through the dynptr data, calling **callback_fn** on each + * iteration with **callback_ctx** as the context parameter. + * The **callback_fn** should be a static function and + * the **callback_ctx** should be a pointer to the stack. + * Currently **flags** is unused and must be 0. + * + * int (\*callback_fn)(struct bpf_dynptr \*ptr, void \*ctx); + * + * where **callback_fn** returns the number of bytes to advance + * the callback dynptr by or an error. The iteration will stop if + * **callback_fn** returns 0 or an error or tries to advance by more + * bytes than the remaining size. + * + * Please note that **ptr** will remain untouched (eg offset and + * size will not be modified) though the data pointed to by **ptr** + * may have been modified. Please also note that you cannot release + * a dynptr within the callback function. + * Return + * 0 on success, -EINVAL if the dynptr is invalid or **flags** is not 0, + * -ERANGE if attempting to iterate more bytes than available, or other + * error code if **callback_fn** returns an error. */ #define ___BPF_FUNC_MAPPER(FN, ctx...) \ FN(unspec, 0, ##ctx) \ @@ -5842,6 +5866,7 @@ union bpf_attr { FN(dynptr_get_size, 218, ##ctx) \ FN(dynptr_get_offset, 219, ##ctx) \ FN(dynptr_clone, 220, ##ctx) \ + FN(dynptr_iterator, 221, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't From patchwork Wed Dec 7 20:55:37 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 13067665 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3753FC4708D for ; Wed, 7 Dec 2022 21:17:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229628AbiLGVRy (ORCPT ); Wed, 7 Dec 2022 16:17:54 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52780 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229652AbiLGVRx (ORCPT ); Wed, 7 Dec 2022 16:17:53 -0500 Received: from 66-220-144-178.mail-mxout.facebook.com (66-220-144-178.mail-mxout.facebook.com [66.220.144.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 97C2C30564 for ; Wed, 7 Dec 2022 13:17:51 -0800 (PST) Received: by devvm15675.prn0.facebook.com (Postfix, from userid 115148) id C6AB213553AF; Wed, 7 Dec 2022 12:56:49 -0800 (PST) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, kernel-team@meta.com, ast@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev, song@kernel.org, Joanne Koong Subject: [PATCH v2 bpf-next 6/6] selftests/bpf: Tests for dynptr convenience helpers Date: Wed, 7 Dec 2022 12:55:37 -0800 Message-Id: <20221207205537.860248-7-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20221207205537.860248-1-joannelkoong@gmail.com> References: <20221207205537.860248-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net Test dynptr convenience helpers in the following way: 1) bpf_dynptr_trim and bpf_dynptr_advance * "test_advance_trim" tests that dynptr offset and size get adjusted correctly. * "test_advance_trim_err" tests that advances beyond dynptr size and trims larger than dynptr size are rejected. * "test_zero_size_dynptr" tests that a zero-size dynptr (after advancing or trimming) can only read and write 0 bytes. 2) bpf_dynptr_is_null * "dynptr_is_null_invalid" tests that only initialized dynptrs can be passed in. * "test_dynptr_is_null" tests that null dynptrs return true and non-null dynptrs return false. 3) bpf_dynptr_is_rdonly * "dynptr_is_rdonly_invalid" tests that only initialized dynptrs can be passed in. * "test_dynptr_is_rdonly" tests that rdonly dynptrs return true and non-rdonly or invalid dynptrs return false. 4) bpf_dynptr_get_size * "dynptr_get_size_invalid" tests that only initialized dynptrs can be passed in. * Additional functionality is tested as a by-product in "test_advance_trim" 5) bpf_dynptr_get_offset * "dynptr_get_offset_invalid" tests that only initialized dynptrs can be passed in. * Additional functionality is tested as a by-product in "test_advance_trim" 6) bpf_dynptr_clone * "clone_invalidate_{1..6}" tests that invalidating a dynptr invalidates all instances and invalidating a dynptr's data slices invalidates all data slices for all instances. * "clone_skb_packet_data" tests that data slices of skb dynptr instances are invalidated when packet data changes. * "clone_xdp_packet_data" tests that data slices of xdp dynptr instances are invalidated when packet data changes. * "clone_invalid1" tests that only initialized dynptrs can be cloned. * "clone_invalid2" tests that only uninitialized dynptrs can be a clone. * "test_dynptr_clone" tests that the views from the same dynptr instances are independent (advancing or trimming a dynptr doesn't affect other instances), and that a clone will return a dynptr with the same type, offset, size, and rd-only property. * "test_dynptr_clone_offset" tests cloning at invalid offsets and at valid offsets. 7) bpf_dynptr_iterator * "iterator_invalid1" tests that any dynptr requiring a release that gets acquired in an iterator callback must also be released within the callback * "iterator_invalid2" tests that bpf_dynptr_iterator can't be called on an uninitialized dynptr * "iterator_invalid3" tests that the initialized dynptr can't be initialized again in the iterator callback function * "iterator_invalid4" tests that the dynptr in the iterator callback function can't be released * "iterator_invalid5" tests that the dynptr passed as a callback ctx can't be released within the callback * "iterator_invalid6" tests that the dynptr can't be modified within the iterator callback * "iterator_invalid7" tests that the callback function can't return a value larger than an int * "test_dynptr_iterator" tests basic functionality of the iterator * "iterator_parse_strings" tests parsing strings as values Signed-off-by: Joanne Koong --- .../testing/selftests/bpf/prog_tests/dynptr.c | 31 + .../testing/selftests/bpf/progs/dynptr_fail.c | 439 ++++++++++++++ .../selftests/bpf/progs/dynptr_success.c | 534 +++++++++++++++++- 3 files changed, 1002 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index 3c55721f8f6d..8052aded2261 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -57,12 +57,43 @@ static struct { {"skb_invalid_ctx", "unknown func bpf_dynptr_from_skb"}, {"xdp_invalid_ctx", "unknown func bpf_dynptr_from_xdp"}, {"skb_invalid_write", "cannot write into rdonly_mem"}, + {"dynptr_is_null_invalid", "Expected an initialized dynptr as arg #1"}, + {"dynptr_is_rdonly_invalid", "Expected an initialized dynptr as arg #1"}, + {"dynptr_get_size_invalid", "Expected an initialized dynptr as arg #1"}, + {"dynptr_get_offset_invalid", "Expected an initialized dynptr as arg #1"}, + {"clone_invalid1", "Expected an initialized dynptr as arg #1"}, + {"clone_invalid2", "Dynptr has to be an uninitialized dynptr"}, + {"clone_invalidate1", "Expected an initialized dynptr"}, + {"clone_invalidate2", "Expected an initialized dynptr"}, + {"clone_invalidate3", "Expected an initialized dynptr"}, + {"clone_invalidate4", "invalid mem access 'scalar'"}, + {"clone_invalidate5", "invalid mem access 'scalar'"}, + {"clone_invalidate6", "invalid mem access 'scalar'"}, + {"clone_skb_packet_data", "invalid mem access 'scalar'"}, + {"clone_xdp_packet_data", "invalid mem access 'scalar'"}, + {"iterator_invalid1", "Unreleased reference id=1"}, + {"iterator_invalid2", "Expected an initialized dynptr as arg #1"}, + {"iterator_invalid3", "PTR_TO_DYNPTR is already an initialized dynptr"}, + {"iterator_invalid4", "arg 1 is an unacquired reference"}, + {"iterator_invalid5", "Unreleased reference"}, + {"iterator_invalid6", "invalid mem access 'dynptr_ptr'"}, + {"iterator_invalid7", + "At callback return the register R0 has value (0x100000000; 0x0)"}, /* these tests should be run and should succeed */ {"test_read_write", NULL, SETUP_SYSCALL_SLEEP}, {"test_data_slice", NULL, SETUP_SYSCALL_SLEEP}, {"test_ringbuf", NULL, SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", NULL, SETUP_SKB_PROG}, + {"test_advance_trim", NULL, SETUP_SYSCALL_SLEEP}, + {"test_advance_trim_err", NULL, SETUP_SYSCALL_SLEEP}, + {"test_zero_size_dynptr", NULL, SETUP_SYSCALL_SLEEP}, + {"test_dynptr_is_null", NULL, SETUP_SYSCALL_SLEEP}, + {"test_dynptr_is_rdonly", NULL, SETUP_SKB_PROG}, + {"test_dynptr_clone", NULL, SETUP_SKB_PROG}, + {"test_dynptr_clone_offset", NULL, SETUP_SKB_PROG}, + {"test_dynptr_iterator", NULL, SETUP_SKB_PROG}, + {"iterator_parse_strings", NULL, SETUP_SYSCALL_SLEEP}, }; static void verify_fail(const char *prog_name, const char *expected_err_msg) diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index fe9b668b4999..2e91642ded16 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -733,3 +733,442 @@ int skb_invalid_write(struct __sk_buff *skb) return 0; } + +/* dynptr_is_null can only be called on initialized dynptrs */ +SEC("?raw_tp") +int dynptr_is_null_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_is_null(&ptr); + + return 0; +} + +/* dynptr_is_rdonly can only be called on initialized dynptrs */ +SEC("?raw_tp") +int dynptr_is_rdonly_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_is_rdonly(&ptr); + + return 0; +} + +/* dynptr_get_size can only be called on initialized dynptrs */ +SEC("?raw_tp") +int dynptr_get_size_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_get_size(&ptr); + + return 0; +} + +/* dynptr_get_offset can only be called on initialized dynptrs */ +SEC("?raw_tp") +int dynptr_get_offset_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_get_offset(&ptr); + + return 0; +} + +/* Only initialized dynptrs can be cloned */ +SEC("?raw_tp") +int clone_invalid1(void *ctx) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + + /* this should fail */ + bpf_dynptr_clone(&ptr1, &ptr2, 0); + + return 0; +} + +/* Only uninitialized dynptrs can be clones */ +SEC("?xdp") +int clone_invalid2(struct xdp_md *xdp) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr clone; + + bpf_dynptr_from_xdp(xdp, 0, &ptr1); + + bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &clone); + + /* this should fail */ + bpf_dynptr_clone(&ptr1, &clone, 0); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its clones */ +SEC("?raw_tp") +int clone_invalidate1(void *ctx) +{ + struct bpf_dynptr clone; + struct bpf_dynptr ptr; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone, 0); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &clone, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its parent */ +SEC("?raw_tp") +int clone_invalidate2(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone, 0); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its siblings */ +SEC("?raw_tp") +int clone_invalidate3(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone1; + struct bpf_dynptr clone2; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone1, 0); + + bpf_dynptr_clone(&ptr, &clone2, 0); + + bpf_ringbuf_submit_dynptr(&clone2, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &clone1, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its clones + */ +SEC("?raw_tp") +int clone_invalidate4(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone, 0); + data = bpf_dynptr_data(&clone, 0, sizeof(val)); + if (!data) + return 0; + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its parent + */ +SEC("?raw_tp") +int clone_invalidate5(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + data = bpf_dynptr_data(&ptr, 0, sizeof(val)); + if (!data) + return 0; + + bpf_dynptr_clone(&ptr, &clone, 0); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its sibling + */ +SEC("?raw_tp") +int clone_invalidate6(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone1; + struct bpf_dynptr clone2; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone1, 0); + + bpf_dynptr_clone(&ptr, &clone2, 0); + + data = bpf_dynptr_data(&clone1, 0, sizeof(val)); + if (!data) + return 0; + + bpf_ringbuf_submit_dynptr(&clone2, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* A skb clone's data slices should be invalid anytime packet data changes */ +SEC("?tc") +int clone_skb_packet_data(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + __u32 *data; + + bpf_dynptr_from_skb(skb, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone, 0); + data = bpf_dynptr_data(&clone, 0, sizeof(*data)); + if (!data) + return XDP_DROP; + + if (bpf_skb_pull_data(skb, skb->len)) + return SK_DROP; + + /* this should fail */ + *data = 123; + + return 0; +} + +/* A xdp clone's data slices should be invalid anytime packet data changes */ +SEC("?xdp") +int clone_xdp_packet_data(struct xdp_md *xdp) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + struct ethhdr *hdr; + __u32 *data; + + bpf_dynptr_from_xdp(xdp, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone, 0); + data = bpf_dynptr_data(&clone, 0, sizeof(*data)); + if (!data) + return XDP_DROP; + + if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr))) + return XDP_DROP; + + /* this should fail */ + *data = 123; + + return 0; +} + +static int iterator_callback1(struct bpf_dynptr *ptr, void *ctx) +{ + struct bpf_dynptr local_ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &local_ptr); + + /* missing a call to bpf_ringbuf_discard/submit_dynptr */ + + return 0; +} + +/* If a dynptr requiring a release is initialized within the iterator callback + * function, then it must also be released within that function + */ +SEC("?xdp") +int iterator_invalid1(struct xdp_md *xdp) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_from_xdp(xdp, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback1, NULL, 0); + + return 0; +} + +/* bpf_dynptr_iterator can't be called on an uninitialized dynptr */ +SEC("?xdp") +int iterator_invalid2(struct xdp_md *xdp) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_iterator(&ptr, iterator_callback1, NULL, 0); + + return 0; +} + +static int iterator_callback3(struct bpf_dynptr *ptr, void *ctx) +{ + /* this should fail */ + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, ptr); + + bpf_ringbuf_submit_dynptr(ptr, 0); + + return 1; +} + +/* The dynptr callback ctx can't be re-initialized as a separate dynptr + * within the callback function + */ +SEC("?raw_tp") +int iterator_invalid3(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback3, NULL, 0); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + return 0; +} + +static int iterator_callback4(struct bpf_dynptr *ptr, void *ctx) +{ + char write_data[64] = "hello there, world!!"; + + bpf_dynptr_write(ptr, 0, write_data, sizeof(write_data), 0); + + /* this should fail */ + bpf_ringbuf_submit_dynptr(ptr, 0); + + return 0; +} + +/* The dynptr can't be released within the iterator callback */ +SEC("?raw_tp") +int iterator_invalid4(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback4, NULL, 0); + + return 0; +} + +static int iterator_callback5(struct bpf_dynptr *ptr, void *ctx) +{ + bpf_ringbuf_submit_dynptr(ctx, 0); + + return 0; +} + +/* If a dynptr is passed in as the callback ctx, the dynptr + * can't be released. + * + * Currently, the verifier doesn't strictly check for this since + * it only runs the callback once when verifying. For now, we + * use the fact that the verifier doesn't mark the reference in + * the parent func state as released if it's released in the + * callback. This is what we currently lean on in bpf_loop() as + * well. This is a bit of a hack for now, and will need to be + * addressed more thoroughly in the future. + */ +SEC("?raw_tp") +int iterator_invalid5(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback5, &ptr, 0); + + return 0; +} + +static int iterator_callback6(struct bpf_dynptr *ptr, void *ctx) +{ + char write_data[64] = "hello there, world!!"; + + bpf_dynptr_write(ptr, 0, write_data, sizeof(write_data), 0); + + /* this should fail */ + *(int *)ptr = 12; + + return 1; +} + +/* The dynptr struct can't be modified in the iterator callback */ +SEC("?raw_tp") +int iterator_invalid6(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback6, NULL, 0); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + return 0; +} + +static __u64 iterator_callback7(struct bpf_dynptr *ptr, void *ctx) +{ + /* callback should return an int */ + return 1UL << 32; +} + +/* The callback should return an int */ +SEC("?raw_tp") +int iterator_invalid7(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_iterator(&ptr, iterator_callback7, NULL, 0); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index 349def97f50a..e8866e662b06 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022 Facebook */ +#include #include #include -#include #include "bpf_misc.h" -#include "errno.h" +#include char _license[] SEC("license") = "GPL"; @@ -29,6 +29,13 @@ struct { __type(value, __u32); } array_map SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __uint(value_size, 64); +} array_map2 SEC(".maps"); + SEC("tp/syscalls/sys_enter_nanosleep") int test_read_write(void *ctx) { @@ -185,3 +192,526 @@ int test_skb_readonly(struct __sk_buff *skb) return 0; } + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_advance_trim(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 bytes = 64; + __u32 off = 10; + __u32 trim = 5; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, bytes, 0, &ptr); + if (err) { + err = 1; + goto done; + } + + if (bpf_dynptr_get_size(&ptr) != bytes) { + err = 2; + goto done; + } + + /* Advance the dynptr by off */ + err = bpf_dynptr_advance(&ptr, off); + if (err) { + err = 3; + goto done; + } + + /* Check that the dynptr off and size were adjusted correctly */ + if (bpf_dynptr_get_offset(&ptr) != off) { + err = 4; + goto done; + } + if (bpf_dynptr_get_size(&ptr) != bytes - off) { + err = 5; + goto done; + } + + /* Trim the dynptr */ + err = bpf_dynptr_trim(&ptr, trim); + if (err) { + err = 6; + goto done; + } + + /* Check that the off was unaffected */ + if (bpf_dynptr_get_offset(&ptr) != off) { + err = 7; + goto done; + } + /* Check that the size was adjusted correctly */ + if (bpf_dynptr_get_size(&ptr) != bytes - off - trim) { + err = 8; + goto done; + } + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_advance_trim_err(void *ctx) +{ + char write_data[45] = "hello there, world!!"; + struct bpf_dynptr ptr; + __u32 trim_size = 10; + __u32 size = 64; + __u32 off = 10; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr)) { + err = 1; + goto done; + } + + /* Check that you can't advance beyond size of dynptr data */ + if (bpf_dynptr_advance(&ptr, size + 1) != -ERANGE) { + err = 2; + goto done; + } + + if (bpf_dynptr_advance(&ptr, off)) { + err = 3; + goto done; + } + + /* Check that you can't trim more than size of dynptr data */ + if (bpf_dynptr_trim(&ptr, size - off + 1) != -ERANGE) { + err = 4; + goto done; + } + + /* Check that you can't write more bytes than available into the dynptr + * after you've trimmed it + */ + if (bpf_dynptr_trim(&ptr, trim_size)) { + err = 5; + goto done; + } + + if (bpf_dynptr_write(&ptr, 0, &write_data, sizeof(write_data), 0) != -E2BIG) { + err = 6; + goto done; + } + + /* Check that even after advancing / trimming, submitting/discarding + * a ringbuf dynptr works + */ + bpf_ringbuf_submit_dynptr(&ptr, 0); + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_zero_size_dynptr(void *ctx) +{ + char write_data = 'x', read_data; + struct bpf_dynptr ptr; + __u32 size = 64; + __u32 off = 10; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + /* check that you can reserve a dynamic size reservation */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr)) { + err = 1; + goto done; + } + + /* After this, the dynptr has a size of 0 */ + if (bpf_dynptr_advance(&ptr, size)) { + err = 2; + goto done; + } + + /* Test that reading + writing non-zero bytes is not ok */ + if (bpf_dynptr_read(&read_data, sizeof(read_data), &ptr, 0, 0) != -E2BIG) { + err = 3; + goto done; + } + + if (bpf_dynptr_write(&ptr, 0, &write_data, sizeof(write_data), 0) != -E2BIG) { + err = 4; + goto done; + } + + /* Test that reading + writing 0 bytes from a 0-size dynptr is ok */ + if (bpf_dynptr_read(&read_data, 0, &ptr, 0, 0)) { + err = 5; + goto done; + } + + if (bpf_dynptr_write(&ptr, 0, &write_data, 0, 0)) { + err = 6; + goto done; + } + + err = 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_dynptr_is_null(void *ctx) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + __u64 size = 4; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + /* Pass in invalid flags, get back an invalid dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 123, &ptr1) != -EINVAL) { + err = 1; + goto exit_early; + } + + /* Test that the invalid dynptr is null */ + if (!bpf_dynptr_is_null(&ptr1)) { + err = 2; + goto exit_early; + } + + /* Get a valid dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr2)) { + err = 3; + goto exit; + } + + /* Test that the valid dynptr is not null */ + if (bpf_dynptr_is_null(&ptr2)) { + err = 4; + goto exit; + } + +exit: + bpf_ringbuf_discard_dynptr(&ptr2, 0); +exit_early: + bpf_ringbuf_discard_dynptr(&ptr1, 0); + return 0; +} + +SEC("cgroup_skb/egress") +int test_dynptr_is_rdonly(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + struct bpf_dynptr ptr3; + + /* Pass in invalid flags, get back an invalid dynptr */ + if (bpf_dynptr_from_skb(skb, 123, &ptr1) != -EINVAL) { + err = 1; + return 0; + } + + /* Test that an invalid dynptr is_rdonly returns false */ + if (bpf_dynptr_is_rdonly(&ptr1)) { + err = 2; + return 0; + } + + /* Get a read-only dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr2)) { + err = 3; + return 0; + } + + /* Test that the dynptr is read-only */ + if (!bpf_dynptr_is_rdonly(&ptr2)) { + err = 4; + return 0; + } + + /* Get a read-writeable dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr3)) { + err = 5; + goto done; + } + + /* Test that the dynptr is read-only */ + if (bpf_dynptr_is_rdonly(&ptr3)) { + err = 6; + goto done; + } + +done: + bpf_ringbuf_discard_dynptr(&ptr3, 0); + return 0; +} + +SEC("cgroup_skb/egress") +int test_dynptr_clone(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + __u32 off = 2, size; + + /* Get a dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr1)) { + err = 1; + return 0; + } + + if (bpf_dynptr_advance(&ptr1, off)) { + err = 2; + return 0; + } + + /* Clone the dynptr */ + if (bpf_dynptr_clone(&ptr1, &ptr2, 0)) { + err = 3; + return 0; + } + + size = bpf_dynptr_get_size(&ptr1); + + /* Check that the clone has the same offset, size, and rd-only */ + if (bpf_dynptr_get_size(&ptr2) != size) { + err = 4; + return 0; + } + + if (bpf_dynptr_get_offset(&ptr2) != off) { + err = 5; + return 0; + } + + if (bpf_dynptr_is_rdonly(&ptr2) != bpf_dynptr_is_rdonly(&ptr1)) { + err = 6; + return 0; + } + + /* Advance and trim the original dynptr */ + bpf_dynptr_advance(&ptr1, 50); + bpf_dynptr_trim(&ptr1, 50); + + /* Check that only original dynptr was affected, and the clone wasn't */ + if (bpf_dynptr_get_offset(&ptr2) != off) { + err = 7; + return 0; + } + + if (bpf_dynptr_get_size(&ptr2) != size) { + err = 8; + return 0; + } + + return 0; +} + +SEC("cgroup_skb/egress") +int test_dynptr_clone_offset(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + struct bpf_dynptr ptr3; + __u32 off = 2, size; + + /* Get a dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr1)) { + err = 1; + return 0; + } + + if (bpf_dynptr_advance(&ptr1, off)) { + err = 2; + return 0; + } + + size = bpf_dynptr_get_size(&ptr1); + + /* Clone the dynptr at an invalid offset */ + if (bpf_dynptr_clone(&ptr1, &ptr2, size + 1) != -ERANGE) { + err = 3; + return 0; + } + + /* Clone the dynptr at valid offset */ + if (bpf_dynptr_clone(&ptr1, &ptr3, off)) { + err = 4; + return 0; + } + + if (bpf_dynptr_get_size(&ptr3) != size - off) { + err = 5; + return 0; + } + + return 0; +} + +static int iter_callback1(struct bpf_dynptr *ptr, void *ctx) +{ + return bpf_dynptr_get_size(ptr) + 1; +} + +static int iter_callback2(struct bpf_dynptr *ptr, void *ctx) +{ + return -EFAULT; +} + +SEC("cgroup_skb/egress") +int test_dynptr_iterator(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + __u32 off = 1, size; + /* Get a dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 0; + } + + if (bpf_dynptr_advance(&ptr, off)) { + err = 2; + return 0; + } + + size = bpf_dynptr_get_size(&ptr); + + /* Test the case where the callback tries to advance by more + * bytes than available + */ + if (bpf_dynptr_iterator(&ptr, iter_callback1, NULL, 0) != -ERANGE) { + err = 3; + return 0; + } + if (bpf_dynptr_get_size(&ptr) != size) { + err = 4; + return 0; + } + if (bpf_dynptr_get_offset(&ptr) != off) { + err = 5; + return 0; + } + + /* Test the case where the callback returns an error code */ + if (bpf_dynptr_iterator(&ptr, iter_callback2, NULL, 0) != -EFAULT) { + err = 6; + return 0; + } + + return 0; +} + +static char values[3][64] = {}; + +#define MAX_STRINGS_LEN 10 +static int parse_strings_callback(struct bpf_dynptr *ptr, int *nr_values) +{ + __u32 size = bpf_dynptr_get_size(ptr); + char buf[MAX_STRINGS_LEN] = {}; + char *data; + int i, j, k; + int err; + + if (size < MAX_STRINGS_LEN) { + err = bpf_dynptr_read(buf, size, ptr, 0, 0); + if (err) + return err; + data = buf; + } else { + data = bpf_dynptr_data(ptr, 0, MAX_STRINGS_LEN); + if (!data) + return -ENOENT; + size = MAX_STRINGS_LEN; + } + + for (i = 0; i < size; i++) { + if (data[i] != '=') + continue; + + for (j = i; j < size - i; j++) { + int index = 0; + + if (data[j] != '/') + continue; + + for (k = i + 1; k < j; k++) { + values[*nr_values][index] = data[k]; + index += 1; + } + + *nr_values += 1; + return j; + } + + return -ENOENT; + } + + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int iterator_parse_strings(void *ctx) +{ + char val[64] = "x=foo/y=bar/z=baz/"; + struct bpf_dynptr ptr; + __u32 map_val_size; + int nr_values = 0; + __u32 key = 0; + char *map_val; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + map_val_size = sizeof(val); + + if (bpf_map_update_elem(&array_map2, &key, &val, 0)) { + err = 1; + return 0; + } + + map_val = bpf_map_lookup_elem(&array_map2, &key); + if (!map_val) { + err = 2; + return 0; + } + + if (bpf_dynptr_from_mem(map_val, map_val_size, 0, &ptr)) { + err = 3; + return 0; + } + + if (bpf_dynptr_iterator(&ptr, parse_strings_callback, + &nr_values, 0)) { + err = 4; + return 0; + } + + if (nr_values != 3) { + err = 8; + return 0; + } + + if (memcmp(values[0], "foo", sizeof("foo"))) { + err = 5; + return 0; + } + + if (memcmp(values[1], "bar", sizeof("bar"))) { + err = 6; + return 0; + } + + if (memcmp(values[2], "baz", sizeof("baz"))) { + err = 7; + return 0; + } + + return 0; +}