diff mbox series

[v13,bpf-next,1/6] bpf: Add kfunc bpf_get_file_xattr

Message ID 20231123233936.3079687-2-song@kernel.org (mailing list archive)
State New
Headers show
Series bpf: File verification with LSM and fsverity | expand

Commit Message

Song Liu Nov. 23, 2023, 11:39 p.m. UTC
It is common practice for security solutions to store tags/labels in
xattrs. To implement similar functionalities in BPF LSM, add new kfunc
bpf_get_file_xattr().

The first use case of bpf_get_file_xattr() is to implement file
verifications with asymmetric keys. Specificially, security applications
could use fsverity for file hashes and use xattr to store file signatures.
(kfunc for fsverity hash will be added in a separate commit.)

Currently, only xattrs with "user." prefix can be read with kfunc
bpf_get_file_xattr(). As use cases evolve, we may add a dedicated prefix
for bpf_get_file_xattr().

To avoid recursion, bpf_get_file_xattr can be only called from LSM hooks.

Signed-off-by: Song Liu <song@kernel.org>
---
 kernel/trace/bpf_trace.c | 63 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)

Comments

Song Liu Nov. 24, 2023, 2:53 a.m. UTC | #1
On Thu, Nov 23, 2023 at 3:40 PM Song Liu <song@kernel.org> wrote:
>
> It is common practice for security solutions to store tags/labels in
> xattrs. To implement similar functionalities in BPF LSM, add new kfunc
> bpf_get_file_xattr().
>
> The first use case of bpf_get_file_xattr() is to implement file
> verifications with asymmetric keys. Specificially, security applications
> could use fsverity for file hashes and use xattr to store file signatures.
> (kfunc for fsverity hash will be added in a separate commit.)
>
> Currently, only xattrs with "user." prefix can be read with kfunc
> bpf_get_file_xattr(). As use cases evolve, we may add a dedicated prefix
> for bpf_get_file_xattr().
>
> To avoid recursion, bpf_get_file_xattr can be only called from LSM hooks.
>
> Signed-off-by: Song Liu <song@kernel.org>
> ---
>  kernel/trace/bpf_trace.c | 63 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 63 insertions(+)
>
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> index f0b8b7c29126..55758a6fbe90 100644
> --- a/kernel/trace/bpf_trace.c
> +++ b/kernel/trace/bpf_trace.c
> @@ -24,6 +24,7 @@
>  #include <linux/key.h>
>  #include <linux/verification.h>
>  #include <linux/namei.h>
> +#include <linux/fileattr.h>
>
>  #include <net/bpf_sk_storage.h>
>
> @@ -1431,6 +1432,68 @@ static int __init bpf_key_sig_kfuncs_init(void)
>  late_initcall(bpf_key_sig_kfuncs_init);
>  #endif /* CONFIG_KEYS */
>
> +/* filesystem kfuncs */
> +__bpf_kfunc_start_defs();
> +
> +/**
> + * bpf_get_file_xattr - get xattr of a file
> + * @file: file to get xattr from
> + * @name__str: name of the xattr
> + * @value_ptr: output buffer of the xattr value
> + *
> + * Get xattr *name__str* of *file* and store the output in *value_ptr*.
> + *
> + * For security reasons, only *name__str* with prefix "user." is allowed.
> + *
> + * Return: 0 on success, a negative value on error.
> + */
> +__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
> +                                  struct bpf_dynptr_kern *value_ptr)
> +{
> +       struct dentry *dentry;
> +       u32 value_len;
> +       void *value;
> +
> +       if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> +               return -EPERM;
> +
> +       value_len = __bpf_dynptr_size(value_ptr);
> +       value = __bpf_dynptr_data_rw(value_ptr, value_len);
> +       if (!value)
> +               return -EINVAL;
> +
> +       dentry = file_dentry(file);
> +       return __vfs_getxattr(dentry, dentry->d_inode, name__str, value, value_len);
> +}
> +
> +__bpf_kfunc_end_defs();
> +
> +BTF_SET8_START(fs_kfunc_set_ids)
> +BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
> +BTF_SET8_END(fs_kfunc_set_ids)
> +
> +static int bpf_get_file_xattr_filter(const struct bpf_prog *prog, u32 kfunc_id)
> +{
> +       if (!btf_id_set8_contains(&fs_kfunc_set_ids, kfunc_id))
> +               return 0;
> +
> +       /* Only allow to attach from LSM hooks, to avoid recursion */
> +       return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
> +}
> +
> +const struct btf_kfunc_id_set bpf_fs_kfunc_set = {

Missed static here. If there will be v14, I will fix it here.

Thanks,
Song

> +       .owner = THIS_MODULE,
> +       .set = &fs_kfunc_set_ids,
> +       .filter = bpf_get_file_xattr_filter,
> +};
> +
> +static int __init bpf_fs_kfuncs_init(void)
> +{
> +       return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
> +}
> +
> +late_initcall(bpf_fs_kfuncs_init);
> +
>  static const struct bpf_func_proto *
>  bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
>  {
> --
> 2.34.1
>
Christian Brauner Nov. 24, 2023, 8:44 a.m. UTC | #2
On Thu, Nov 23, 2023 at 03:39:31PM -0800, Song Liu wrote:
> It is common practice for security solutions to store tags/labels in
> xattrs. To implement similar functionalities in BPF LSM, add new kfunc
> bpf_get_file_xattr().
> 
> The first use case of bpf_get_file_xattr() is to implement file
> verifications with asymmetric keys. Specificially, security applications
> could use fsverity for file hashes and use xattr to store file signatures.
> (kfunc for fsverity hash will be added in a separate commit.)
> 
> Currently, only xattrs with "user." prefix can be read with kfunc
> bpf_get_file_xattr(). As use cases evolve, we may add a dedicated prefix
> for bpf_get_file_xattr().
> 
> To avoid recursion, bpf_get_file_xattr can be only called from LSM hooks.
> 
> Signed-off-by: Song Liu <song@kernel.org>
> ---

Looks ok to me. But see below for a question.

If you ever allow the retrieval of additional extended attributes
through bfs_get_file_xattr() or other bpf interfaces we would like to be
Cced, please. The xattr stuff is (/me looks for suitable words)...

Over the last months we've moved POSIX_ACL retrieval out of these
low-level functions. They now have a dedicated api. The same is going to
happen for fscaps as well.

But even with these out of the way we would want the bpf helpers to
always maintain an allowlist of retrievable attributes.

>  kernel/trace/bpf_trace.c | 63 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 63 insertions(+)
> 
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> index f0b8b7c29126..55758a6fbe90 100644
> --- a/kernel/trace/bpf_trace.c
> +++ b/kernel/trace/bpf_trace.c
> @@ -24,6 +24,7 @@
>  #include <linux/key.h>
>  #include <linux/verification.h>
>  #include <linux/namei.h>
> +#include <linux/fileattr.h>
>  
>  #include <net/bpf_sk_storage.h>
>  
> @@ -1431,6 +1432,68 @@ static int __init bpf_key_sig_kfuncs_init(void)
>  late_initcall(bpf_key_sig_kfuncs_init);
>  #endif /* CONFIG_KEYS */
>  
> +/* filesystem kfuncs */
> +__bpf_kfunc_start_defs();
> +
> +/**
> + * bpf_get_file_xattr - get xattr of a file
> + * @file: file to get xattr from
> + * @name__str: name of the xattr
> + * @value_ptr: output buffer of the xattr value
> + *
> + * Get xattr *name__str* of *file* and store the output in *value_ptr*.
> + *
> + * For security reasons, only *name__str* with prefix "user." is allowed.
> + *
> + * Return: 0 on success, a negative value on error.
> + */
> +__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
> +				   struct bpf_dynptr_kern *value_ptr)
> +{
> +	struct dentry *dentry;
> +	u32 value_len;
> +	void *value;
> +
> +	if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> +		return -EPERM;
> +
> +	value_len = __bpf_dynptr_size(value_ptr);
> +	value = __bpf_dynptr_data_rw(value_ptr, value_len);
> +	if (!value)
> +		return -EINVAL;
> +
> +	dentry = file_dentry(file);
> +	return __vfs_getxattr(dentry, dentry->d_inode, name__str, value, value_len);

By calling __vfs_getxattr() from bpf_get_file_xattr() you're skipping at
least inode_permission() from xattr_permission(). I'm probably just
missing or forgot the context. But why is that ok?

> +}
> +
> +__bpf_kfunc_end_defs();
> +
> +BTF_SET8_START(fs_kfunc_set_ids)
> +BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
> +BTF_SET8_END(fs_kfunc_set_ids)
> +
> +static int bpf_get_file_xattr_filter(const struct bpf_prog *prog, u32 kfunc_id)
> +{
> +	if (!btf_id_set8_contains(&fs_kfunc_set_ids, kfunc_id))
> +		return 0;
> +
> +	/* Only allow to attach from LSM hooks, to avoid recursion */
> +	return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
> +}
> +
> +const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
> +	.owner = THIS_MODULE,
> +	.set = &fs_kfunc_set_ids,
> +	.filter = bpf_get_file_xattr_filter,
> +};
> +
> +static int __init bpf_fs_kfuncs_init(void)
> +{
> +	return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
> +}
> +
> +late_initcall(bpf_fs_kfuncs_init);
> +
>  static const struct bpf_func_proto *
>  bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
>  {
> -- 
> 2.34.1
>
Song Liu Nov. 24, 2023, 5:07 p.m. UTC | #3
On Fri, Nov 24, 2023 at 12:44 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Thu, Nov 23, 2023 at 03:39:31PM -0800, Song Liu wrote:
> > It is common practice for security solutions to store tags/labels in
> > xattrs. To implement similar functionalities in BPF LSM, add new kfunc
> > bpf_get_file_xattr().
> >
> > The first use case of bpf_get_file_xattr() is to implement file
> > verifications with asymmetric keys. Specificially, security applications
> > could use fsverity for file hashes and use xattr to store file signatures.
> > (kfunc for fsverity hash will be added in a separate commit.)
> >
> > Currently, only xattrs with "user." prefix can be read with kfunc
> > bpf_get_file_xattr(). As use cases evolve, we may add a dedicated prefix
> > for bpf_get_file_xattr().
> >
> > To avoid recursion, bpf_get_file_xattr can be only called from LSM hooks.
> >
> > Signed-off-by: Song Liu <song@kernel.org>
> > ---
>
> Looks ok to me. But see below for a question.
>
> If you ever allow the retrieval of additional extended attributes
> through bfs_get_file_xattr() or other bpf interfaces we would like to be
> Cced, please. The xattr stuff is (/me looks for suitable words)...
>
> Over the last months we've moved POSIX_ACL retrieval out of these
> low-level functions. They now have a dedicated api. The same is going to
> happen for fscaps as well.
>
> But even with these out of the way we would want the bpf helpers to
> always maintain an allowlist of retrievable attributes.

Agreed. We will be very specific which attributes are available to bpf
helpers/kfuncs.

>
> >  kernel/trace/bpf_trace.c | 63 ++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 63 insertions(+)
> >
> > diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> > index f0b8b7c29126..55758a6fbe90 100644
> > --- a/kernel/trace/bpf_trace.c
> > +++ b/kernel/trace/bpf_trace.c
> > @@ -24,6 +24,7 @@
> >  #include <linux/key.h>
> >  #include <linux/verification.h>
> >  #include <linux/namei.h>
> > +#include <linux/fileattr.h>
> >
> >  #include <net/bpf_sk_storage.h>
> >
> > @@ -1431,6 +1432,68 @@ static int __init bpf_key_sig_kfuncs_init(void)
> >  late_initcall(bpf_key_sig_kfuncs_init);
> >  #endif /* CONFIG_KEYS */
> >
> > +/* filesystem kfuncs */
> > +__bpf_kfunc_start_defs();
> > +
> > +/**
> > + * bpf_get_file_xattr - get xattr of a file
> > + * @file: file to get xattr from
> > + * @name__str: name of the xattr
> > + * @value_ptr: output buffer of the xattr value
> > + *
> > + * Get xattr *name__str* of *file* and store the output in *value_ptr*.
> > + *
> > + * For security reasons, only *name__str* with prefix "user." is allowed.
> > + *
> > + * Return: 0 on success, a negative value on error.
> > + */
> > +__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
> > +                                struct bpf_dynptr_kern *value_ptr)
> > +{
> > +     struct dentry *dentry;
> > +     u32 value_len;
> > +     void *value;
> > +
> > +     if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> > +             return -EPERM;
> > +
> > +     value_len = __bpf_dynptr_size(value_ptr);
> > +     value = __bpf_dynptr_data_rw(value_ptr, value_len);
> > +     if (!value)
> > +             return -EINVAL;
> > +
> > +     dentry = file_dentry(file);
> > +     return __vfs_getxattr(dentry, dentry->d_inode, name__str, value, value_len);
>
> By calling __vfs_getxattr() from bpf_get_file_xattr() you're skipping at
> least inode_permission() from xattr_permission(). I'm probably just
> missing or forgot the context. But why is that ok?

AFAICT, the XATTR_USER_PREFIX above is equivalent to the prefix
check in xattr_permission().

For inode_permission(), I think it is not required because we already
have the "struct file" of  the target file. Did I misunderstand something
here?

Thanks,
Song

> > +}
> > +

