Message ID | 20201120131708.3237864-2-kpsingh@chromium.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [bpf-next,1/3] ima: Implement ima_inode_hash | expand |
On 11/20/20 5:17 AM, KP Singh wrote: > From: KP Singh <kpsingh@google.com> > > Provide a wrapper function to get the IMA hash of an inode. This helper > is useful in fingerprinting files (e.g executables on execution) and > using these fingerprints in detections like an executable unlinking > itself. > > Since the ima_inode_hash can sleep, it's only allowed for sleepable > LSM hooks. > > Signed-off-by: KP Singh <kpsingh@google.com> > --- > include/uapi/linux/bpf.h | 11 +++++++++++ > kernel/bpf/bpf_lsm.c | 26 ++++++++++++++++++++++++++ > scripts/bpf_helpers_doc.py | 1 + > tools/include/uapi/linux/bpf.h | 11 +++++++++++ > 4 files changed, 49 insertions(+) > > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h > index 3ca6146f001a..dd5b8622bb89 100644 > --- a/include/uapi/linux/bpf.h > +++ b/include/uapi/linux/bpf.h > @@ -3807,6 +3807,16 @@ union bpf_attr { > * See: **clock_gettime**\ (**CLOCK_MONOTONIC_COARSE**) > * Return > * Current *ktime*. > + * > + * long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size) > + * Description > + * Returns the stored IMA hash of the *inode* (if it's avaialable). > + * If the hash is larger than *size*, then only *size* > + * bytes will be copied to *dst* > + * Return > + * The **hash_algo** of is returned on success, of => if? > + * **-EOPNOTSUP** if IMA is disabled and **-EINVAL** if and => or > + * invalid arguments are passed. > */ > #define __BPF_FUNC_MAPPER(FN) \ > FN(unspec), \ > @@ -3970,6 +3980,7 @@ union bpf_attr { > FN(get_current_task_btf), \ > FN(bprm_opts_set), \ > FN(ktime_get_coarse_ns), \ > + FN(ima_inode_hash), \ > /* */ > > /* integer value in 'imm' field of BPF_CALL instruction selects which helper > diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c > index b4f27a874092..51c36f61339e 100644 > --- a/kernel/bpf/bpf_lsm.c > +++ b/kernel/bpf/bpf_lsm.c > @@ -15,6 +15,7 @@ > #include <net/bpf_sk_storage.h> > #include <linux/bpf_local_storage.h> > #include <linux/btf_ids.h> > +#include <linux/ima.h> > > /* For every LSM hook that allows attachment of BPF programs, declare a nop > * function where a BPF program can be attached. > @@ -75,6 +76,29 @@ const static struct bpf_func_proto bpf_bprm_opts_set_proto = { > .arg2_type = ARG_ANYTHING, > }; > > +BPF_CALL_3(bpf_ima_inode_hash, struct inode *, inode, void *, dst, u32, size) > +{ > + return ima_inode_hash(inode, dst, size); > +} > + > +static bool bpf_ima_inode_hash_allowed(const struct bpf_prog *prog) > +{ > + return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id); > +} > + > +BTF_ID_LIST_SINGLE(bpf_ima_inode_hash_btf_ids, struct, inode) > + > +const static struct bpf_func_proto bpf_ima_inode_hash_proto = { > + .func = bpf_ima_inode_hash, > + .gpl_only = false, > + .ret_type = RET_INTEGER, > + .arg1_type = ARG_PTR_TO_BTF_ID, > + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], > + .arg2_type = ARG_PTR_TO_UNINIT_MEM, > + .arg3_type = ARG_CONST_SIZE_OR_ZERO, I know ARG_CONST_SIZE_OR_ZERO provides some flexibility and may make verifier easier to verify programs. But beyond that did you see any real use case user will pass a zero size buf to get hash value? > + .allowed = bpf_ima_inode_hash_allowed, > +}; > + > static const struct bpf_func_proto * > bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > { > @@ -97,6 +121,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > return &bpf_task_storage_delete_proto; > case BPF_FUNC_bprm_opts_set: > return &bpf_bprm_opts_set_proto; > + case BPF_FUNC_ima_inode_hash: > + return &bpf_ima_inode_hash_proto; > default: > return tracing_prog_func_proto(func_id, prog); > } > diff --git a/scripts/bpf_helpers_doc.py b/scripts/bpf_helpers_doc.py > index add7fcb32dcd..cb16687acb66 100755 > --- a/scripts/bpf_helpers_doc.py > +++ b/scripts/bpf_helpers_doc.py > @@ -430,6 +430,7 @@ class PrinterHelpers(Printer): > 'struct tcp_request_sock', > 'struct udp6_sock', > 'struct task_struct', > + 'struct inode', > > 'struct __sk_buff', > 'struct sk_msg_md', > diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h > index 3ca6146f001a..dd5b8622bb89 100644 > --- a/tools/include/uapi/linux/bpf.h > +++ b/tools/include/uapi/linux/bpf.h > @@ -3807,6 +3807,16 @@ union bpf_attr { > * See: **clock_gettime**\ (**CLOCK_MONOTONIC_COARSE**) > * Return > * Current *ktime*. > + * > + * long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size) > + * Description > + * Returns the stored IMA hash of the *inode* (if it's avaialable). > + * If the hash is larger than *size*, then only *size* > + * bytes will be copied to *dst* > + * Return > + * The **hash_algo** of is returned on success, of => if? > + * **-EOPNOTSUP** if IMA is disabled and **-EINVAL** if and => or. > + * invalid arguments are passed. > */ > #define __BPF_FUNC_MAPPER(FN) \ > FN(unspec), \ > @@ -3970,6 +3980,7 @@ union bpf_attr { > FN(get_current_task_btf), \ > FN(bprm_opts_set), \ > FN(ktime_get_coarse_ns), \ > + FN(ima_inode_hash), \ > /* */ > > /* integer value in 'imm' field of BPF_CALL instruction selects which helper >
[...] > > + * long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size) > > + * Description > > + * Returns the stored IMA hash of the *inode* (if it's avaialable). > > + * If the hash is larger than *size*, then only *size* > > + * bytes will be copied to *dst* > > + * Return > + * The **hash_algo** of is returned on success, > > of => if? Just changed it to: "The **hash_algo** is returned on success" > > > + * **-EOPNOTSUP** if IMA is disabled and **-EINVAL** if > > and => or Done. (and the same for tools/) > [...] > > + .gpl_only = false, > > + .ret_type = RET_INTEGER, > > + .arg1_type = ARG_PTR_TO_BTF_ID, > > + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], > > + .arg2_type = ARG_PTR_TO_UNINIT_MEM, > > + .arg3_type = ARG_CONST_SIZE_OR_ZERO, > > I know ARG_CONST_SIZE_OR_ZERO provides some flexibility and may > make verifier easier to verify programs. But beyond that did > you see any real use case user will pass a zero size buf to > get hash value? > I agree, in this case it makes more sense to ARG_CONST_SIZE. > > + .allowed = bpf_ima_inode_hash_allowed, > > +}; [...]
On Fri, Nov 20, 2020 at 01:17:07PM +0000, KP Singh wrote: > + > +static bool bpf_ima_inode_hash_allowed(const struct bpf_prog *prog) > +{ > + return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id); > +} > + > +BTF_ID_LIST_SINGLE(bpf_ima_inode_hash_btf_ids, struct, inode) > + > +const static struct bpf_func_proto bpf_ima_inode_hash_proto = { > + .func = bpf_ima_inode_hash, > + .gpl_only = false, > + .ret_type = RET_INTEGER, > + .arg1_type = ARG_PTR_TO_BTF_ID, > + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], > + .arg2_type = ARG_PTR_TO_UNINIT_MEM, > + .arg3_type = ARG_CONST_SIZE_OR_ZERO, > + .allowed = bpf_ima_inode_hash_allowed, > +}; > + > static const struct bpf_func_proto * > bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > { > @@ -97,6 +121,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > return &bpf_task_storage_delete_proto; > case BPF_FUNC_bprm_opts_set: > return &bpf_bprm_opts_set_proto; > + case BPF_FUNC_ima_inode_hash: > + return &bpf_ima_inode_hash_proto; That's not enough for correctness. Not only hook has to sleepable, but the program has to be sleepable too. The patch 3 should be causing all sort of kernel warnings for calling mutex from preempt disabled. There it calls bpf_ima_inode_hash() from SEC("lsm/file_mprotect") program. "lsm/" is non-sleepable. "lsm.s/" is. please enable CONFIG_DEBUG_ATOMIC_SLEEP=y in your config.
On Tue, Nov 24, 2020 at 5:02 AM Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote: > > On Fri, Nov 20, 2020 at 01:17:07PM +0000, KP Singh wrote: > > + > > +static bool bpf_ima_inode_hash_allowed(const struct bpf_prog *prog) > > +{ > > + return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id); > > +} > > + > > +BTF_ID_LIST_SINGLE(bpf_ima_inode_hash_btf_ids, struct, inode) > > + > > +const static struct bpf_func_proto bpf_ima_inode_hash_proto = { > > + .func = bpf_ima_inode_hash, > > + .gpl_only = false, > > + .ret_type = RET_INTEGER, > > + .arg1_type = ARG_PTR_TO_BTF_ID, > > + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], > > + .arg2_type = ARG_PTR_TO_UNINIT_MEM, > > + .arg3_type = ARG_CONST_SIZE_OR_ZERO, > > + .allowed = bpf_ima_inode_hash_allowed, > > +}; > > + > > static const struct bpf_func_proto * > > bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > > { > > @@ -97,6 +121,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > > return &bpf_task_storage_delete_proto; > > case BPF_FUNC_bprm_opts_set: > > return &bpf_bprm_opts_set_proto; > > + case BPF_FUNC_ima_inode_hash: > > + return &bpf_ima_inode_hash_proto; > > That's not enough for correctness. > Not only hook has to sleepable, but the program has to be sleepable too. > The patch 3 should be causing all sort of kernel warnings > for calling mutex from preempt disabled. > There it calls bpf_ima_inode_hash() from SEC("lsm/file_mprotect") program. I did actually mean to use SEC("lsm.s/bprm_committed_creds"), my bad. > "lsm/" is non-sleepable. "lsm.s/" is. > please enable CONFIG_DEBUG_ATOMIC_SLEEP=y in your config. Oops, yes I did notice that during recent work on the test cases. Since we need a stronger check than just warnings, I am doing something similar to what we do for bpf_copy_from_user i.e. return prog->aux->sleepable ? &bpf_ima_inode_hash_proto : NULL;
On Tue, Nov 24, 2020 at 12:04 PM KP Singh <kpsingh@chromium.org> wrote: > > On Tue, Nov 24, 2020 at 5:02 AM Alexei Starovoitov > <alexei.starovoitov@gmail.com> wrote: > > > > On Fri, Nov 20, 2020 at 01:17:07PM +0000, KP Singh wrote: > > > + > > > +static bool bpf_ima_inode_hash_allowed(const struct bpf_prog *prog) > > > +{ > > > + return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id); > > > +} > > > + > > > +BTF_ID_LIST_SINGLE(bpf_ima_inode_hash_btf_ids, struct, inode) > > > + > > > +const static struct bpf_func_proto bpf_ima_inode_hash_proto = { > > > + .func = bpf_ima_inode_hash, > > > + .gpl_only = false, > > > + .ret_type = RET_INTEGER, > > > + .arg1_type = ARG_PTR_TO_BTF_ID, > > > + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], > > > + .arg2_type = ARG_PTR_TO_UNINIT_MEM, > > > + .arg3_type = ARG_CONST_SIZE_OR_ZERO, > > > + .allowed = bpf_ima_inode_hash_allowed, > > > +}; > > > + > > > static const struct bpf_func_proto * > > > bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > > > { > > > @@ -97,6 +121,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) > > > return &bpf_task_storage_delete_proto; > > > case BPF_FUNC_bprm_opts_set: > > > return &bpf_bprm_opts_set_proto; > > > + case BPF_FUNC_ima_inode_hash: > > > + return &bpf_ima_inode_hash_proto; > > > > That's not enough for correctness. > > Not only hook has to sleepable, but the program has to be sleepable too. > > The patch 3 should be causing all sort of kernel warnings > > for calling mutex from preempt disabled. > > There it calls bpf_ima_inode_hash() from SEC("lsm/file_mprotect") program. Okay I dug into why I did not get any warnings, I do have CONFIG_DEBUG_ATOMIC_SLEEP and friends enabled and I do look at dmesg and... I think you misread the diff of my patch :) it's indeed attaching to "lsm.s/bprm_committed_creds": [https://lore.kernel.org/bpf/CACYkzJ7Oi8wXf=9a-e=fFHJirRbD=u47z+3+M2cRTCy_1fwtgw@mail.gmail.com/T/#m8d55bf0cdda614338cecd7154476497628612f6a] SEC("lsm/file_mprotect") int BPF_PROG(test_int_hook, struct vm_area_struct *vma, @@ -65,8 +67,11 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm) __u32 key = 0; __u64 *value; - if (monitored_pid == pid) + if (monitored_pid == pid) { bprm_count++; + ima_hash_ret = bpf_ima_inode_hash(bprm->file->f_inode, + &ima_hash, sizeof(ima_hash)); + } bpf_copy_from_user(args, sizeof(args), (void *)bprm->vma->vm_mm->arg_start); bpf_copy_from_user(args, sizeof(args), (void *)bprm->mm->arg_start);
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 3ca6146f001a..dd5b8622bb89 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -3807,6 +3807,16 @@ union bpf_attr { * See: **clock_gettime**\ (**CLOCK_MONOTONIC_COARSE**) * Return * Current *ktime*. + * + * long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size) + * Description + * Returns the stored IMA hash of the *inode* (if it's avaialable). + * If the hash is larger than *size*, then only *size* + * bytes will be copied to *dst* + * Return + * The **hash_algo** of is returned on success, + * **-EOPNOTSUP** if IMA is disabled and **-EINVAL** if + * invalid arguments are passed. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -3970,6 +3980,7 @@ union bpf_attr { FN(get_current_task_btf), \ FN(bprm_opts_set), \ FN(ktime_get_coarse_ns), \ + FN(ima_inode_hash), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c index b4f27a874092..51c36f61339e 100644 --- a/kernel/bpf/bpf_lsm.c +++ b/kernel/bpf/bpf_lsm.c @@ -15,6 +15,7 @@ #include <net/bpf_sk_storage.h> #include <linux/bpf_local_storage.h> #include <linux/btf_ids.h> +#include <linux/ima.h> /* For every LSM hook that allows attachment of BPF programs, declare a nop * function where a BPF program can be attached. @@ -75,6 +76,29 @@ const static struct bpf_func_proto bpf_bprm_opts_set_proto = { .arg2_type = ARG_ANYTHING, }; +BPF_CALL_3(bpf_ima_inode_hash, struct inode *, inode, void *, dst, u32, size) +{ + return ima_inode_hash(inode, dst, size); +} + +static bool bpf_ima_inode_hash_allowed(const struct bpf_prog *prog) +{ + return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id); +} + +BTF_ID_LIST_SINGLE(bpf_ima_inode_hash_btf_ids, struct, inode) + +const static struct bpf_func_proto bpf_ima_inode_hash_proto = { + .func = bpf_ima_inode_hash, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_BTF_ID, + .arg1_btf_id = &bpf_ima_inode_hash_btf_ids[0], + .arg2_type = ARG_PTR_TO_UNINIT_MEM, + .arg3_type = ARG_CONST_SIZE_OR_ZERO, + .allowed = bpf_ima_inode_hash_allowed, +}; + static const struct bpf_func_proto * bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { @@ -97,6 +121,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) return &bpf_task_storage_delete_proto; case BPF_FUNC_bprm_opts_set: return &bpf_bprm_opts_set_proto; + case BPF_FUNC_ima_inode_hash: + return &bpf_ima_inode_hash_proto; default: return tracing_prog_func_proto(func_id, prog); } diff --git a/scripts/bpf_helpers_doc.py b/scripts/bpf_helpers_doc.py index add7fcb32dcd..cb16687acb66 100755 --- a/scripts/bpf_helpers_doc.py +++ b/scripts/bpf_helpers_doc.py @@ -430,6 +430,7 @@ class PrinterHelpers(Printer): 'struct tcp_request_sock', 'struct udp6_sock', 'struct task_struct', + 'struct inode', 'struct __sk_buff', 'struct sk_msg_md', diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 3ca6146f001a..dd5b8622bb89 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -3807,6 +3807,16 @@ union bpf_attr { * See: **clock_gettime**\ (**CLOCK_MONOTONIC_COARSE**) * Return * Current *ktime*. + * + * long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size) + * Description + * Returns the stored IMA hash of the *inode* (if it's avaialable). + * If the hash is larger than *size*, then only *size* + * bytes will be copied to *dst* + * Return + * The **hash_algo** of is returned on success, + * **-EOPNOTSUP** if IMA is disabled and **-EINVAL** if + * invalid arguments are passed. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -3970,6 +3980,7 @@ union bpf_attr { FN(get_current_task_btf), \ FN(bprm_opts_set), \ FN(ktime_get_coarse_ns), \ + FN(ima_inode_hash), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper