Commit a77c2cfd authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents 357b3cc3 7bae42b6
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -5119,6 +5119,16 @@ union bpf_attr {
 *		0 on success.
 *		**-EINVAL** for invalid input
 *		**-EOPNOTSUPP** for unsupported protocol
 *
 * long bpf_ima_file_hash(struct file *file, void *dst, u32 size)
 *	Description
 *		Returns a calculated IMA hash of the *file*.
 *		If the hash is larger than *size*, then only *size*
 *		bytes will be copied to *dst*
 *	Return
 *		The **hash_algo** is returned on success,
 *		**-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
 *		invalid arguments are passed.
 */
#define __BPF_FUNC_MAPPER(FN)		\
	FN(unspec),			\
@@ -5314,6 +5324,7 @@ union bpf_attr {
	FN(xdp_store_bytes),		\
	FN(copy_from_user_task),	\
	FN(skb_set_tstamp),		\
	FN(ima_file_hash),		\
	/* */

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
+21 −0
Original line number Diff line number Diff line
@@ -99,6 +99,24 @@ static const struct bpf_func_proto bpf_ima_inode_hash_proto = {
	.allowed	= bpf_ima_inode_hash_allowed,
};

BPF_CALL_3(bpf_ima_file_hash, struct file *, file, void *, dst, u32, size)
{
	return ima_file_hash(file, dst, size);
}

BTF_ID_LIST_SINGLE(bpf_ima_file_hash_btf_ids, struct, file)

static const struct bpf_func_proto bpf_ima_file_hash_proto = {
	.func		= bpf_ima_file_hash,
	.gpl_only	= false,
	.ret_type	= RET_INTEGER,
	.arg1_type	= ARG_PTR_TO_BTF_ID,
	.arg1_btf_id	= &bpf_ima_file_hash_btf_ids[0],
	.arg2_type	= ARG_PTR_TO_UNINIT_MEM,
	.arg3_type	= ARG_CONST_SIZE,
	.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)
{
@@ -121,6 +139,8 @@ bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
		return &bpf_bprm_opts_set_proto;
	case BPF_FUNC_ima_inode_hash:
		return prog->aux->sleepable ? &bpf_ima_inode_hash_proto : NULL;
	case BPF_FUNC_ima_file_hash:
		return prog->aux->sleepable ? &bpf_ima_file_hash_proto : NULL;
	default:
		return tracing_prog_func_proto(func_id, prog);
	}
@@ -167,6 +187,7 @@ BTF_ID(func, bpf_lsm_inode_setxattr)
BTF_ID(func, bpf_lsm_inode_symlink)
BTF_ID(func, bpf_lsm_inode_unlink)
BTF_ID(func, bpf_lsm_kernel_module_request)
BTF_ID(func, bpf_lsm_kernel_read_file)
BTF_ID(func, bpf_lsm_kernfs_init_security)

#ifdef CONFIG_KEYS
+39 −18
Original line number Diff line number Diff line
@@ -418,6 +418,7 @@ int ima_file_mmap(struct file *file, unsigned long prot)

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

static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf,
			    size_t buf_size)
{
	struct integrity_iint_cache *iint;
	int hash_algo;

	if (!ima_policy_flag)
		return -EOPNOTSUPP;
	struct integrity_iint_cache *iint = NULL, tmp_iint;
	int rc, hash_algo;

	if (ima_policy_flag) {
		iint = integrity_iint_find(inode);
	if (!iint)
		if (iint)
			mutex_lock(&iint->mutex);
	}

	if ((!iint || !(iint->flags & IMA_COLLECTED)) && file) {
		if (iint)
			mutex_unlock(&iint->mutex);

		memset(&tmp_iint, 0, sizeof(tmp_iint));
		tmp_iint.inode = inode;
		mutex_init(&tmp_iint.mutex);

		rc = ima_collect_measurement(&tmp_iint, file, NULL, 0,
					     ima_hash_algo, NULL);
		if (rc < 0)
			return -EOPNOTSUPP;

		iint = &tmp_iint;
		mutex_lock(&iint->mutex);
	}

	if (!iint)
		return -EOPNOTSUPP;

	/*
	 * ima_file_hash can be called when ima_collect_measurement has still
@@ -551,12 +570,14 @@ static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
	hash_algo = iint->ima_hash->algo;
	mutex_unlock(&iint->mutex);

	if (iint == &tmp_iint)
		kfree(iint->ima_hash);

	return hash_algo;
}

/**
 * ima_file_hash - return the stored measurement if a file has been hashed and
 * is in the iint cache.
 * ima_file_hash - return a measurement of the file
 * @file: pointer to the file
 * @buf: buffer in which to store the hash
 * @buf_size: length of the buffer
@@ -569,7 +590,7 @@ static int __ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
 * The file hash returned is based on the entire file, including the appended
 * signature.
 *
 * If IMA is disabled or if no measurement is available, return -EOPNOTSUPP.
 * If the measurement cannot be performed, return -EOPNOTSUPP.
 * If the parameters are incorrect, return -EINVAL.
 */
int ima_file_hash(struct file *file, char *buf, size_t buf_size)
@@ -577,7 +598,7 @@ int ima_file_hash(struct file *file, char *buf, size_t buf_size)
	if (!file)
		return -EINVAL;

	return __ima_inode_hash(file_inode(file), buf, buf_size);
	return __ima_inode_hash(file_inode(file), file, buf, buf_size);
}
EXPORT_SYMBOL_GPL(ima_file_hash);

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

	return __ima_inode_hash(inode, buf, buf_size);
	return __ima_inode_hash(inode, NULL, buf, buf_size);
}
EXPORT_SYMBOL_GPL(ima_inode_hash);