[...]
Christian Brauner Nov. 27, 2023, 10:50 a.m. UTC | #4
On Fri, Nov 24, 2023 at 09:07:33AM -0800, Song Liu wrote:
> On Fri, Nov 24, 2023 at 12:44 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> > On Thu, Nov 23, 2023 at 03:39:31PM -0800, Song Liu wrote:
> > > It is common practice for security solutions to store tags/labels in
> > > xattrs. To implement similar functionalities in BPF LSM, add new kfunc
> > > bpf_get_file_xattr().
> > >
> > > The first use case of bpf_get_file_xattr() is to implement file
> > > verifications with asymmetric keys. Specificially, security applications
> > > could use fsverity for file hashes and use xattr to store file signatures.
> > > (kfunc for fsverity hash will be added in a separate commit.)
> > >
> > > Currently, only xattrs with "user." prefix can be read with kfunc
> > > bpf_get_file_xattr(). As use cases evolve, we may add a dedicated prefix
> > > for bpf_get_file_xattr().
> > >
> > > To avoid recursion, bpf_get_file_xattr can be only called from LSM hooks.
> > >
> > > Signed-off-by: Song Liu <song@kernel.org>
> > > ---
> >
> > Looks ok to me. But see below for a question.
> >
> > If you ever allow the retrieval of additional extended attributes
> > through bfs_get_file_xattr() or other bpf interfaces we would like to be
> > Cced, please. The xattr stuff is (/me looks for suitable words)...
> >
> > Over the last months we've moved POSIX_ACL retrieval out of these
> > low-level functions. They now have a dedicated api. The same is going to
> > happen for fscaps as well.
> >
> > But even with these out of the way we would want the bpf helpers to
> > always maintain an allowlist of retrievable attributes.
> 
> Agreed. We will be very specific which attributes are available to bpf
> helpers/kfuncs.
> 
> >
> > >  kernel/trace/bpf_trace.c | 63 ++++++++++++++++++++++++++++++++++++++++
> > >  1 file changed, 63 insertions(+)
> > >
> > > diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> > > index f0b8b7c29126..55758a6fbe90 100644
> > > --- a/kernel/trace/bpf_trace.c
> > > +++ b/kernel/trace/bpf_trace.c
> > > @@ -24,6 +24,7 @@
> > >  #include <linux/key.h>
> > >  #include <linux/verification.h>
> > >  #include <linux/namei.h>
> > > +#include <linux/fileattr.h>
> > >
> > >  #include <net/bpf_sk_storage.h>
> > >
> > > @@ -1431,6 +1432,68 @@ static int __init bpf_key_sig_kfuncs_init(void)
> > >  late_initcall(bpf_key_sig_kfuncs_init);
> > >  #endif /* CONFIG_KEYS */
> > >
> > > +/* filesystem kfuncs */
> > > +__bpf_kfunc_start_defs();
> > > +
> > > +/**
> > > + * bpf_get_file_xattr - get xattr of a file
> > > + * @file: file to get xattr from
> > > + * @name__str: name of the xattr
> > > + * @value_ptr: output buffer of the xattr value
> > > + *
> > > + * Get xattr *name__str* of *file* and store the output in *value_ptr*.
> > > + *
> > > + * For security reasons, only *name__str* with prefix "user." is allowed.
> > > + *
> > > + * Return: 0 on success, a negative value on error.
> > > + */
> > > +__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
> > > +                                struct bpf_dynptr_kern *value_ptr)
> > > +{
> > > +     struct dentry *dentry;
> > > +     u32 value_len;
> > > +     void *value;
> > > +
> > > +     if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> > > +             return -EPERM;
> > > +
> > > +     value_len = __bpf_dynptr_size(value_ptr);
> > > +     value = __bpf_dynptr_data_rw(value_ptr, value_len);
> > > +     if (!value)
> > > +             return -EINVAL;
> > > +
> > > +     dentry = file_dentry(file);
> > > +     return __vfs_getxattr(dentry, dentry->d_inode, name__str, value, value_len);
> >
> > By calling __vfs_getxattr() from bpf_get_file_xattr() you're skipping at
> > least inode_permission() from xattr_permission(). I'm probably just
> > missing or forgot the context. But why is that ok?
> 
> AFAICT, the XATTR_USER_PREFIX above is equivalent to the prefix
> check in xattr_permission().
> 
> For inode_permission(), I think it is not required because we already
> have the "struct file" of  the target file. Did I misunderstand something
> here?

I had overlooked that you don't allow writing xattrs. But there's still
some issues:

So if you look at the system call interface:

fgetxattr(fd)
-> getxattr()
   -> do_getxattr()
      -> vfs_getxattr()
         -> xattr_permission()
         -> __vfs_getxattr()

and io_uring:

do_getxattr()
-> vfs_getxattr()
   -> xattr_permission()
   -> __vfs_getxattr()

you can see that xattr_permission() is a _read/write-time check_, not an
open check. That's because the read/write permissions may depend on what
xattr is read/written. Since you don't know what xattr will be
read/written at open-time.

So there needs to be a good reason for bpf_get_file_xattr() to deviate
from the system call and io_uring interface. And I'd like to hear it,
please. :)

