This patch introduces <securityfs>/integrity/digest_lists/digest_list_add, which can be used to upload a digest list and add the digests to the hash table; passed data are interpreted as file path if the first byte is / or as the digest list itself otherwise. ima_measure_critical_data() is called to calculate the digest of the digest list and eventually, if an appropriate rule is set in the IMA policy, to measure it. It also introduces <securityfs>/integrity/digest_lists/digest_list_del, which can be used to upload a digest list and delete the digests from the hash table; data are interpreted in the same way as described for digest_list_add. Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> --- Documentation/security/digest_lists.rst | 28 +++ MAINTAINERS | 1 + include/linux/kernel_read_file.h | 1 + security/integrity/digest_lists/Makefile | 2 +- .../integrity/digest_lists/digest_lists.h | 3 +- security/integrity/digest_lists/fs.c | 210 ++++++++++++++++++ security/integrity/integrity.h | 4 + 7 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 security/integrity/digest_lists/fs.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 04ea4b3790e0..85a34a5ad7ce 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -644,3 +644,31 @@ to roll back when one of the steps fails. #. ``digest_list_parse()`` deletes the ``digest_list_item`` on unsuccessful add or successful delete. + + +Interfaces +---------- + +This section introduces the interfaces in +``<securityfs>/integrity/digest_lists`` necessary to interact with Huawei +Digest Lists. + + +``digest_list_add`` +~~~~~~~~~~~~~~~~~~~ + +``digest_list_add`` can be used to upload a digest list and add the digests +to the hash table; passed data are interpreted as file path if the first +byte is ``/`` or as the digest list itself otherwise. + +``ima_measure_critical_data()`` is called to calculate the digest of the +digest list and eventually, if an appropriate rule is set in the IMA +policy, to measure it. + + +``digest_list_del`` +~~~~~~~~~~~~~~~~~~~ + +``digest_list_del`` can be used to upload a digest list and delete the +digests from the hash table; data are interpreted in the same way as +described for ``digest_list_add``. diff --git a/MAINTAINERS b/MAINTAINERS index 31d280acf5fb..c86b410f2c2c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8389,6 +8389,7 @@ T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: Documentation/security/digest_lists.rst F: include/linux/digest_list.h F: security/integrity/digest_lists/digest_list.h +F: security/integrity/digest_lists/fs.c F: security/integrity/digest_lists/methods.c F: security/integrity/digest_lists/parser.c F: uapi/linux/digest_lists.h diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 575ffa1031d3..636ecdfdc616 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -14,6 +14,7 @@ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ id(X509_CERTIFICATE, x509-certificate) \ + id(DIGEST_LIST, digest-list) \ id(MAX_ID, ) #define __fid_enumify(ENUM, dummy) READING_ ## ENUM, diff --git a/security/integrity/digest_lists/Makefile b/security/integrity/digest_lists/Makefile index 86cca5bb7824..00ca06d4bdfd 100644 --- a/security/integrity/digest_lists/Makefile +++ b/security/integrity/digest_lists/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_DIGEST_LISTS) += digest_lists.o -digest_lists-y := methods.o parser.o +digest_lists-y := methods.o parser.o fs.o diff --git a/security/integrity/digest_lists/digest_lists.h b/security/integrity/digest_lists/digest_lists.h index 442ab116a6a5..4b74995848dc 100644 --- a/security/integrity/digest_lists/digest_lists.h +++ b/security/integrity/digest_lists/digest_lists.h @@ -28,7 +28,8 @@ #include <linux/hash_info.h> #include <uapi/linux/digest_lists.h> -#define MAX_DIGEST_SIZE 64 +#include "../integrity.h" + #define HASH_BITS 10 #define MEASURE_HTABLE_SIZE (1 << HASH_BITS) diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c new file mode 100644 index 000000000000..b407c5f7b659 --- /dev/null +++ b/security/integrity/digest_lists/fs.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: fs.c + * Functions for the interfaces exposed in securityfs. + */ + + +#include <linux/fcntl.h> +#include <linux/kernel_read_file.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/rculist.h> +#include <linux/rcupdate.h> +#include <linux/parser.h> +#include <linux/vmalloc.h> +#include <linux/namei.h> +#include <linux/ima.h> + +#include "digest_lists.h" + +static struct dentry *digest_lists_dir; +static struct dentry *digest_list_add_dentry; +static struct dentry *digest_list_del_dentry; + +static ssize_t digest_list_read(char *path, enum ops op) +{ + void *data = NULL; + char *datap; + size_t size; + u8 actions = 0; + bool measured = false; + struct file *file; + u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 }; + enum hash_algo algo = HASH_ALGO__LAST; + int rc, pathlen = strlen(path); + + /* remove \n */ + datap = path; + strsep(&datap, "\n"); + + file = filp_open(path, O_RDONLY, 0); + if (IS_ERR(file)) { + pr_err("unable to open file: %s (%ld)", path, PTR_ERR(file)); + return PTR_ERR(file); + } + + rc = kernel_read_file(file, 0, &data, INT_MAX, NULL, + READING_DIGEST_LIST); + if (rc < 0) { + pr_err("unable to read file: %s (%d)", path, rc); + goto out; + } + + size = rc; + + ima_measure_critical_data("digest_lists", "file_upload", data, size, + false, digest, &algo, &measured); + if (algo == HASH_ALGO__LAST) { + rc = -EINVAL; + goto out_vfree; + } + + if (measured) + actions |= COMPACT_ACTION_IMA_MEASURED; + + rc = digest_list_parse(size, data, op, actions, digest, algo, ""); + if (rc < 0) + pr_err("unable to upload digest list %s (%d)\n", path, rc); +out_vfree: + vfree(data); +out: + fput(file); + + if (rc < 0) + return rc; + + return pathlen; +} + +static ssize_t digest_list_write(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *data; + ssize_t result; + enum ops op = DIGEST_LIST_ADD; + struct dentry *dentry = file_dentry(file); + u8 digest[IMA_MAX_DIGEST_SIZE]; + enum hash_algo algo = HASH_ALGO__LAST; + u8 actions = 0; + bool measured = false; + + /* No partial writes. */ + result = -EINVAL; + if (*ppos != 0) + goto out; + + result = -EFBIG; + if (datalen > 64 * 1024 * 1024 - 1) + goto out; + + data = memdup_user_nul(buf, datalen); + if (IS_ERR(data)) { + result = PTR_ERR(data); + goto out; + } + + if (dentry == digest_list_del_dentry) + op = DIGEST_LIST_DEL; + + result = -EPERM; + + if (data[0] == '/') { + result = digest_list_read(data, op); + } else { + ima_measure_critical_data("digest_lists", "buffer_upload", data, + datalen, false, digest, &algo, + &measured); + if (algo == HASH_ALGO__LAST) { + pr_err("failed to calculate buffer digest\n"); + result = -EINVAL; + goto out_kfree; + } + + if (measured) + actions |= COMPACT_ACTION_IMA_MEASURED; + + result = digest_list_parse(datalen, data, op, actions, digest, + algo, ""); + if (result != datalen) { + pr_err("unable to upload generated digest list\n"); + result = -EINVAL; + } + } +out_kfree: + kfree(data); +out: + return result; +} + +static unsigned long flags; + +/* + * digest_list_open: sequentialize access to the add/del files + */ +static int digest_list_open(struct inode *inode, struct file *filp) +{ + if ((filp->f_flags & O_ACCMODE) != O_WRONLY) + return -EACCES; + + if (test_and_set_bit(0, &flags)) + return -EBUSY; + + return 0; +} + +/* + * digest_list_release - release the add/del files + */ +static int digest_list_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &flags); + return 0; +} + +static const struct file_operations digest_list_upload_ops = { + .open = digest_list_open, + .write = digest_list_write, + .read = seq_read, + .release = digest_list_release, + .llseek = generic_file_llseek, +}; + +int __init digest_lists_fs_init(void) +{ + digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir); + if (IS_ERR(digest_lists_dir)) + return -1; + + digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200, + digest_lists_dir, NULL, + &digest_list_upload_ops); + if (IS_ERR(digest_list_add_dentry)) + goto out; + + digest_list_del_dentry = securityfs_create_file("digest_list_del", 0200, + digest_lists_dir, NULL, + &digest_list_upload_ops); + if (IS_ERR(digest_list_del_dentry)) + goto out; + + return 0; +out: + securityfs_remove(digest_list_del_dentry); + securityfs_remove(digest_list_add_dentry); + securityfs_remove(digest_lists_dir); + return -1; +} + +late_initcall(digest_lists_fs_init); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 547425c20e11..ac45e1599c2d 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -6,6 +6,9 @@ * Mimi Zohar <zohar@xxxxxxxxxx> */ +#ifndef __INTEGRITY_H +#define __INTEGRITY_H + #ifdef pr_fmt #undef pr_fmt #endif @@ -283,3 +286,4 @@ static inline void __init add_to_platform_keyring(const char *source, { } #endif +#endif /*__INTEGRITY_H*/ -- 2.25.1