Skip to content

Commit a77c2cf

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-lsm: Extend interoperability with IMA'
Roberto Sassu says: ==================== Extend the interoperability with IMA, to give wider flexibility for the implementation of integrity-focused LSMs based on eBPF. Patch 1 fixes some style issues. Patches 2-6 give the ability to eBPF-based LSMs to take advantage of the measurement capability of IMA without needing to setup a policy in IMA (those LSMs might implement the policy capability themselves). Patches 7-9 allow eBPF-based LSMs to evaluate files read by the kernel. Changelog v2: - Add better description to patch 1 (suggested by Shuah) - Recalculate digest if it is not fresh (when IMA_COLLECTED flag not set) - Move declaration of bpf_ima_file_hash() at the end (suggested by Yonghong) - Add tests to check if the digest has been recalculated - Add deny test for bpf_kernel_read_file() - Add description to tests v1: - Modify ima_file_hash() only and allow the usage of the function with the modified behavior by eBPF-based LSMs through the new function bpf_ima_file_hash() (suggested by Mimi) - Make bpf_lsm_kernel_read_file() sleepable so that bpf_ima_inode_hash() and bpf_ima_file_hash() can be called inside the implementation of eBPF-based LSMs for this hook ==================== Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 357b3cc + 7bae42b commit a77c2cf

File tree

7 files changed

+321
-29
lines changed

7 files changed

+321
-29
lines changed