I think I might see the argument because you document the helper as "may
only be called from BPF LSM function" in which case you're trying to say
that bpf_get_file_xattr() is equivalent to a call to __vfs_getxattr()
from an LSM to get at it's own security xattr.

But if that's the case you really should have a way to verify that these
helpers are only callable from a specific BPF context. Because you
otherwise omit read/write-time permission checking when retrieving
xattrs which is a potentialy security issue and may be abused by a BPF
program to skip permission checks that are otherwise enforced.

Is there a way for BPF to enforce/verify that a function is only called
from a specific BPF program? It should be able to recognize that, no?
And then refuse to load that BPF program if a helper is called outside
it's intended context.
Song Liu Nov. 27, 2023, 6:05 p.m. UTC | #5
Hi Christian,

Thanks again for your comments.

On Mon, Nov 27, 2023 at 2:50 AM Christian Brauner <brauner@kernel.org> wrote:
>
[...]
> >
> > AFAICT, the XATTR_USER_PREFIX above is equivalent to the prefix
> > check in xattr_permission().
> >
> > For inode_permission(), I think it is not required because we already
> > have the "struct file" of  the target file. Did I misunderstand something
> > here?
>
> I had overlooked that you don't allow writing xattrs. But there's still
> some issues:
>
> So if you look at the system call interface:
>
> fgetxattr(fd)
> -> getxattr()
>    -> do_getxattr()
>       -> vfs_getxattr()
>          -> xattr_permission()
>          -> __vfs_getxattr()
>
> and io_uring:
>
> do_getxattr()
> -> vfs_getxattr()
>    -> xattr_permission()
>    -> __vfs_getxattr()
>
> you can see that xattr_permission() is a _read/write-time check_, not an
> open check. That's because the read/write permissions may depend on what
> xattr is read/written. Since you don't know what xattr will be
> read/written at open-time.
>
> So there needs to be a good reason for bpf_get_file_xattr() to deviate
> from the system call and io_uring interface. And I'd like to hear it,
> please. :)
>
> I think I might see the argument because you document the helper as "may
> only be called from BPF LSM function" in which case you're trying to say
> that bpf_get_file_xattr() is equivalent to a call to __vfs_getxattr()
> from an LSM to get at it's own security xattr.
>
> But if that's the case you really should have a way to verify that these
> helpers are only callable from a specific BPF context. Because you
> otherwise omit read/write-time permission checking when retrieving
> xattrs which is a potentialy security issue and may be abused by a BPF
> program to skip permission checks that are otherwise enforced.

What do you mean by "a specific BPF context"? Current implementation
makes sure the helper only works on LSM hooks with "struct file *" in the
argument list. Specifically, we can only use them from the following hooks:

    security_binder_transfer_file
    security_bprm_creds_from_file
    security_file_permission
    security_file_alloc_security
    security_file_free_security
    security_file_ioctl
    security_mmap_file
    security_file_lock
    security_file_fcntl
    security_file_set_fowner
    security_file_receive
    security_file_open
    security_file_truncate
    security_kernel_read_file
    security_kernel_post_read_file

Note that, we disallow pointer-walking with the kfunc, so the kfunc is not
allowed from hooks with indirect access to "struct file". For example, we
cannot use it with security_bprm_creds_for_exec(struct linux_binprm *bprm)
as this hook only has bprm, and calling bpf_get_file_xattr(bprm->file) is
not allowed.

> Is there a way for BPF to enforce/verify that a function is only called
> from a specific BPF program? It should be able to recognize that, no?
> And then refuse to load that BPF program if a helper is called outside
> it's intended context.

Similarly, I am not quite sure what you mean by "a specific BPF program".
My answer to this is probably the same as above.

Going back to xattr_permission itself. AFAICT, it does 3 checks:

1. MAY_WRITE check;
2. prefix check;
3. inode_permission().