/**
 * ima_post_create_tmpfile - mark newly created tmpfile as new
 * @mnt_userns: user namespace of the mount the inode was found from
 * @file : newly created tmpfile
 * @inode: inode of the newly created tmpfile
 *
 * No measuring, appraising or auditing of newly created tmpfiles is needed.
 * Skip calling process_measurement(), but indicate which newly, created
@@ -814,8 +835,8 @@ int ima_load_data(enum kernel_load_data_id id, bool contents)
 * ima_post_load_data - appraise decision based on policy
 * @buf: pointer to in memory file contents
 * @size: size of in memory file contents
 * @id: kernel load data caller identifier
 * @description: @id-specific description of contents
 * @load_id: kernel load data caller identifier
 * @description: @load_id-specific description of contents
 *
 * Measure/appraise/audit in memory buffer based on policy.  Policy rules
 * are written in terms of a policy identifier.
+11 −0
Original line number Diff line number Diff line
@@ -5119,6 +5119,16 @@ union bpf_attr {
 *		0 on success.
 *		**-EINVAL** for invalid input
 *		**-EOPNOTSUPP** for unsupported protocol
 *
 * long bpf_ima_file_hash(struct file *file, void *dst, u32 size)
 *	Description
 *		Returns a calculated IMA hash of the *file*.
 *		If the hash is larger than *size*, then only *size*
 *		bytes will be copied to *dst*
 *	Return
 *		The **hash_algo** is returned on success,
 *		**-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
 *		invalid arguments are passed.
 */
#define __BPF_FUNC_MAPPER(FN)		\
	FN(unspec),			\
@@ -5314,6 +5324,7 @@ union bpf_attr {
	FN(xdp_store_bytes),		\
	FN(copy_from_user_task),	\
	FN(skb_set_tstamp),		\
	FN(ima_file_hash),		\
	/* */

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
+34 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ LOG_FILE="$(mktemp /tmp/ima_setup.XXXX.log)"

usage()
{
	echo "Usage: $0 <setup|cleanup|run> <existing_tmp_dir>"
	echo "Usage: $0 <setup|cleanup|run|modify-bin|restore-bin|load-policy> <existing_tmp_dir>"
	exit 1
}

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

	ensure_mount_securityfs
	echo "measure func=BPRM_CHECK fsuuid=${mount_uuid}" > ${IMA_POLICY_FILE}
	echo "measure func=BPRM_CHECK fsuuid=${mount_uuid}" > ${mount_dir}/policy_test
}

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

modify_bin()
{
	local tmp_dir="$1"
	local mount_dir="${tmp_dir}/mnt"
	local copied_bin_path="${mount_dir}/$(basename ${TEST_BINARY})"

	echo "mod" >> "${copied_bin_path}"
}

restore_bin()
{
	local tmp_dir="$1"
	local mount_dir="${tmp_dir}/mnt"
	local copied_bin_path="${mount_dir}/$(basename ${TEST_BINARY})"

	truncate -s -4 "${copied_bin_path}"
}

load_policy()
{
	local tmp_dir="$1"
	local mount_dir="${tmp_dir}/mnt"

	echo ${mount_dir}/policy_test > ${IMA_POLICY_FILE} 2> /dev/null
}

catch()
{
	local exit_code="$1"
@@ -105,6 +132,12 @@ main()
		cleanup "${tmp_dir}"
	elif [[ "${action}" == "run" ]]; then
		run "${tmp_dir}"
	elif [[ "${action}" == "modify-bin" ]]; then
		modify_bin "${tmp_dir}"
	elif [[ "${action}" == "restore-bin" ]]; then
		restore_bin "${tmp_dir}"
	elif [[ "${action}" == "load-policy" ]]; then
		load_policy "${tmp_dir}"
	else
		echo "Unknown action: ${action}"
		exit 1
Loading