include/uapi/linux/bpf.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5119,6 +5119,16 @@ union bpf_attr {
51195119
* 0 on success.
51205120
* **-EINVAL** for invalid input
51215121
* **-EOPNOTSUPP** for unsupported protocol
5122+
*
5123+
* long bpf_ima_file_hash(struct file *file, void *dst, u32 size)
5124+
* Description
5125+
* Returns a calculated IMA hash of the *file*.
5126+
* If the hash is larger than *size*, then only *size*
5127+
* bytes will be copied to *dst*
5128+
* Return
5129+
* The **hash_algo** is returned on success,
5130+
* **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
5131+
* invalid arguments are passed.
51225132
*/
51235133
#define __BPF_FUNC_MAPPER(FN) \
51245134
FN(unspec), \
@@ -5314,6 +5324,7 @@ union bpf_attr {
53145324
FN(xdp_store_bytes), \
53155325
FN(copy_from_user_task), \
53165326
FN(skb_set_tstamp), \
5327+
FN(ima_file_hash), \
53175328
/* */
53185329

53195330
/* integer value in 'imm' field of BPF_CALL instruction selects which helper

kernel/bpf/bpf_lsm.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ static const struct bpf_func_proto bpf_ima_inode_hash_proto = {
9999
.allowed = bpf_ima_inode_hash_allowed,
100100
};
101101

102+
BPF_CALL_3(bpf_ima_file_hash, struct file *, file, void *, dst, u32, size)
103+
{
104+
return ima_file_hash(file, dst, size);
105+
}
106+
107+
BTF_ID_LIST_SINGLE(bpf_ima_file_hash_btf_ids, struct, file)
108+
109+
static const struct bpf_func_proto bpf_ima_file_hash_proto = {
110+
.func = bpf_ima_file_hash,
111+
.gpl_only = false,
112+
.ret_type = RET_INTEGER,
113+
.arg1_type = ARG_PTR_TO_BTF_ID,
114+
.arg1_btf_id = &bpf_ima_file_hash_btf_ids[0],
115+
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
116+
.arg3_type = ARG_CONST_SIZE,
117+
.allowed = bpf_ima_inode_hash_allowed,
118+
};
119+
102120
static const struct bpf_func_proto *
103121
bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
104122
{
@@ -121,6 +139,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
121139
return &bpf_bprm_opts_set_proto;
122140
case BPF_FUNC_ima_inode_hash:
123141
return prog->aux->sleepable ? &bpf_ima_inode_hash_proto : NULL;
142+
case BPF_FUNC_ima_file_hash:
143+
return prog->aux->sleepable ? &bpf_ima_file_hash_proto : NULL;
124144
default:
125145
return tracing_prog_func_proto(func_id, prog);
126146
}
@@ -167,6 +187,7 @@ BTF_ID(func, bpf_lsm_inode_setxattr)
167187
BTF_ID(func, bpf_lsm_inode_symlink)
168188
BTF_ID(func, bpf_lsm_inode_unlink)
169189
BTF_ID(func, bpf_lsm_kernel_module_request)
190+
BTF_ID(func, bpf_lsm_kernel_read_file)
170191
BTF_ID(func, bpf_lsm_kernfs_init_security)
171192

172193
#ifdef CONFIG_KEYS

security/integrity/ima/ima_main.c

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ int ima_file_mmap(struct file *file, unsigned long prot)
418418

419419
/**
420420
* ima_file_mprotect - based on policy, limit mprotect change
421+
* @vma: vm_area_struct protection is set to
421422
* @prot: contains the protection that will be applied by the kernel.
422423
*
423424
* Files can be mmap'ed read/write and later changed to execute to circumvent
@@ -519,20 +520,38 @@ int ima_file_check(struct file *file, int mask)
519520
}
520521
EXPORT_SYMBOL_GPL(ima_file_check);
521522

522-
static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
523+
static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf,
524+
size_t buf_size)
523525
{
524-
struct integrity_iint_cache *iint;
525-
int hash_algo;
526+
struct integrity_iint_cache *iint = NULL, tmp_iint;
527+
int rc, hash_algo;
526528

527-
if (!ima_policy_flag)
528-
return -EOPNOTSUPP;
529+
if (ima_policy_flag) {
530+
iint = integrity_iint_find(inode);
531+
if (iint)
532+
mutex_lock(&iint->mutex);
533+
}
534+
535+
if ((!iint || !(iint->flags & IMA_COLLECTED)) && file) {
536+
if (iint)
537+
mutex_unlock(&iint->mutex);
538+
539+
memset(&tmp_iint, 0, sizeof(tmp_iint));
540+
tmp_iint.inode = inode;
541+
mutex_init(&tmp_iint.mutex);
542+
543+
rc = ima_collect_measurement(&tmp_iint, file, NULL, 0,
544+
ima_hash_algo, NULL);
545+
if (rc < 0)
546+
return -EOPNOTSUPP;
547+
548+
iint = &tmp_iint;
549+
mutex_lock(&iint->mutex);
550+
}
529551

530-
iint = integrity_iint_find(inode);
531552
if (!iint)
532553
return -EOPNOTSUPP;
533554

534-
mutex_lock(&iint->mutex);
535-
536555
/*
537556
* ima_file_hash can be called when ima_collect_measurement has still
538557
* not been called, we might not always have a hash.
@@ -551,12 +570,14 @@ static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
551570
hash_algo = iint->ima_hash->algo;
552571
mutex_unlock(&iint->mutex);
553572

573+
if (iint == &tmp_iint)
574+
kfree(iint->ima_hash);
575+
554576
return hash_algo;
555577
}
556578

557579
/**
558-
* ima_file_hash - return the stored measurement if a file has been hashed and
559-
* is in the iint cache.
580+
* ima_file_hash - return a measurement of the file
560581
* @file: pointer to the file
561582
* @buf: buffer in which to store the hash
562583
* @buf_size: length of the buffer
@@ -569,15 +590,15 @@ static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
569590
* The file hash returned is based on the entire file, including the appended
570591
* signature.
571592
*
572-
* If IMA is disabled or if no measurement is available, return -EOPNOTSUPP.
593+
* If the measurement cannot be performed, return -EOPNOTSUPP.
573594
* If the parameters are incorrect, return -EINVAL.
574595
*/
575596
int ima_file_hash(struct file *file, char *buf, size_t buf_size)
576597
{
577598
if (!file)
578599
return -EINVAL;
579600

580-
return __ima_inode_hash(file_inode(file), buf, buf_size);
601+
return __ima_inode_hash(file_inode(file), file, buf, buf_size);
581602
}
582603
EXPORT_SYMBOL_GPL(ima_file_hash);
583604

@@ -604,14 +625,14 @@ int ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
604625
if (!inode)
605626
return -EINVAL;
606627

607-
return __ima_inode_hash(inode, buf, buf_size);
628+
return __ima_inode_hash(inode, NULL, buf, buf_size);
608629
}
609630
EXPORT_SYMBOL_GPL(ima_inode_hash);
610631

611632
/**
612633
* ima_post_create_tmpfile - mark newly created tmpfile as new
613-
* @mnt_userns: user namespace of the mount the inode was found from
614-
* @file : newly created tmpfile
634+
* @mnt_userns: user namespace of the mount the inode was found from
635+
* @inode: inode of the newly created tmpfile
615636
*
616637
* No measuring, appraising or auditing of newly created tmpfiles is needed.
617638
* Skip calling process_measurement(), but indicate which newly, created
@@ -643,7 +664,7 @@ void ima_post_create_tmpfile(struct user_namespace *mnt_userns,
643664

644665
/**
645666
* ima_post_path_mknod - mark as a new inode
646-
* @mnt_userns: user namespace of the mount the inode was found from
667+
* @mnt_userns: user namespace of the mount the inode was found from
647668
* @dentry: newly created dentry
648669
*
649670
* Mark files created via the mknodat syscall as new, so that the
@@ -814,8 +835,8 @@ int ima_load_data(enum kernel_load_data_id id, bool contents)
814835
* ima_post_load_data - appraise decision based on policy
815836
* @buf: pointer to in memory file contents
816837
* @size: size of in memory file contents
817-
* @id: kernel load data caller identifier
818-
* @description: @id-specific description of contents
838+
* @load_id: kernel load data caller identifier
839+
* @description: @load_id-specific description of contents
819840
*
820841
* Measure/appraise/audit in memory buffer based on policy. Policy rules
821842
* are written in terms of a policy identifier.

tools/include/uapi/linux/bpf.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5119,6 +5119,16 @@ union bpf_attr {
51195119
* 0 on success.
51205120
* **-EINVAL** for invalid input
51215121
* **-EOPNOTSUPP** for unsupported protocol
5122+
*
5123+
* long bpf_ima_file_hash(struct file *file, void *dst, u32 size)
5124+
* Description
5125+
* Returns a calculated IMA hash of the *file*.
5126+
* If the hash is larger than *size*, then only *size*
5127+
* bytes will be copied to *dst*
5128+
* Return
5129+
* The **hash_algo** is returned on success,
5130+
* **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
5131+
* invalid arguments are passed.
51225132
*/
51235133
#define __BPF_FUNC_MAPPER(FN) \
51245134
FN(unspec), \
@@ -5314,6 +5324,7 @@ union bpf_attr {
53145324
FN(xdp_store_bytes), \
53155325
FN(copy_from_user_task), \
53165326
FN(skb_set_tstamp), \
5327+
FN(ima_file_hash), \
53175328
/* */
53185329

53195330
/* integer value in 'imm' field of BPF_CALL instruction selects which helper

tools/testing/selftests/bpf/ima_setup.sh

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ LOG_FILE="$(mktemp /tmp/ima_setup.XXXX.log)"
1212

1313
usage()
1414
{
15-
echo "Usage: $0 <setup|cleanup|run> <existing_tmp_dir>"
15+
echo "Usage: $0 <setup|cleanup|run|modify-bin|restore-bin|load-policy> <existing_tmp_dir>"
1616
exit 1
1717
}
1818

@@ -51,6 +51,7 @@ setup()
5151

5252
ensure_mount_securityfs
5353
echo "measure func=BPRM_CHECK fsuuid=${mount_uuid}" > ${IMA_POLICY_FILE}
54+
echo "measure func=BPRM_CHECK fsuuid=${mount_uuid}" > ${mount_dir}/policy_test
5455
}
5556

5657
cleanup() {
@@ -77,6 +78,32 @@ run()
7778
exec "${copied_bin_path}"
7879
}
7980

81+
modify_bin()
82+
{
83+
local tmp_dir="$1"
84+
local mount_dir="${tmp_dir}/mnt"
85+
local copied_bin_path="${mount_dir}/$(basename ${TEST_BINARY})"
86+
87+
echo "mod" >> "${copied_bin_path}"
88+
}
89+
90+
restore_bin()
91+
{
92+
local tmp_dir="$1"
93+
local mount_dir="${tmp_dir}/mnt"
94+
local copied_bin_path="${mount_dir}/$(basename ${TEST_BINARY})"
95+
96+
truncate -s -4 "${copied_bin_path}"
97+
}
98+
99+
load_policy()
100+
{
101+
local tmp_dir="$1"
102+
local mount_dir="${tmp_dir}/mnt"
103+
104+
echo ${mount_dir}/policy_test > ${IMA_POLICY_FILE} 2> /dev/null
105+
}
106+
80107
catch()
81108
{
82109
local exit_code="$1"
@@ -105,6 +132,12 @@ main()
105132
cleanup "${tmp_dir}"
106133
elif [[ "${action}" == "run" ]]; then
107134
run "${tmp_dir}"
135+
elif [[ "${action}" == "modify-bin" ]]; then
136+
modify_bin "${tmp_dir}"
137+
elif [[ "${action}" == "restore-bin" ]]; then
138+
restore_bin "${tmp_dir}"
139+
elif [[ "${action}" == "load-policy" ]]; then
140+
load_policy "${tmp_dir}"
108141
else
109142
echo "Unknown action: ${action}"
110143
exit 1

0 commit comments

Comments
 (0)