We don't need MAY_WRITE check as bpf_get_file_xattr is read only.
We have the prefix check embedded in bpf_get_file_xattr():

       if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
               return -EPERM;

inode_permission() is a little trickier here, which checks against idmap.
However, I don't think the check makes sense in the context of LSM.
In this case, we have two processes: one security daemon, which
owns the BPF LSM program, and a process being monitored.
idmap here, from file_mnt_idmap(file), is the idmap from the being
monitored process. However, whether the BPF LSM program have the
permission to read the xattr should be determined by the security
daemon.

Overall, we can technically add xattr_permission() check here. But I
don't think that's the right check for the LSM use case.

Does this make sense? Did I miss or misunderstand something?

Thanks,
Song
Song Liu Nov. 27, 2023, 9:12 p.m. UTC | #6
On Mon, Nov 27, 2023 at 10:05 AM Song Liu <song@kernel.org> wrote:
>
> Hi Christian,
>
> Thanks again for your comments.
>
> On Mon, Nov 27, 2023 at 2:50 AM Christian Brauner <brauner@kernel.org> wrote:
> >
[...]
> Going back to xattr_permission itself. AFAICT, it does 3 checks:
>
> 1. MAY_WRITE check;
> 2. prefix check;
> 3. inode_permission().
>
> We don't need MAY_WRITE check as bpf_get_file_xattr is read only.
> We have the prefix check embedded in bpf_get_file_xattr():
>
>        if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
>                return -EPERM;
>
> inode_permission() is a little trickier here, which checks against idmap.
> However, I don't think the check makes sense in the context of LSM.
> In this case, we have two processes: one security daemon, which
> owns the BPF LSM program, and a process being monitored.
> idmap here, from file_mnt_idmap(file), is the idmap from the being
> monitored process. However, whether the BPF LSM program have the
> permission to read the xattr should be determined by the security
> daemon.

