diff mbox series

[bpf-next,v5,2/6] bpf: Add a ARG_PTR_TO_CONST_STR argument type

Message ID 20210419155243.1632274-3-revest@chromium.org (mailing list archive)
State Accepted
Delegated to: BPF
Headers show
Series Add a snprintf eBPF helper | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for bpf-next
netdev/subject_prefix success Link
netdev/cc_maintainers warning 4 maintainers not CCed: netdev@vger.kernel.org john.fastabend@gmail.com kafai@fb.com songliubraving@fb.com
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 11892 this patch: 11892
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch warning WARNING: line length of 83 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 90 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns
netdev/build_allmodconfig_warn success Errors and warnings before: 12379 this patch: 12379
netdev/header_inline success Link

Commit Message

Florent Revest April 19, 2021, 3:52 p.m. UTC
This type provides the guarantee that an argument is going to be a const
pointer to somewhere in a read-only map value. It also checks that this
pointer is followed by a zero character before the end of the map value.

Signed-off-by: Florent Revest <revest@chromium.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
---
 include/linux/bpf.h   |  1 +
 kernel/bpf/verifier.c | 41 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

Comments

Alexei Starovoitov April 19, 2021, 10:54 p.m. UTC | #1
On Mon, Apr 19, 2021 at 05:52:39PM +0200, Florent Revest wrote:
> This type provides the guarantee that an argument is going to be a const
> pointer to somewhere in a read-only map value. It also checks that this
> pointer is followed by a zero character before the end of the map value.
> 
> Signed-off-by: Florent Revest <revest@chromium.org>
> Acked-by: Andrii Nakryiko <andrii@kernel.org>
> ---
>  include/linux/bpf.h   |  1 +
>  kernel/bpf/verifier.c | 41 +++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 42 insertions(+)
> 
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 77d1d8c65b81..c160526fc8bf 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -309,6 +309,7 @@ enum bpf_arg_type {
>  	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
>  	ARG_PTR_TO_FUNC,	/* pointer to a bpf program function */
>  	ARG_PTR_TO_STACK_OR_NULL,	/* pointer to stack or NULL */
> +	ARG_PTR_TO_CONST_STR,	/* pointer to a null terminated read-only string */
>  	__BPF_ARG_TYPE_MAX,
>  };
>  
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 852541a435ef..5f46dd6f3383 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -4787,6 +4787,7 @@ static const struct bpf_reg_types spin_lock_types = { .types = { PTR_TO_MAP_VALU
>  static const struct bpf_reg_types percpu_btf_ptr_types = { .types = { PTR_TO_PERCPU_BTF_ID } };
>  static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
>  static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
> +static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
>  
>  static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
>  	[ARG_PTR_TO_MAP_KEY]		= &map_key_value_types,
> @@ -4817,6 +4818,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
>  	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
>  	[ARG_PTR_TO_FUNC]		= &func_ptr_types,
>  	[ARG_PTR_TO_STACK_OR_NULL]	= &stack_ptr_types,
> +	[ARG_PTR_TO_CONST_STR]		= &const_str_ptr_types,
>  };
>  
>  static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
> @@ -5067,6 +5069,45 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
>  		if (err)
>  			return err;
>  		err = check_ptr_alignment(env, reg, 0, size, true);
> +	} else if (arg_type == ARG_PTR_TO_CONST_STR) {
> +		struct bpf_map *map = reg->map_ptr;
> +		int map_off;
> +		u64 map_addr;
> +		char *str_ptr;
> +
> +		if (reg->type != PTR_TO_MAP_VALUE || !map ||

I think the 'type' check is redundant,
since check_reg_type() did it via compatible_reg_types.
If so it's probably better to remove it here ?

'!map' looks unnecessary. Can it ever happen? If yes, it's a verifier bug.
For example in check_mem_access() we just deref reg->map_ptr without checking
which, I think, is correct.

> +		    !bpf_map_is_rdonly(map)) {

This check is needed, of course.

> +			verbose(env, "R%d does not point to a readonly map'\n", regno);
> +			return -EACCES;
> +		}
> +
> +		if (!tnum_is_const(reg->var_off)) {
> +			verbose(env, "R%d is not a constant address'\n", regno);
> +			return -EACCES;
> +		}
> +
> +		if (!map->ops->map_direct_value_addr) {
> +			verbose(env, "no direct value access support for this map type\n");
> +			return -EACCES;
> +		}
> +
> +		err = check_map_access(env, regno, reg->off,
> +				       map->value_size - reg->off, false);
> +		if (err)
> +			return err;
> +
> +		map_off = reg->off + reg->var_off.value;
> +		err = map->ops->map_direct_value_addr(map, &map_addr, map_off);
> +		if (err) {

since the code checks it here the same check in check_bpf_snprintf_call() should
probably do:
 if (err) {
   verbose("verifier bug\n");
   return -EFAULT;
 }

instead of just "return err;"
?

> +			verbose(env, "direct value access on string failed\n");

I think the message doesn't tell users much, but they probably should never
see it unless they try to do lookup from readonly array with
more than one element.
So I guess it's fine to keep this one as-is. Just flagging.

Anyway the whole set looks great, so I've applied to bpf-next.
Thanks!
Florent Revest April 20, 2021, 12:35 p.m. UTC | #2
On Tue, Apr 20, 2021 at 12:54 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Mon, Apr 19, 2021 at 05:52:39PM +0200, Florent Revest wrote:
> > This type provides the guarantee that an argument is going to be a const
> > pointer to somewhere in a read-only map value. It also checks that this
> > pointer is followed by a zero character before the end of the map value.
> >
> > Signed-off-by: Florent Revest <revest@chromium.org>
> > Acked-by: Andrii Nakryiko <andrii@kernel.org>
> > ---
> >  include/linux/bpf.h   |  1 +
> >  kernel/bpf/verifier.c | 41 +++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 42 insertions(+)
> >
> > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > index 77d1d8c65b81..c160526fc8bf 100644
> > --- a/include/linux/bpf.h
> > +++ b/include/linux/bpf.h
> > @@ -309,6 +309,7 @@ enum bpf_arg_type {
> >       ARG_PTR_TO_PERCPU_BTF_ID,       /* pointer to in-kernel percpu type */
> >       ARG_PTR_TO_FUNC,        /* pointer to a bpf program function */
> >       ARG_PTR_TO_STACK_OR_NULL,       /* pointer to stack or NULL */
> > +     ARG_PTR_TO_CONST_STR,   /* pointer to a null terminated read-only string */
> >       __BPF_ARG_TYPE_MAX,
> >  };
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 852541a435ef..5f46dd6f3383 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -4787,6 +4787,7 @@ static const struct bpf_reg_types spin_lock_types = { .types = { PTR_TO_MAP_VALU
> >  static const struct bpf_reg_types percpu_btf_ptr_types = { .types = { PTR_TO_PERCPU_BTF_ID } };
> >  static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
> >  static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
> > +static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
> >
> >  static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> >       [ARG_PTR_TO_MAP_KEY]            = &map_key_value_types,
> > @@ -4817,6 +4818,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> >       [ARG_PTR_TO_PERCPU_BTF_ID]      = &percpu_btf_ptr_types,
> >       [ARG_PTR_TO_FUNC]               = &func_ptr_types,
> >       [ARG_PTR_TO_STACK_OR_NULL]      = &stack_ptr_types,
> > +     [ARG_PTR_TO_CONST_STR]          = &const_str_ptr_types,
> >  };
> >
> >  static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
> > @@ -5067,6 +5069,45 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
> >               if (err)
> >                       return err;
> >               err = check_ptr_alignment(env, reg, 0, size, true);
> > +     } else if (arg_type == ARG_PTR_TO_CONST_STR) {
> > +             struct bpf_map *map = reg->map_ptr;
> > +             int map_off;
> > +             u64 map_addr;
> > +             char *str_ptr;
> > +
> > +             if (reg->type != PTR_TO_MAP_VALUE || !map ||
>
> I think the 'type' check is redundant,
> since check_reg_type() did it via compatible_reg_types.
> If so it's probably better to remove it here ?
>
> '!map' looks unnecessary. Can it ever happen? If yes, it's a verifier bug.
> For example in check_mem_access() we just deref reg->map_ptr without checking
> which, I think, is correct.

I agree with all of the above. I only thought it's better to be safe
than sorry but if you'd like I could follow up with a patch that
removes some checks?

> > +                 !bpf_map_is_rdonly(map)) {
>
> This check is needed, of course.
>
> > +                     verbose(env, "R%d does not point to a readonly map'\n", regno);
> > +                     return -EACCES;
> > +             }
> > +
> > +             if (!tnum_is_const(reg->var_off)) {
> > +                     verbose(env, "R%d is not a constant address'\n", regno);
> > +                     return -EACCES;
> > +             }
> > +
> > +             if (!map->ops->map_direct_value_addr) {
> > +                     verbose(env, "no direct value access support for this map type\n");
> > +                     return -EACCES;
> > +             }
> > +
> > +             err = check_map_access(env, regno, reg->off,
> > +                                    map->value_size - reg->off, false);
> > +             if (err)
> > +                     return err;
> > +
> > +             map_off = reg->off + reg->var_off.value;
> > +             err = map->ops->map_direct_value_addr(map, &map_addr, map_off);
> > +             if (err) {
>
> since the code checks it here the same check in check_bpf_snprintf_call() should
> probably do:
>  if (err) {
>    verbose("verifier bug\n");
>    return -EFAULT;
>  }
>
> instead of just "return err;"
> ?

Sure, does not hurt. I can also follow up with a patch unless if you
prefer doing it yourself.

> > +                     verbose(env, "direct value access on string failed\n");
>
> I think the message doesn't tell users much, but they probably should never
> see it unless they try to do lookup from readonly array with
> more than one element.
> So I guess it's fine to keep this one as-is. Just flagging.

Ack

> Anyway the whole set looks great, so I've applied to bpf-next.
> Thanks!

Thank you :D
Alexei Starovoitov April 20, 2021, 3:23 p.m. UTC | #3
On Tue, Apr 20, 2021 at 5:35 AM Florent Revest <revest@chromium.org> wrote:
>
> On Tue, Apr 20, 2021 at 12:54 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Mon, Apr 19, 2021 at 05:52:39PM +0200, Florent Revest wrote:
> > > This type provides the guarantee that an argument is going to be a const
> > > pointer to somewhere in a read-only map value. It also checks that this
> > > pointer is followed by a zero character before the end of the map value.
> > >
> > > Signed-off-by: Florent Revest <revest@chromium.org>
> > > Acked-by: Andrii Nakryiko <andrii@kernel.org>
> > > ---
> > >  include/linux/bpf.h   |  1 +
> > >  kernel/bpf/verifier.c | 41 +++++++++++++++++++++++++++++++++++++++++
> > >  2 files changed, 42 insertions(+)
> > >
> > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > > index 77d1d8c65b81..c160526fc8bf 100644
> > > --- a/include/linux/bpf.h
> > > +++ b/include/linux/bpf.h
> > > @@ -309,6 +309,7 @@ enum bpf_arg_type {
> > >       ARG_PTR_TO_PERCPU_BTF_ID,       /* pointer to in-kernel percpu type */
> > >       ARG_PTR_TO_FUNC,        /* pointer to a bpf program function */
> > >       ARG_PTR_TO_STACK_OR_NULL,       /* pointer to stack or NULL */
> > > +     ARG_PTR_TO_CONST_STR,   /* pointer to a null terminated read-only string */
> > >       __BPF_ARG_TYPE_MAX,
> > >  };
> > >
> > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > > index 852541a435ef..5f46dd6f3383 100644
> > > --- a/kernel/bpf/verifier.c
> > > +++ b/kernel/bpf/verifier.c
> > > @@ -4787,6 +4787,7 @@ static const struct bpf_reg_types spin_lock_types = { .types = { PTR_TO_MAP_VALU
> > >  static const struct bpf_reg_types percpu_btf_ptr_types = { .types = { PTR_TO_PERCPU_BTF_ID } };
> > >  static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
> > >  static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
> > > +static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
> > >
> > >  static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> > >       [ARG_PTR_TO_MAP_KEY]            = &map_key_value_types,
> > > @@ -4817,6 +4818,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> > >       [ARG_PTR_TO_PERCPU_BTF_ID]      = &percpu_btf_ptr_types,
> > >       [ARG_PTR_TO_FUNC]               = &func_ptr_types,
> > >       [ARG_PTR_TO_STACK_OR_NULL]      = &stack_ptr_types,
> > > +     [ARG_PTR_TO_CONST_STR]          = &const_str_ptr_types,
> > >  };
> > >
> > >  static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
> > > @@ -5067,6 +5069,45 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
> > >               if (err)
> > >                       return err;
> > >               err = check_ptr_alignment(env, reg, 0, size, true);
> > > +     } else if (arg_type == ARG_PTR_TO_CONST_STR) {
> > > +             struct bpf_map *map = reg->map_ptr;
> > > +             int map_off;
> > > +             u64 map_addr;
> > > +             char *str_ptr;
> > > +
> > > +             if (reg->type != PTR_TO_MAP_VALUE || !map ||
> >
> > I think the 'type' check is redundant,
> > since check_reg_type() did it via compatible_reg_types.
> > If so it's probably better to remove it here ?
> >
> > '!map' looks unnecessary. Can it ever happen? If yes, it's a verifier bug.
> > For example in check_mem_access() we just deref reg->map_ptr without checking
> > which, I think, is correct.
>
> I agree with all of the above. I only thought it's better to be safe
> than sorry but if you'd like I could follow up with a patch that
> removes some checks?
...
> Sure, does not hurt. I can also follow up with a patch unless if you
> prefer doing it yourself.

Please send a follow up patch.
I consider this kind of "safe than sorry" to be defensive programming that
promotes less-thinking-is-fine-because-its-faster-to-code style.
I'm sure you've seen my rants against defensive programming in the past :)
Florent Revest April 22, 2021, 8:41 a.m. UTC | #4
On Tue, Apr 20, 2021 at 5:23 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Apr 20, 2021 at 5:35 AM Florent Revest <revest@chromium.org> wrote:
> >
> > On Tue, Apr 20, 2021 at 12:54 AM Alexei Starovoitov
> > <alexei.starovoitov@gmail.com> wrote:
> > >
> > > On Mon, Apr 19, 2021 at 05:52:39PM +0200, Florent Revest wrote:
> > > > This type provides the guarantee that an argument is going to be a const
> > > > pointer to somewhere in a read-only map value. It also checks that this
> > > > pointer is followed by a zero character before the end of the map value.
> > > >
> > > > Signed-off-by: Florent Revest <revest@chromium.org>
> > > > Acked-by: Andrii Nakryiko <andrii@kernel.org>
> > > > ---
> > > >  include/linux/bpf.h   |  1 +
> > > >  kernel/bpf/verifier.c | 41 +++++++++++++++++++++++++++++++++++++++++
> > > >  2 files changed, 42 insertions(+)
> > > >
> > > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > > > index 77d1d8c65b81..c160526fc8bf 100644
> > > > --- a/include/linux/bpf.h
> > > > +++ b/include/linux/bpf.h
> > > > @@ -309,6 +309,7 @@ enum bpf_arg_type {
> > > >       ARG_PTR_TO_PERCPU_BTF_ID,       /* pointer to in-kernel percpu type */
> > > >       ARG_PTR_TO_FUNC,        /* pointer to a bpf program function */
> > > >       ARG_PTR_TO_STACK_OR_NULL,       /* pointer to stack or NULL */
> > > > +     ARG_PTR_TO_CONST_STR,   /* pointer to a null terminated read-only string */
> > > >       __BPF_ARG_TYPE_MAX,
> > > >  };
> > > >
> > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > > > index 852541a435ef..5f46dd6f3383 100644
> > > > --- a/kernel/bpf/verifier.c
> > > > +++ b/kernel/bpf/verifier.c
> > > > @@ -4787,6 +4787,7 @@ static const struct bpf_reg_types spin_lock_types = { .types = { PTR_TO_MAP_VALU
> > > >  static const struct bpf_reg_types percpu_btf_ptr_types = { .types = { PTR_TO_PERCPU_BTF_ID } };
> > > >  static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
> > > >  static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
> > > > +static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
> > > >
> > > >  static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> > > >       [ARG_PTR_TO_MAP_KEY]            = &map_key_value_types,
> > > > @@ -4817,6 +4818,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
> > > >       [ARG_PTR_TO_PERCPU_BTF_ID]      = &percpu_btf_ptr_types,
> > > >       [ARG_PTR_TO_FUNC]               = &func_ptr_types,
> > > >       [ARG_PTR_TO_STACK_OR_NULL]      = &stack_ptr_types,
> > > > +     [ARG_PTR_TO_CONST_STR]          = &const_str_ptr_types,
> > > >  };
> > > >
> > > >  static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
> > > > @@ -5067,6 +5069,45 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
> > > >               if (err)
> > > >                       return err;
> > > >               err = check_ptr_alignment(env, reg, 0, size, true);
> > > > +     } else if (arg_type == ARG_PTR_TO_CONST_STR) {
> > > > +             struct bpf_map *map = reg->map_ptr;
> > > > +             int map_off;
> > > > +             u64 map_addr;
> > > > +             char *str_ptr;
> > > > +
> > > > +             if (reg->type != PTR_TO_MAP_VALUE || !map ||
> > >
> > > I think the 'type' check is redundant,
> > > since check_reg_type() did it via compatible_reg_types.
> > > If so it's probably better to remove it here ?
> > >
> > > '!map' looks unnecessary. Can it ever happen? If yes, it's a verifier bug.
> > > For example in check_mem_access() we just deref reg->map_ptr without checking
> > > which, I think, is correct.
> >
> > I agree with all of the above. I only thought it's better to be safe
> > than sorry but if you'd like I could follow up with a patch that
> > removes some checks?
> ...
> > Sure, does not hurt. I can also follow up with a patch unless if you
> > prefer doing it yourself.
>
> Please send a follow up patch.

Okay, doing that today :)

> I consider this kind of "safe than sorry" to be defensive programming that
> promotes less-thinking-is-fine-because-its-faster-to-code style.

Fair

> I'm sure you've seen my rants against defensive programming in the past :)

Ahah, I haven't yet but I surely don't want to make you rant again ;)
diff mbox series

Patch

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 77d1d8c65b81..c160526fc8bf 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -309,6 +309,7 @@  enum bpf_arg_type {
 	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
 	ARG_PTR_TO_FUNC,	/* pointer to a bpf program function */
 	ARG_PTR_TO_STACK_OR_NULL,	/* pointer to stack or NULL */
+	ARG_PTR_TO_CONST_STR,	/* pointer to a null terminated read-only string */
 	__BPF_ARG_TYPE_MAX,
 };
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 852541a435ef..5f46dd6f3383 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4787,6 +4787,7 @@  static const struct bpf_reg_types spin_lock_types = { .types = { PTR_TO_MAP_VALU
 static const struct bpf_reg_types percpu_btf_ptr_types = { .types = { PTR_TO_PERCPU_BTF_ID } };
 static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
 static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
+static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
 
 static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 	[ARG_PTR_TO_MAP_KEY]		= &map_key_value_types,
@@ -4817,6 +4818,7 @@  static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
 	[ARG_PTR_TO_FUNC]		= &func_ptr_types,
 	[ARG_PTR_TO_STACK_OR_NULL]	= &stack_ptr_types,
+	[ARG_PTR_TO_CONST_STR]		= &const_str_ptr_types,
 };
 
 static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
@@ -5067,6 +5069,45 @@  static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		if (err)
 			return err;
 		err = check_ptr_alignment(env, reg, 0, size, true);
+	} else if (arg_type == ARG_PTR_TO_CONST_STR) {
+		struct bpf_map *map = reg->map_ptr;
+		int map_off;
+		u64 map_addr;
+		char *str_ptr;
+
+		if (reg->type != PTR_TO_MAP_VALUE || !map ||
+		    !bpf_map_is_rdonly(map)) {
+			verbose(env, "R%d does not point to a readonly map'\n", regno);
+			return -EACCES;
+		}
+
+		if (!tnum_is_const(reg->var_off)) {
+			verbose(env, "R%d is not a constant address'\n", regno);
+			return -EACCES;
+		}
+
+		if (!map->ops->map_direct_value_addr) {
+			verbose(env, "no direct value access support for this map type\n");
+			return -EACCES;
+		}
+
+		err = check_map_access(env, regno, reg->off,
+				       map->value_size - reg->off, false);
+		if (err)
+			return err;
+
+		map_off = reg->off + reg->var_off.value;
+		err = map->ops->map_direct_value_addr(map, &map_addr, map_off);
+		if (err) {
+			verbose(env, "direct value access on string failed\n");
+			return err;
+		}
+
+		str_ptr = (char *)(long)(map_addr);
+		if (!strnchr(str_ptr + map_off, map->value_size - map_off, 0)) {
+			verbose(env, "string is not zero-terminated\n");
+			return -EINVAL;
+		}
 	}
 
 	return err;