Maybe we should check against nop_mnt_idmap? Would something like
the following make more sense?

Thanks,
Song

diff --git i/kernel/trace/bpf_trace.c w/kernel/trace/bpf_trace.c
index 0019e990408e..62fc51bc57af 100644
--- i/kernel/trace/bpf_trace.c
+++ w/kernel/trace/bpf_trace.c
@@ -1453,6 +1453,7 @@ __bpf_kfunc int bpf_get_file_xattr(struct file
*file, const char *name__str,
        struct dentry *dentry;
        u32 value_len;
        void *value;
+       int ret;

        if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
                return -EPERM;
@@ -1463,6 +1464,9 @@ __bpf_kfunc int bpf_get_file_xattr(struct file
*file, const char *name__str,
                return -EINVAL;

        dentry = file_dentry(file);
+       ret = inode_permission(&nop_mnt_idmap, dentry->d_inode, MAY_READ);
+       if (ret)
+               return ret;
        return __vfs_getxattr(dentry, dentry->d_inode, name__str,
value, value_len);
 }
Christian Brauner Nov. 28, 2023, 9:13 a.m. UTC | #7
On Mon, Nov 27, 2023 at 10:05:23AM -0800, Song Liu wrote:
> Hi Christian,
> 
> Thanks again for your comments.
> 
> On Mon, Nov 27, 2023 at 2:50 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> [...]
> > >
> > > AFAICT, the XATTR_USER_PREFIX above is equivalent to the prefix
> > > check in xattr_permission().
> > >
> > > For inode_permission(), I think it is not required because we already
> > > have the "struct file" of  the target file. Did I misunderstand something
> > > here?
> >
> > I had overlooked that you don't allow writing xattrs. But there's still
> > some issues:
> >
> > So if you look at the system call interface:
> >
> > fgetxattr(fd)
> > -> getxattr()
> >    -> do_getxattr()
> >       -> vfs_getxattr()
> >          -> xattr_permission()
> >          -> __vfs_getxattr()
> >
> > and io_uring:
> >
> > do_getxattr()
> > -> vfs_getxattr()
> >    -> xattr_permission()
> >    -> __vfs_getxattr()
> >
> > you can see that xattr_permission() is a _read/write-time check_, not an
> > open check. That's because the read/write permissions may depend on what
> > xattr is read/written. Since you don't know what xattr will be
> > read/written at open-time.
> >
> > So there needs to be a good reason for bpf_get_file_xattr() to deviate
> > from the system call and io_uring interface. And I'd like to hear it,
> > please. :)
> >
> > I think I might see the argument because you document the helper as "may
> > only be called from BPF LSM function" in which case you're trying to say
> > that bpf_get_file_xattr() is equivalent to a call to __vfs_getxattr()
> > from an LSM to get at it's own security xattr.
> >
> > But if that's the case you really should have a way to verify that these
> > helpers are only callable from a specific BPF context. Because you
> > otherwise omit read/write-time permission checking when retrieving
> > xattrs which is a potentialy security issue and may be abused by a BPF
> > program to skip permission checks that are otherwise enforced.
> 
> What do you mean by "a specific BPF context"? Current implementation
> makes sure the helper only works on LSM hooks with "struct file *" in the
> argument list. Specifically, we can only use them from the following hooks:
> 
>     security_binder_transfer_file
>     security_bprm_creds_from_file
>     security_file_permission
>     security_file_alloc_security
>     security_file_free_security
>     security_file_ioctl
>     security_mmap_file
>     security_file_lock
>     security_file_fcntl
>     security_file_set_fowner
>     security_file_receive
>     security_file_open
>     security_file_truncate
>     security_kernel_read_file
>     security_kernel_post_read_file

Ok, good!

> Note that, we disallow pointer-walking with the kfunc, so the kfunc is not
> allowed from hooks with indirect access to "struct file". For example, we
> cannot use it with security_bprm_creds_for_exec(struct linux_binprm *bprm)
> as this hook only has bprm, and calling bpf_get_file_xattr(bprm->file) is
> not allowed.

Great.

> 
> > Is there a way for BPF to enforce/verify that a function is only called
> > from a specific BPF program? It should be able to recognize that, no?
> > And then refuse to load that BPF program if a helper is called outside
> > it's intended context.
> 
> Similarly, I am not quite sure what you mean by "a specific BPF program".
> My answer to this is probably the same as above.

Yes, this is exactly what I meant.

> 
> Going back to xattr_permission itself. AFAICT, it does 3 checks:
> 
> 1. MAY_WRITE check;
> 2. prefix check;
> 3. inode_permission().
> 
> We don't need MAY_WRITE check as bpf_get_file_xattr is read only.
> We have the prefix check embedded in bpf_get_file_xattr():
> 
>        if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
>                return -EPERM;
> 
> inode_permission() is a little trickier here, which checks against idmap.
> However, I don't think the check makes sense in the context of LSM.
> In this case, we have two processes: one security daemon, which
> owns the BPF LSM program, and a process being monitored.
> idmap here, from file_mnt_idmap(file), is the idmap from the being
> monitored process. However, whether the BPF LSM program have the
> permission to read the xattr should be determined by the security
> daemon.
> 
> Overall, we can technically add xattr_permission() check here. But I
> don't think that's the right check for the LSM use case.
> 
> Does this make sense? Did I miss or misunderstand something?

If the helper is only callable from an LSM context then this should be
fine.
Song Liu Nov. 28, 2023, 2:19 p.m. UTC | #8
Hi Christian,

On Tue, Nov 28, 2023 at 1:13 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Mon, Nov 27, 2023 at 10:05:23AM -0800, Song Liu wrote:
[...]
> >
> > Overall, we can technically add xattr_permission() check here. But I
> > don't think that's the right check for the LSM use case.
> >
> > Does this make sense? Did I miss or misunderstand something?
>
> If the helper is only callable from an LSM context then this should be
> fine.

If everything looks good, would you please give an official Acked-by or
Reviewed-by?

Thanks,
Song
Christian Brauner Nov. 28, 2023, 3:10 p.m. UTC | #9
On Tue, Nov 28, 2023 at 06:19:35AM -0800, Song Liu wrote:
> Hi Christian,
> 
> On Tue, Nov 28, 2023 at 1:13 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> > On Mon, Nov 27, 2023 at 10:05:23AM -0800, Song Liu wrote:
> [...]
> > >
> > > Overall, we can technically add xattr_permission() check here. But I
> > > don't think that's the right check for the LSM use case.
> > >
> > > Does this make sense? Did I miss or misunderstand something?
> >
> > If the helper is only callable from an LSM context then this should be
> > fine.
> 
> If everything looks good, would you please give an official Acked-by or
> Reviewed-by?

Yeah looks ok to me,
Acked-by: Christian Brauner <brauner@kernel.org>
diff mbox series

Patch

diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index f0b8b7c29126..55758a6fbe90 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -24,6 +24,7 @@ 
 #include <linux/key.h>
 #include <linux/verification.h>
 #include <linux/namei.h>
+#include <linux/fileattr.h>
 
 #include <net/bpf_sk_storage.h>
 
@@ -1431,6 +1432,68 @@  static int __init bpf_key_sig_kfuncs_init(void)
 late_initcall(bpf_key_sig_kfuncs_init);
 #endif /* CONFIG_KEYS */
 
+/* filesystem kfuncs */
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_get_file_xattr - get xattr of a file
+ * @file: file to get xattr from
+ * @name__str: name of the xattr
+ * @value_ptr: output buffer of the xattr value
+ *
+ * Get xattr *name__str* of *file* and store the output in *value_ptr*.
+ *
+ * For security reasons, only *name__str* with prefix "user." is allowed.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
+				   struct bpf_dynptr_kern *value_ptr)
+{
+	struct dentry *dentry;
+	u32 value_len;
+	void *value;
+
+	if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+		return -EPERM;
+
+	value_len = __bpf_dynptr_size(value_ptr);
+	value = __bpf_dynptr_data_rw(value_ptr, value_len);
+	if (!value)
+		return -EINVAL;
+
+	dentry = file_dentry(file);
+	return __vfs_getxattr(dentry, dentry->d_inode, name__str, value, value_len);
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_SET8_START(fs_kfunc_set_ids)
+BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_SET8_END(fs_kfunc_set_ids)
+
+static int bpf_get_file_xattr_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+	if (!btf_id_set8_contains(&fs_kfunc_set_ids, kfunc_id))
+		return 0;
+
+	/* Only allow to attach from LSM hooks, to avoid recursion */
+	return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
+}
+
+const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &fs_kfunc_set_ids,
+	.filter = bpf_get_file_xattr_filter,
+};
+
+static int __init bpf_fs_kfuncs_init(void)
+{
+	return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+}
+
+late_initcall(bpf_fs_kfuncs_init);
+
 static const struct bpf_func_proto *
 bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 {