2021-09-15 16:36:41

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 0/9] integrity: Introduce DIGLIM advanced features

Introduction
============

This patch set depends on:
- support for the euid policy keyword for critical data
(https://lore.kernel.org/linux-integrity/[email protected]/)
- basic DIGLIM
(https://lore.kernel.org/linux-integrity/[email protected]/)

Introduce the remaining features necessary to upload to the kernel
reference values from RPM headers or digest lists in other formats.

Loader: it will automatically uploads digest lists from a directory
specified in the kernel configuration and will execute a user space
uploader to upload digest lists in a format that is not recognized
by the kernel;

LSM: it identifies digest list parsers and monitor their activity for
integrity evaluation; it protects digest list parsers from other user
space processes considered as untrusted;

Digest list generators: user space tools to generate digest lists from
files (in the compact format) or from the RPM DB;

Digest list uploader and parsers: user space tools responsible to upload to
the kernel digest lists not in the
compact format (e.g. those derived from
the RPM DB);

Administration guide: it describes the steps necessary to upload to the
kernel all the digests of an RPM-based Linux
distribution, using a custom kernel with the DIGLIM
patches applied.

With these changes, DIGLIM is ready to be used by IMA for measurement and
appraisal (this functionality will be added with a future patch set).

DIGLIM already supports appended signatures, but at the moment they cannot
be interpreted by IMA (unsupported ID PKEY_ID_PGP). Another patch set is
necessary to load the PGP keys from the Linux distribution to the system
keyring and to verify the PGP signatures of the RPM headers.

With the patch sets above and the execution policies for IMA proposed some
time ago, it will be possible to generate a measurement list with digest
lists and unknown files, and enable IMA appraisal in enforcing mode.
The kernel command line would be:

ima_template=ima-modsig ima_policy="exec_tcb|tmpfs|digest_lists|appraise_exec_tcb|appraise_tmpfs|appraise_digest_lists"

The effort required for Linux distribution vendors will be to generate and
sign the digest lists for the digest list uploader and the RPM parser. This
could be done for example in the kernel-tools package (or in a separate
package). Existing package signatures are sufficient for remaining files.


Issues/Questions
================

Lockdep (patch 2/9)
-------------------

I'm using iterate_dir() and file_open_root() to iterate and open files
in a directory. Unfortunately, I get the following warning:

============================================
WARNING: possible recursive locking detected
5.15.0-rc1-dont-use-00049-ga5a881519991 #134 Not tainted
--------------------------------------------
swapper/1 is trying to acquire lock:
0000000066812898 (&sb->s_type->i_mutex_key#7){++++}-{4:4}, at: path_openat+0x75d/0xd20

but task is already holding lock:
0000000066812898 (&sb->s_type->i_mutex_key#7){++++}-{4:4}, at: iterate_dir+0x65/0x250

other info that might help us debug this:
Possible unsafe locking scenario:

CPU0
----
lock(&sb->s_type->i_mutex_key#7);
lock(&sb->s_type->i_mutex_key#7);

*** DEADLOCK ***


due to the fact that path_openat() might be trying to lock the directory
already locked by iterate_dir(). What it would be a good way to avoid it?


Inode availability in security_file_free() (patch 3/9)
------------------------------------------------------

It seems that this hook is called when the last reference to a file is
released. After enabling debugging, sometimes the kernel reported that the
inode I was trying to access was already freed.

To avoid this situation, I'm grabbing an additional reference of the inode
in the security_file_open() hook, to ensure that the inode does not
disappear, and I'm releasing it in the security_file_free() hook. Is this
solution acceptable?

Roberto Sassu (9):
ima: Introduce new hook DIGEST_LIST_CHECK
diglim: Loader
diglim: LSM
diglim: Tests - LSM
diglim: Compact digest list generator
diglim: RPM digest list generator
diglim: Digest list uploader
diglim: RPM parser
diglim: Admin guide

Documentation/admin-guide/diglim.rst | 136 +++++
Documentation/admin-guide/index.rst | 1 +
.../security/diglim/implementation.rst | 16 +
Documentation/security/diglim/index.rst | 1 +
Documentation/security/diglim/lsm.rst | 65 +++
Documentation/security/diglim/tests.rst | 18 +-
MAINTAINERS | 10 +
security/integrity/diglim/Kconfig | 14 +
security/integrity/diglim/Makefile | 2 +-
security/integrity/diglim/diglim.h | 27 +
security/integrity/diglim/fs.c | 3 +
security/integrity/diglim/hooks.c | 436 ++++++++++++++++
security/integrity/diglim/loader.c | 92 ++++
security/integrity/iint.c | 1 +
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_main.c | 3 +-
security/integrity/ima/ima_policy.c | 3 +
security/integrity/integrity.h | 8 +
tools/diglim/Makefile | 27 +
tools/diglim/common.c | 79 +++
tools/diglim/common.h | 59 +++
tools/diglim/compact_gen.c | 349 +++++++++++++
tools/diglim/rpm_gen.c | 334 ++++++++++++
tools/diglim/rpm_parser.c | 483 ++++++++++++++++++
tools/diglim/upload_digest_lists.c | 238 +++++++++
tools/testing/selftests/diglim/Makefile | 12 +-
tools/testing/selftests/diglim/common.h | 9 +
tools/testing/selftests/diglim/selftest.c | 357 ++++++++++++-
28 files changed, 2764 insertions(+), 20 deletions(-)
create mode 100644 Documentation/admin-guide/diglim.rst
create mode 100644 Documentation/security/diglim/lsm.rst
create mode 100644 security/integrity/diglim/hooks.c
create mode 100644 security/integrity/diglim/loader.c
create mode 100644 tools/diglim/Makefile
create mode 100644 tools/diglim/common.c
create mode 100644 tools/diglim/common.h
create mode 100644 tools/diglim/compact_gen.c
create mode 100644 tools/diglim/rpm_gen.c
create mode 100644 tools/diglim/rpm_parser.c
create mode 100644 tools/diglim/upload_digest_lists.c

--
2.25.1


2021-09-15 16:36:55

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 2/9] diglim: Loader

Digest lists should be loaded as soon as possible, before files are
accessed, so that services like IMA can perform their queries.

Implement two methods for loading digest lists at kernel initialization
time. Take a directory from CONFIG_DIGLIM_DIGEST_LISTS_DIR and execute
digest_list_read() for each file in that directory. Afterwards, execute a
digest list uploader at CONFIG_DIGLIM_UPLOADER_PATH with the following
syntax:

<CONFIG_DIGLIM_UPLOADER_PATH> add <CONFIG_DIGLIM_DIGEST_LISTS_DIR>

Signed-off-by: Roberto Sassu <[email protected]>
---
.../security/diglim/implementation.rst | 16 ++++
MAINTAINERS | 1 +
security/integrity/diglim/Kconfig | 14 +++
security/integrity/diglim/Makefile | 2 +-
security/integrity/diglim/loader.c | 92 +++++++++++++++++++
security/integrity/iint.c | 1 +
security/integrity/integrity.h | 8 ++
7 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 security/integrity/diglim/loader.c

diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst
index f7b48c093f39..0290b42b014b 100644
--- a/Documentation/security/diglim/implementation.rst
+++ b/Documentation/security/diglim/implementation.rst
@@ -226,3 +226,19 @@ This section introduces the interfaces in
``<securityfs>/integrity/diglim`` necessary to interact with DIGLIM.

.. kernel-doc:: security/integrity/diglim/fs.c
+
+
+Loader
+------
+
+Digest lists should be loaded as soon as possible, before files are
+accessed, so that services like IMA can perform their queries.
+
+The kernel loader implements two methods for loading digest lists at kernel
+initialization time. The first method takes a directory from
+CONFIG_DIGLIM_DIGEST_LISTS_DIR and executes digest_list_read() for each
+file in that directory. The second method, invoked sequentially after the
+first, executes a digest list uploader at CONFIG_DIGLIM_UPLOADER_PATH with
+the following syntax:
+
+<CONFIG_DIGLIM_UPLOADER_PATH> add <CONFIG_DIGLIM_DIGEST_LISTS_DIR>
diff --git a/MAINTAINERS b/MAINTAINERS
index 033c70014568..0ffceb271803 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5518,6 +5518,7 @@ F: include/uapi/linux/diglim.h
F: security/integrity/diglim/diglim.h
F: security/integrity/diglim/fs.c
F: security/integrity/diglim/ima.c
+F: security/integrity/diglim/loader.c
F: security/integrity/diglim/methods.c
F: security/integrity/diglim/parser.c
F: tools/testing/selftests/diglim/
diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig
index 436a76a14337..6c76a0fefe42 100644
--- a/security/integrity/diglim/Kconfig
+++ b/security/integrity/diglim/Kconfig
@@ -9,3 +9,17 @@ config DIGLIM
help
DIGLIM provides reference values for file content and metadata,
that can be used for measurement and appraisal with IMA.
+
+config DIGLIM_DIGEST_LISTS_DIR
+ string "Path of the directory containing digest lists"
+ depends on DIGLIM
+ default "/etc/digest_lists"
+ help
+ This option defines the path of the directory containing digest lists.
+
+config DIGLIM_UPLOADER_PATH
+ string "Path of the user space digest list uploader"
+ depends on DIGLIM
+ default "/usr/libexec/diglim/upload_digest_lists"
+ help
+ This option defines the path of the user space digest list uploader.
diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile
index 5cb1a8bfa0fc..ae79a3317ec8 100644
--- a/security/integrity/diglim/Makefile
+++ b/security/integrity/diglim/Makefile
@@ -5,4 +5,4 @@

obj-$(CONFIG_DIGLIM) += diglim.o

-diglim-y := methods.o parser.o ima.o fs.o
+diglim-y := methods.o parser.o ima.o fs.o loader.o
diff --git a/security/integrity/diglim/loader.c b/security/integrity/diglim/loader.c
new file mode 100644
index 000000000000..e95d3716c153
--- /dev/null
+++ b/security/integrity/diglim/loader.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Functions to load digest lists.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/namei.h>
+#include <linux/ima.h>
+
+#include "diglim.h"
+
+struct readdir_callback {
+ struct dir_context ctx;
+ struct path *path;
+};
+
+static bool digest_list_supported_by_kernel(const char *filename)
+{
+ char *type_start, *format_start, *format_end;
+
+ type_start = strchr(filename, '-');
+ if (!type_start++)
+ return false;
+
+ format_start = strchr(type_start, '-');
+ if (!format_start++)
+ return false;
+
+ format_end = strchr(format_start, '-');
+ if (!format_end)
+ return false;
+
+ if (format_end - format_start != strlen("compact") ||
+ strncmp(format_start, "compact", format_end - format_start))
+ return false;
+
+ return true;
+}
+
+static int __init digest_list_load(struct dir_context *__ctx, const char *name,
+ int namelen, loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx);
+ int ret;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ return 0;
+
+ if (!digest_list_supported_by_kernel(name))
+ return 0;
+
+ ret = digest_list_read(ctx->path, (char *)name, DIGEST_LIST_ADD);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void digest_list_exec_parser(void)
+{
+ char *argv[4] = {NULL}, *envp[1] = {NULL};
+
+ argv[0] = (char *)CONFIG_DIGLIM_UPLOADER_PATH;
+ argv[1] = "add";
+ argv[2] = CONFIG_DIGLIM_DIGEST_LISTS_DIR;
+
+ call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
+}
+
+void __init digest_lists_load(void)
+{
+ struct file *file;
+ struct readdir_callback buf = {
+ .ctx.actor = digest_list_load,
+ };
+
+ file = filp_open(CONFIG_DIGLIM_DIGEST_LISTS_DIR, O_RDONLY, 0);
+ if (IS_ERR(file))
+ return;
+
+ buf.path = &file->f_path;
+ iterate_dir(file, &buf.ctx);
+ fput(file);
+
+ digest_list_exec_parser();
+}
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index 8638976f7990..3799311cc0c3 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -211,6 +211,7 @@ void __init integrity_load_keys(void)

if (!IS_ENABLED(CONFIG_IMA_LOAD_X509))
evm_load_x509();
+ digest_lists_load();
}

static int __init integrity_fs_init(void)
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index de5dde382f11..2ab85f286d17 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -288,4 +288,12 @@ static inline void __init add_to_platform_keyring(const char *source,
{
}
#endif
+
+#ifdef CONFIG_DIGLIM
+void __init digest_lists_load(void);
+#else
+static inline void __init digest_lists_load(void)
+{
+}
+#endif
#endif /*__INTEGRITY_H*/
--
2.25.1

2021-09-15 16:37:25

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 4/9] diglim: Tests - LSM

Introduce more tests to ensure that DIGLIM LSM works as expected:

- digest_list_add_del_test_parser_upload;
- digest_list_add_del_test_parser_upload_not_measured;
- digest_list_add_del_test_parser_upload_write;
- digest_list_add_del_test_parser_upload_read;
- digest_list_add_del_test_parser_upload_char_dev.

The tests are in tools/testing/selftests/diglim/selftest.c.

A description of the tests can be found in
Documentation/security/diglim/tests.rst.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/security/diglim/tests.rst | 18 +-
tools/testing/selftests/diglim/Makefile | 12 +-
tools/testing/selftests/diglim/common.h | 9 +
tools/testing/selftests/diglim/selftest.c | 357 +++++++++++++++++++++-
4 files changed, 378 insertions(+), 18 deletions(-)

diff --git a/Documentation/security/diglim/tests.rst b/Documentation/security/diglim/tests.rst
index 899e7d6683cf..21874918433d 100644
--- a/Documentation/security/diglim/tests.rst
+++ b/Documentation/security/diglim/tests.rst
@@ -14,7 +14,12 @@ expected:
- ``digest_list_add_del_test_file_upload_measured``;
- ``digest_list_add_del_test_file_upload_measured_chown``;
- ``digest_list_check_measurement_list_test_file_upload``;
-- ``digest_list_check_measurement_list_test_buffer_upload``.
+- ``digest_list_check_measurement_list_test_buffer_upload``;
+- ``digest_list_add_del_test_parser_upload``;
+- ``digest_list_add_del_test_parser_upload_not_measured``
+- ``digest_list_add_del_test_parser_upload_write``;
+- ``digest_list_add_del_test_parser_upload_read``;
+- ``digest_list_add_del_test_parser_upload_char_dev``.

The tests are in ``tools/testing/selftests/diglim/selftest.c``.

@@ -68,3 +73,14 @@ addition.

The ``file_upload`` variant uploads a file, while the ``buffer_upload``
variant uploads a buffer.
+
+The ``digest_list_add_del_test_parser`` tests verify the correctness of
+DIGLIM LSM. The ``upload`` variant ensures that files opened by the parser
+are evaluated and the actions are copied to the converted digest list. The
+``upload_not_measured`` variant ensures that the IMA measure action is not
+set to the converted digest list if the parser read a file not measured.
+The ``upload_write`` variant ensures that the parser cannot access a file
+concurrently written by another process. The ``upload_read`` variant
+ensures that files being read by the parser cannot be written by other
+processes. Finally, the ``upload_char_dev`` variant ensures that the parser
+cannot access files without measurable content (e.g. character device).
diff --git a/tools/testing/selftests/diglim/Makefile b/tools/testing/selftests/diglim/Makefile
index 100c219955d7..0e68d8363dd3 100644
--- a/tools/testing/selftests/diglim/Makefile
+++ b/tools/testing/selftests/diglim/Makefile
@@ -7,13 +7,19 @@ LDLIBS += -lpthread

OVERRIDE_TARGETS = 1

-TEST_GEN_PROGS = selftest
-TEST_GEN_PROGS_EXTENDED = libcommon.so
+TEST_GEN_PROGS = selftest manage_digest_lists
+TEST_GEN_PROGS_EXTENDED = libcommon.so libcommon.a common.o

include ../lib.mk

$(OUTPUT)/libcommon.so: common.c
$(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@

+$(OUTPUT)/libcommon.a: common.o
+ ar rcs libcommon.a common.o
+
$(OUTPUT)/selftest: selftest.c $(TEST_GEN_PROGS_EXTENDED)
- $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) -lcommon
+ $(CC) $(CFLAGS) -DOUTPUT=\"$(OUTPUT)\" $< -o $@ $(LDFLAGS) -lcommon
+
+$(OUTPUT)/manage_digest_lists: manage_digest_lists.c $(TEST_GEN_PROGS_EXTENDED)
+ $(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS) -lcommon
diff --git a/tools/testing/selftests/diglim/common.h b/tools/testing/selftests/diglim/common.h
index 6c7979f4182e..b71031adb830 100644
--- a/tools/testing/selftests/diglim/common.h
+++ b/tools/testing/selftests/diglim/common.h
@@ -26,6 +26,15 @@

#define BUFFER_SIZE 1024

+#define INTEGRITY_DIR "/sys/kernel/security/integrity"
+#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim"
+#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query"
+#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_list_label"
+#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add"
+#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del"
+#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded"
+#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count"
+
int write_buffer(char *path, char *buffer, size_t buffer_len, int uid);
int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc,
bool is_char);
diff --git a/tools/testing/selftests/diglim/selftest.c b/tools/testing/selftests/diglim/selftest.c
index 273ba80c43fd..af7a590c445f 100644
--- a/tools/testing/selftests/diglim/selftest.c
+++ b/tools/testing/selftests/diglim/selftest.c
@@ -63,16 +63,6 @@ typedef uint64_t u64;

#define DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX"

-#define INTEGRITY_DIR "/sys/kernel/security/integrity"
-
-#define DIGEST_LIST_DIR INTEGRITY_DIR "/diglim"
-#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query"
-#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_list_label"
-#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add"
-#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del"
-#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded"
-#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count"
-
#define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy"
#define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements"

@@ -94,7 +84,11 @@ typedef uint64_t u64;
#define MAX_DIGEST_LIST_SIZE 10000
#define NUM_ITERATIONS 100000

-enum upload_types { UPLOAD_FILE, UPLOAD_FILE_CHOWN, UPLOAD_BUFFER };
+#define DIGEST_LIST_PARSER_PATH OUTPUT "/manage_digest_lists"
+#define DIGEST_LIST_PARSER_PATH_COPY "/tmp/diglim/manage_digest_lists"
+
+enum upload_types { UPLOAD_FILE, UPLOAD_FILE_CHOWN, UPLOAD_BUFFER,
+ UPLOAD_PARSER, UPLOAD_PARSER_CHOWN, NO_UPLOAD };

const char *const hash_algo_name[HASH_ALGO__LAST] = {
[HASH_ALGO_MD4] = "md4",
@@ -486,6 +480,69 @@ static struct digest_list_item *digest_list_generate_random(void)
return !ret ? digest_list : NULL;
}

+static struct digest_list_item *digest_list_generate_file(char *file_path,
+ enum compact_types type)
+{
+ struct digest_list_item *digest_list;
+ struct compact_list_hdr *hdr;
+ u8 digest[64];
+ int ret;
+
+ digest_list = calloc(1, sizeof(*digest_list));
+ if (!digest_list)
+ return NULL;
+
+ digest_list->size = sizeof(struct compact_list_hdr) +
+ hash_digest_size[HASH_ALGO_SHA256];
+ digest_list->buf = calloc(digest_list->size, sizeof(unsigned char));
+ if (!digest_list->buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ hdr = (struct compact_list_hdr *)digest_list->buf;
+
+ hdr->version = 1;
+ hdr->_reserved = 0;
+ hdr->type = type;
+ hdr->modifiers = 0;
+ hdr->algo = HASH_ALGO_SHA256;
+ hdr->count = 1;
+ hdr->datalen = hash_digest_size[hdr->algo];
+
+ hdr->type = __cpu_to_le16(hdr->type);
+ hdr->algo = __cpu_to_le16(hdr->algo);
+ hdr->count = __cpu_to_le32(hdr->count);
+ hdr->datalen = __cpu_to_le32(hdr->datalen);
+
+ ret = calc_file_digest(digest_list->buf + sizeof(*hdr), file_path,
+ HASH_ALGO_SHA256);
+ if (ret < 0)
+ goto out;
+
+ digest_list->algo = get_ima_hash_algo();
+ if (digest_list->algo == HASH_ALGO__LAST) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = calc_digest(digest, digest_list->buf, digest_list->size,
+ digest_list->algo);
+ if (ret < 0)
+ goto out;
+
+ _bin2hex(digest_list->digest_str, digest,
+ hash_digest_size[digest_list->algo]);
+out:
+ if (ret < 0) {
+ free(digest_list->buf);
+ free(digest_list);
+ return NULL;
+ }
+
+ return digest_list;
+}
+
static int digest_list_upload(struct digest_list_item *digest_list, enum ops op,
enum upload_types upload_type, int uid)
{
@@ -494,16 +551,20 @@ static int digest_list_upload(struct digest_list_item *digest_list, enum ops op,
unsigned char *buffer = digest_list->buf;
size_t buffer_len = digest_list->size;
unsigned char rnd[3];
- int ret = 0, fd;
+ int ret = 0, fd, status;

if (op == DIGEST_LIST_ADD) {
if (upload_type == UPLOAD_FILE ||
- upload_type == UPLOAD_FILE_CHOWN) {
+ upload_type == UPLOAD_FILE_CHOWN ||
+ upload_type == UPLOAD_PARSER ||
+ upload_type == UPLOAD_PARSER_CHOWN ||
+ upload_type == NO_UPLOAD) {
fd = mkstemp(path_template);
if (fd < 0)
return -EPERM;

- if (upload_type == UPLOAD_FILE_CHOWN)
+ if (upload_type == UPLOAD_FILE_CHOWN ||
+ upload_type == UPLOAD_PARSER_CHOWN)
ret = fchown(fd, 3000, -1);

fchmod(fd, 0644);
@@ -550,6 +611,25 @@ static int digest_list_upload(struct digest_list_item *digest_list, enum ops op,
strlen(basename), -1);
if (ret < 0)
goto out;
+ } else if (upload_type == UPLOAD_PARSER ||
+ upload_type == UPLOAD_PARSER_CHOWN) {
+ if (fork() == 0) {
+ execlp(DIGEST_LIST_PARSER_PATH_COPY,
+ DIGEST_LIST_PARSER_PATH_COPY,
+ path_template, path_upload, NULL);
+ exit(1);
+ }
+
+ ret = 0;
+
+ wait(&status);
+ if (WEXITSTATUS(status) != 0)
+ ret = -EINVAL;
+
+ goto out;
+ } else if (upload_type == NO_UPLOAD) {
+ ret = 0;
+ goto out;
}

ret = write_buffer(path_upload, (char *)buffer, buffer_len, uid);
@@ -1439,4 +1519,253 @@ TEST_F_TIMEOUT(test_measure,
UPLOAD_BUFFER);
}

+FIXTURE(test_parser)
+{
+ struct digest_list_item *digest_list;
+};
+
+#define PARSER_RULES "measure func=DIGEST_LIST_CHECK fowner=3000 \n"
+
+FIXTURE_SETUP(test_parser)
+{
+ int ret;
+
+ ret = load_ima_policy(PARSER_RULES);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("load_ima_policy() failed");
+ }
+
+ unlink(DIGEST_LIST_PARSER_PATH_COPY);
+ mkdir("/tmp/diglim", 0700);
+
+ ret = copy_file(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH_COPY);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("copy_file() failed");
+ }
+
+ ret = chown(DIGEST_LIST_PARSER_PATH_COPY, 3000, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("chown() failed");
+ }
+
+ chmod(DIGEST_LIST_PARSER_PATH_COPY, 0755);
+
+ self->digest_list = digest_list_generate_file(
+ DIGEST_LIST_PARSER_PATH_COPY,
+ COMPACT_PARSER);
+ ASSERT_NE(NULL, self->digest_list) {
+ TH_LOG("Cannot generate digest list");
+ }
+
+ self->digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED);
+
+ ret = digest_list_upload(self->digest_list, DIGEST_LIST_ADD,
+ UPLOAD_FILE, 1000);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ ret = digest_list_check(self->digest_list, DIGEST_LIST_ADD);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_check() failed");
+ }
+}
+
+FIXTURE_TEARDOWN(test_parser)
+{
+ int ret;
+
+ ret = digest_list_upload(self->digest_list, DIGEST_LIST_DEL,
+ UPLOAD_FILE, 1000);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ free(self->digest_list->buf);
+ free(self->digest_list);
+}
+
+TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload, UINT_MAX)
+{
+ struct digest_list_item *digest_list;
+ int ret;
+
+ digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE);
+ ASSERT_NE(NULL, digest_list) {
+ TH_LOG("Cannot generate digest list");
+ }
+
+ digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED);
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_ADD,
+ UPLOAD_PARSER_CHOWN, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ ret = digest_list_check(digest_list, DIGEST_LIST_ADD);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_check() failed");
+ }
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_DEL,
+ UPLOAD_PARSER_CHOWN, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ ret = digest_list_check(digest_list, DIGEST_LIST_DEL);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_check() failed");
+ }
+
+ free(digest_list);
+}
+
+TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_not_measured,
+ UINT_MAX)
+{
+ struct digest_list_item *digest_list;
+ int ret;
+
+ digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE);
+ ASSERT_NE(NULL, digest_list) {
+ TH_LOG("Cannot generate digest list");
+ }
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_ADD,
+ UPLOAD_PARSER, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ ret = digest_list_check(digest_list, DIGEST_LIST_ADD);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_check() failed");
+ }
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_DEL,
+ UPLOAD_PARSER, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ ret = digest_list_check(digest_list, DIGEST_LIST_DEL);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_check() failed");
+ }
+
+ free(digest_list);
+}
+
+TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_write,
+ UINT_MAX)
+{
+ char path_template[] = DIGEST_LIST_PATH_TEMPLATE;
+ struct digest_list_item *digest_list;
+ int ret, fd, status, fds[2];
+ char c = 0;
+
+ digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE);
+ ASSERT_NE(NULL, digest_list) {
+ TH_LOG("Cannot generate digest list");
+ }
+
+ digest_list->actions |= (1 << COMPACT_ACTION_IMA_MEASURED);
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, NO_UPLOAD, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7,
+ digest_list->filename_suffix, 6);
+
+ ret = pipe(fds);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("pipe() failed");
+ }
+
+ if (fork() == 0) {
+ fd = open(path_template, O_WRONLY);
+ if (fd < 0)
+ exit(1);
+
+ close(fds[1]);
+ ret = read(fds[0], &c, sizeof(c));
+ exit(1);
+ }
+
+ close(fds[0]);
+
+ if (fork() == 0) {
+ execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH,
+ path_template, DIGEST_LIST_ADD_PATH, NULL);
+ exit(1);
+ }
+
+ wait(&status);
+ ASSERT_EQ(1, WEXITSTATUS(status)) {
+ TH_LOG("digest list parser unexpected exit code %d",
+ WEXITSTATUS(status));
+ }
+
+ free(digest_list);
+}
+
+TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_read,
+ UINT_MAX)
+{
+ char path_template[] = DIGEST_LIST_PATH_TEMPLATE;
+ struct digest_list_item *digest_list;
+ int ret, fd;
+
+ digest_list = digest_list_generate_file("/bin/cat", COMPACT_FILE);
+ ASSERT_NE(NULL, digest_list) {
+ TH_LOG("Cannot generate digest list");
+ }
+
+ ret = digest_list_upload(digest_list, DIGEST_LIST_ADD, NO_UPLOAD, -1);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("digest_list_upload() failed");
+ }
+
+ memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7,
+ digest_list->filename_suffix, 6);
+
+ if (fork() == 0) {
+ execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH,
+ path_template, DIGEST_LIST_ADD_PATH, "open_and_wait",
+ NULL);
+ exit(1);
+ }
+
+ fd = open(path_template, O_WRONLY);
+ ASSERT_LT(0, fd) {
+ TH_LOG("digest list open success unexpected");
+ close(fd);
+ }
+
+ wait(NULL);
+ free(digest_list);
+ unlink(path_template);
+}
+
+TEST_F_TIMEOUT(test_parser, digest_list_add_del_test_parser_upload_char_dev,
+ UINT_MAX)
+{
+ int status;
+
+ if (fork() == 0) {
+ execlp(DIGEST_LIST_PARSER_PATH, DIGEST_LIST_PARSER_PATH,
+ "/dev/null", DIGEST_LIST_ADD_PATH, NULL);
+ exit(1);
+ }
+
+ wait(&status);
+ ASSERT_NE(0, WEXITSTATUS(status)) {
+ TH_LOG("digest list parser success unexpected");
+ }
+}
+
TEST_HARNESS_MAIN
--
2.25.1

2021-09-15 16:37:45

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 9/9] diglim: Admin guide

Introduce a DIGLIM administration guide. Its main purpose is to help users
to configure a system to load to the kernel all the digests of executable
and firmware from the RPM DB, and kernel modules of a custom kernel and a
temporary file mapped as executable as custom digest lists.

With further patch sets, it will be possible to load an execution policy in
IMA and create a measurement list only with digest lists and unknown files,
and to perform secure boot at application level.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/admin-guide/diglim.rst | 136 +++++++++++++++++++++++++++
Documentation/admin-guide/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 138 insertions(+)
create mode 100644 Documentation/admin-guide/diglim.rst

diff --git a/Documentation/admin-guide/diglim.rst b/Documentation/admin-guide/diglim.rst
new file mode 100644
index 000000000000..886100cf5a62
--- /dev/null
+++ b/Documentation/admin-guide/diglim.rst
@@ -0,0 +1,136 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+DIGLIM
+======
+
+Digest Lists Integrity Module (DIGLIM) is an integrity extension aiming to
+facilitate the deployment of remote attestation and secure boot solutions
+based on Integrity Measurement Architecture (IMA).
+
+DIGLIM documentation can be retrieved at ``Documentation/security/diglim``.
+
+Kernel Configuration Options
+============================
+
+DIGLIM can be enabled by setting ``CONFIG_DIGLIM=y`` in the kernel
+configuration. Optionally, it is possible to set
+``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` with the directory digest lists are
+taken from by a kernel loader executed at kernel initialization time.
+Finally, with ``CONFIG_DIGLIM_UPLOADER_PATH`` it is possible to specify the
+path of the digest list uploader, which will execute user space parsers to
+process the digest lists in ``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` that are not
+in the format recognized by the kernel.
+
+
+LSM
+===
+
+DIGLIM includes an LSM to protect user space parsers from other processes,
+when the parsers convert a digest list and uploads it to the kernel. As for
+other LSMs, ``diglim`` should be added to the list of enabled LSMs,
+provided with the ``lsm=`` kernel option. If DIGLIM LSM is not enabled,
+digest lists uploaded by the parser will not be marked as processed by IMA
+and will not be suitable for use.
+
+
+Setup
+=====
+
+Digest lists must be loaded as soon as possible, before files are accessed,
+so that IMA finds the digest of those files with a query. More details on
+the benefits of DIGLIM for IMA can be found in
+``Documentation/security/diglim/introduction.rst``.
+
+
+Digest List Generation
+----------------------
+
+Digest lists can be generated with the tools provided in ``tools/diglim``
+in the kernel sources. In order to compile the tools, it is necessary to
+install the ``glibc-static`` and ``rpm-devel`` packages.
+
+``compact_gen`` can be used to generate digest lists in the compact format,
+which can be directly uploaded to the kernel.
+
+In order to upload digests from the RPM database, it is necessary to
+generate three digest lists: one for ``upload_digest_lists``, which is
+responsible to execute the parsers for digest lists not in the compact
+format; two for ``rpm_parser``, which actually loads the RPM digest lists.
+
+``rpm_parser`` requires two digest lists, one for identification by DIGLIM
+LSM, and the other for measurement and appraisal with IMA. The commands
+are::
+
+ # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/rpm_parser -t parser
+ # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/rpm_parser -t file
+ # tools/diglim/compact_gen -d /etc/digest_lists -i /usr/libexec/diglim/upload_digest_lists -t file
+
+Optionally, an appended signature can be added to the generated digest
+lists, with the sign-file tool included in the kernel sources::
+
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-parser_list-compact-rpm_parser
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-rpm_parser
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-upload_digest_lists
+
+With an appropriate policy, appended signatures can be seen in the
+measurement, by selecting the ``ima-modsig`` template.
+
+Afterwards, digest lists can be generated from the RPM database with the
+command::
+
+ # tools/diglim/rpm_gen -d /etc/digest_lists
+
+If a custom kernel is used, an additional digest list should be generated
+for kernel modules::
+
+ # tools/diglim/compact_gen -d /etc/digest_lists -i /lib/modules/`uname -r` -t file
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-`uname -r`
+
+Finally, in Fedora there is an mmap with execution permission on a file
+with 4K of zeros. A digest list can be generated by executing::
+
+ # dd if=/dev/zero of=/tmp/mmap bs=4096 count=1
+ # tools/diglim/compact_gen -d /etc/digest_lists -i /tmp/mmap -f
+ # scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.pem /etc/digest_lists/0-file_list-compact-mmap
+
+
+Initial Ram Disk
+----------------
+
+Generated digest lists should be copied to the initial ram disk in the
+``CONFIG_DIGLIM_DIGEST_LISTS_DIR`` directory. This can be accomplished,
+with dracut, by adding in /etc/dracut.conf::
+
+ install_optional_items+=" /etc/digest_lists/* "
+
+if ``CONFIG_DIGLIM_DIGEST_LISTS_DIR=/etc/digest_lists``.
+
+``upload_digest_lists`` and ``rpm_parser`` can be also copied to the
+initial ram disk by adding the following lines in /etc/dracut.conf::
+
+ install_optional_items+=" /usr/libexec/diglim/upload_digest_lists "
+ install_optional_items+=" /usr/libexec/diglim/rpm_parser "
+
+assuming that the binaries are installed in /usr/libexec/diglim.
+
+Another important option is::
+
+ do_strip="no"
+
+This prevents dracut from stripping the symbols from binaries. If binaries
+are altered, their digest will be different from the reference value and
+will not be found in the DIGLIM hash table.
+
+
+Boot and Digest List Upload
+---------------------------
+
+After generating the initial ram disk and rebooting, digest lists should
+have been added to the DIGLIM hash table. This can be checked by executing::
+
+ # cat /sys/kernel/security/integrity/diglim/digests_count
+ Parser digests: 1
+ File digests: 104273
+ Metadata digests: 0
+ Digest list digests: 2430
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index dc00afcabb95..1cc7d3b3e79c 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -79,6 +79,7 @@ configure specific aspects of kernel behavior to your liking.
cputopology
dell_rbu
device-mapper/index
+ diglim
edid
efi-stub
ext4
diff --git a/MAINTAINERS b/MAINTAINERS
index 1efc1724376e..953c86915c49 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5507,6 +5507,7 @@ M: Roberto Sassu <[email protected]>
L: [email protected]
S: Supported
T: git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
+F: Documentation/admin-guide/diglim.rst
F: Documentation/security/diglim/architecture.rst
F: Documentation/security/diglim/implementation.rst
F: Documentation/security/diglim/index.rst
--
2.25.1

2021-09-15 16:38:08

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 6/9] diglim: RPM digest list generator

Introduce the generator of RPM digest lists, which takes the RPM header
from a package or the RPM DB. Optionally, it appends the RPM header
signature with the same format of kernel modules and ID PKEY_ID_PGP.

This type of digest list can be loaded through the user space parser
rpm_parser, which is introduced in a subsequent patch.

Signed-off-by: Roberto Sassu <[email protected]>
---
MAINTAINERS | 1 +
tools/diglim/Makefile | 5 +-
tools/diglim/rpm_gen.c | 334 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 339 insertions(+), 1 deletion(-)
create mode 100644 tools/diglim/rpm_gen.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b752790c06ea..04b252ebd7e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5526,6 +5526,7 @@ F: tools/diglim/Makefile
F: tools/diglim/common.c
F: tools/diglim/common.h
F: tools/diglim/compact_gen.c
+F: tools/diglim/rpm_gen.c
F: tools/testing/selftests/diglim/

DIOLAN U2C-12 I2C DRIVER
diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile
index 45efa554449d..332bcd93af78 100644
--- a/tools/diglim/Makefile
+++ b/tools/diglim/Makefile
@@ -3,7 +3,7 @@
CC := $(CROSS_COMPILE)gcc
CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb

-PROGS := compact_gen
+PROGS := compact_gen rpm_gen
PROGS_EXTENDED := common.o

all: $(PROGS)
@@ -16,3 +16,6 @@ common.o: common.c

compact_gen: compact_gen.c $(PROGS_EXTENDED)
$(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lcrypto
+
+rpm_gen: rpm_gen.c $(PROGS_EXTENDED)
+ $(CC) $(CFLAGS) $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS) -lrpm -lrpmio
diff --git a/tools/diglim/rpm_gen.c b/tools/diglim/rpm_gen.c
new file mode 100644
index 000000000000..fbee65ea0394
--- /dev/null
+++ b/tools/diglim/rpm_gen.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Generate RPM digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <limits.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmtag.h>
+#include <bits/endianness.h>
+
+#include "common.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <linux/byteorder/big_endian.h>
+#else
+#include <linux/byteorder/little_endian.h>
+#endif
+
+#include "../../usr/include/linux/diglim.h"
+
+const unsigned char rpm_header_magic[8] = {
+ 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
+#define MODULE_SIG_STRING "~Module signature appended~\n"
+
+enum pkey_id_type {
+ PKEY_ID_PGP, /* OpenPGP generated key ID */
+ PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */
+ PKEY_ID_PKCS7, /* Signature in PKCS#7 message */
+};
+
+/*
+ * Module signature information block.
+ *
+ * The constituents of the signature section are, in order:
+ *
+ * - Signer's name
+ * - Key identifier
+ * - Signature data
+ * - Information block
+ */
+struct module_signature {
+ u8 algo; /* Public-key crypto algorithm [0] */
+ u8 hash; /* Digest algorithm [0] */
+ u8 id_type; /* Key identifier type [PKEY_ID_PKCS7] */
+ u8 signer_len; /* Length of signer's name [0] */
+ u8 key_id_len; /* Length of key identifier [0] */
+ u8 __pad[3];
+ __be32 sig_len; /* Length of signature data */
+};
+
+static int gen_filename_prefix(char *filename, int filename_len, int pos,
+ const char *format, enum compact_types type)
+{
+ return snprintf(filename, filename_len, "%d-%s_list-%s-",
+ (pos >= 0) ? pos : 0, compact_types_str[type], format);
+}
+
+static void gen_filename(Header rpm, int pos, enum compact_types type,
+ char *filename, int filename_len, char *output_format)
+{
+ rpmtd name = rpmtdNew(), version = rpmtdNew();
+ rpmtd release = rpmtdNew(), arch = rpmtdNew();
+ int prefix_len;
+
+ headerGet(rpm, RPMTAG_NAME, name, 0);
+ headerGet(rpm, RPMTAG_VERSION, version, 0);
+ headerGet(rpm, RPMTAG_RELEASE, release, 0);
+ headerGet(rpm, RPMTAG_ARCH, arch, 0);
+
+ prefix_len = gen_filename_prefix(filename, filename_len, pos,
+ output_format, type);
+
+ snprintf(filename + prefix_len, filename_len - prefix_len,
+ "%s-%s-%s.%s", rpmtdGetString(name), rpmtdGetString(version),
+ rpmtdGetString(release), rpmtdGetString(arch));
+
+ rpmtdFree(name);
+ rpmtdFree(version);
+ rpmtdFree(release);
+ rpmtdFree(arch);
+}
+
+static int find_package(Header rpm, char *package)
+{
+ rpmtd name = rpmtdNew();
+ int found = 0;
+
+ headerGet(rpm, RPMTAG_NAME, name, 0);
+ if (!strncmp(rpmtdGetString(name), package, strlen(package)))
+ found = 1;
+
+ rpmtdFree(name);
+ return found;
+}
+
+static int write_rpm_header(Header rpm, int dirfd, char *filename)
+{
+ rpmtd immutable;
+ ssize_t ret;
+ int fd;
+
+ fd = openat(dirfd, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ return -EACCES;
+
+ ret = write(fd, rpm_header_magic, sizeof(rpm_header_magic));
+ if (ret != sizeof(rpm_header_magic)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ immutable = rpmtdNew();
+ headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0);
+ ret = write(fd, immutable->data, immutable->count);
+ if (ret != immutable->count) {
+ ret = -EIO;
+ goto out;
+ }
+
+ rpmtdFree(immutable);
+out:
+ close(fd);
+
+ if (ret < 0)
+ unlinkat(dirfd, filename, 0);
+
+ return ret;
+}
+
+static int write_rpm_header_signature(Header rpm, int dirfd, char *filename)
+{
+ struct module_signature modsig = { 0 };
+ rpmtd signature = rpmtdNew();
+ int ret, fd;
+
+ headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+ fd = openat(dirfd, filename, O_WRONLY | O_APPEND);
+ if (fd < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ modsig.id_type = PKEY_ID_PGP;
+ modsig.sig_len = signature->count;
+ modsig.sig_len = __cpu_to_be32(modsig.sig_len);
+
+ ret = write(fd, signature->data, signature->count);
+ if (ret != signature->count) {
+ ret = -EIO;
+ goto out_fd;
+ }
+
+ ret = write(fd, &modsig, sizeof(modsig));
+ if (ret != sizeof(modsig)) {
+ ret = -EIO;
+ goto out_fd;
+ }
+
+ ret = write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1);
+ if (ret != sizeof(MODULE_SIG_STRING) - 1) {
+ ret = -EIO;
+ goto out;
+ }
+
+ ret = 0;
+out_fd:
+ close(fd);
+out:
+ rpmtdFree(signature);
+
+ if (ret < 0)
+ unlinkat(dirfd, filename, 0);
+
+ return ret;
+}
+
+static void usage(char *progname)
+{
+ printf("Usage: %s <options>\n", progname);
+ printf("Options:\n");
+ printf("\t-d <output directory>: directory digest lists are written to\n"
+ "\t-r <RPM path>: RPM package the digest list is generated from (all RPM packages in DB if not specified)\n"
+ "\t-p <package>: selected RPM package in RPM DB\n"
+ "\t-h: display help\n");
+}
+
+static void gen_rpm_digest_list(Header rpm, int dirfd, char *filename)
+{
+ int ret;
+
+ ret = write_rpm_header(rpm, dirfd, filename);
+ if (ret < 0) {
+ printf("Cannot generate %s digest list\n", filename);
+ return;
+ }
+
+ ret = write_rpm_header_signature(rpm, dirfd, filename);
+ if (ret < 0)
+ printf("Cannot add signature to %s digest list\n",
+ filename);
+}
+
+int main(int argc, char *argv[])
+{
+ char filename[NAME_MAX + 1];
+ rpmts ts = NULL;
+ Header hdr;
+ FD_t fd;
+ rpmdbMatchIterator mi;
+ rpmVSFlags vsflags = 0;
+ char *input_package = NULL, *selected_package = NULL;
+ char *output_dir = NULL;
+ struct stat st;
+ int c;
+ int ret, dirfd;
+
+ while ((c = getopt(argc, argv, "d:r:p:h")) != -1) {
+ switch (c) {
+ case 'd':
+ output_dir = optarg;
+ break;
+ case 'r':
+ input_package = optarg;
+ break;
+ case 'p':
+ selected_package = optarg;
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ default:
+ printf("Invalid option %c\n", c);
+ exit(1);
+ }
+ }
+
+ if (!output_dir) {
+ printf("Output directory not specified\n");
+ exit(1);
+ }
+
+ if (stat(output_dir, &st) == -1)
+ mkdir(output_dir, 0755);
+
+ dirfd = open(output_dir, O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0) {
+ printf("Unable to open %s, ret: %d\n", output_dir, -errno);
+ ret = -errno;
+ goto out;
+ }
+
+ ts = rpmtsCreate();
+ if (!ts) {
+ rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ ret = rpmReadConfigFiles(NULL, NULL);
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (input_package) {
+ vsflags |= _RPMVSF_NODIGESTS;
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ rpmtsSetVSFlags(ts, vsflags);
+
+ fd = Fopen(input_package, "r.ufdio");
+ if ((!fd) || Ferror(fd)) {
+ rpmlog(RPMLOG_NOTICE,
+ "Failed to open package file %s, %s\n",
+ input_package, Fstrerror(fd));
+ ret = -EACCES;
+ goto out_rpm;
+ }
+
+ ret = rpmReadPackageFile(ts, fd, "rpm", &hdr);
+ Fclose(fd);
+
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE,
+ "Could not read package file %s\n",
+ input_package);
+ goto out_rpm;
+ }
+
+ gen_filename(hdr, 0, COMPACT_FILE, filename, sizeof(filename),
+ "rpm");
+
+ gen_rpm_digest_list(hdr, dirfd, filename);
+ headerFree(hdr);
+ goto out_rpm;
+ }
+
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+ while ((hdr = rpmdbNextIterator(mi)) != NULL) {
+ gen_filename(hdr, 0, COMPACT_FILE, filename, sizeof(filename),
+ "rpm");
+
+ if (strstr(filename, "gpg-pubkey") != NULL)
+ continue;
+
+ if (selected_package && !find_package(hdr, selected_package))
+ continue;
+
+ gen_rpm_digest_list(hdr, dirfd, filename);
+ }
+
+ rpmdbFreeIterator(mi);
+out_rpm:
+ rpmFreeRpmrc();
+ rpmtsFree(ts);
+out:
+ close(dirfd);
+ return ret;
+}
--
2.25.1

2021-09-15 16:38:47

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 8/9] diglim: RPM parser

Introduce a parser of the RPM digest list, which converts the digest lists
to the compact format and uploads the converted digest lists to the kernel.

It takes as input the type of operation to perform, add or delete, and the
file or directory with the files to process.

Also the RPM parser is intentionally compiled as a static binary to avoid
to generate a compact digest list also for its dependencies.

Signed-off-by: Roberto Sassu <[email protected]>
---
MAINTAINERS | 1 +
tools/diglim/Makefile | 5 +-
tools/diglim/rpm_parser.c | 483 ++++++++++++++++++++++++++++++++++++++
3 files changed, 488 insertions(+), 1 deletion(-)
create mode 100644 tools/diglim/rpm_parser.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 148a2a7957b7..1efc1724376e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5527,6 +5527,7 @@ F: tools/diglim/common.c
F: tools/diglim/common.h
F: tools/diglim/compact_gen.c
F: tools/diglim/rpm_gen.c
+F: tools/diglim/rpm_parser.c
F: tools/diglim/upload_digest_lists.c
F: tools/testing/selftests/diglim/

diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile
index a22125ad0281..7019c5b9fad9 100644
--- a/tools/diglim/Makefile
+++ b/tools/diglim/Makefile
@@ -3,7 +3,7 @@
CC := $(CROSS_COMPILE)gcc
CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb

-PROGS := compact_gen rpm_gen upload_digest_lists
+PROGS := compact_gen rpm_gen upload_digest_lists rpm_parser
PROGS_EXTENDED := common.o

all: $(PROGS)
@@ -22,3 +22,6 @@ rpm_gen: rpm_gen.c $(PROGS_EXTENDED)

upload_digest_lists: upload_digest_lists.c
$(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS)
+
+rpm_parser: rpm_parser.c $(PROGS_EXTENDED)
+ $(CC) $(CFLAGS) -static $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS)
diff --git a/tools/diglim/rpm_parser.c b/tools/diglim/rpm_parser.c
new file mode 100644
index 000000000000..f8a4b63b2fa8
--- /dev/null
+++ b/tools/diglim/rpm_parser.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Parse RPM header and upload digest list to the kernel.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <fts.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/xattr.h>
+#include <sys/mount.h>
+#include <linux/diglim.h>
+#include <linux/hash_info.h>
+#include <bits/endianness.h>
+
+#include "common.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <linux/byteorder/big_endian.h>
+#else
+#include <linux/byteorder/little_endian.h>
+#endif
+
+#define SYSFS_MNTPOINT "/sys"
+#define SECURITYFS_MNTPOINT SYSFS_MNTPOINT "/kernel/security"
+#define DIGLIM_DIR SECURITYFS_MNTPOINT "/integrity/diglim"
+#define DIGEST_LIST_ADD DIGLIM_DIR "/digest_list_add"
+#define DIGEST_LIST_DEL DIGLIM_DIR "/digest_list_del"
+#define DIGEST_LIST_LABEL DIGLIM_DIR "/digest_list_label"
+#define DIGEST_LIST_DIR "/etc/digest_lists"
+
+#define MOUNT_FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME)
+
+enum hash_algo pgp_algo_mapping[PGPHASHALGO_SHA224 + 1] = {
+ [PGPHASHALGO_MD5] = HASH_ALGO_MD5,
+ [PGPHASHALGO_SHA1] = HASH_ALGO_SHA1,
+ [PGPHASHALGO_SHA224] = HASH_ALGO_SHA224,
+ [PGPHASHALGO_SHA256] = HASH_ALGO_SHA256,
+ [PGPHASHALGO_SHA384] = HASH_ALGO_SHA384,
+ [PGPHASHALGO_SHA512] = HASH_ALGO_SHA512,
+};
+
+struct rpm_hdr {
+ int32_t magic;
+ int32_t reserved;
+ int32_t tags;
+ int32_t datasize;
+} __attribute__((packed));
+
+struct rpm_entryinfo {
+ int32_t tag;
+ int32_t type;
+ int32_t offset;
+ int32_t count;
+} __attribute__((packed));
+
+/* from lib/hexdump.c (Linux kernel) */
+static int hex_to_bin(char ch)
+{
+ if ((ch >= '0') && (ch <= '9'))
+ return ch - '0';
+ ch = tolower(ch);
+ if ((ch >= 'a') && (ch <= 'f'))
+ return ch - 'a' + 10;
+ return -1;
+}
+
+int _hex2bin(unsigned char *dst, const char *src, size_t count)
+{
+ while (count--) {
+ int hi = hex_to_bin(*src++);
+ int lo = hex_to_bin(*src++);
+
+ if ((hi < 0) || (lo < 0))
+ return -1;
+
+ *dst++ = (hi << 4) | lo;
+ }
+ return 0;
+}
+
+static u8 *new_digest_list(enum hash_algo algo, enum compact_types type,
+ u16 modifiers, u32 count)
+{
+ u8 *digest_list;
+ struct compact_list_hdr *hdr;
+
+ digest_list = calloc(1, sizeof(struct compact_list_hdr) +
+ count * hash_digest_size[algo]);
+ if (!digest_list)
+ return NULL;
+
+ hdr = (struct compact_list_hdr *)digest_list;
+ hdr->version = 1;
+ hdr->type = __cpu_to_le16(type);
+ hdr->modifiers = __cpu_to_le16(modifiers);
+ hdr->algo = __cpu_to_le16(algo);
+ return digest_list;
+}
+
+static int upload_digest_list(int add_del_fd, int label_fd, u8 *digest_list,
+ char *label)
+{
+ struct compact_list_hdr *hdr;
+ u32 datalen;
+ ssize_t ret;
+
+ hdr = (struct compact_list_hdr *)digest_list;
+ if (!hdr->count)
+ return 0;
+
+ datalen = hdr->datalen;
+ hdr->count = __cpu_to_le32(hdr->count);
+ hdr->datalen = __cpu_to_le32(hdr->datalen);
+
+ ret = write(label_fd, label, strlen(label) + 1);
+ if (ret < 0 || ret != strlen(label) + 1)
+ return -EIO;
+
+ ret = write(add_del_fd, digest_list, sizeof(*hdr) + datalen);
+ if (ret < 0) {
+ if (errno == EEXIST || errno == ENOENT)
+ return 0;
+
+ return ret;
+ } else if (ret != sizeof(*hdr) + datalen) {
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int parse_rpm(int add_del_fd, int label_fd, char *path,
+ struct stat *st, bool only_immutable)
+{
+ void *buf, *bufp, *bufendp, *datap;
+ struct rpm_hdr *hdr;
+ int32_t tags;
+ struct rpm_entryinfo *entry;
+ void *digests = NULL, *algo_buf = NULL, *modes = NULL, *sizes = NULL;
+ void *dirnames = NULL, *basenames = NULL, *dirindexes = NULL;
+ char **dirnames_ptr = NULL;
+ u8 *digest_list = NULL, *digest_list_immutable = NULL;
+ u32 digests_count = 0, dirnames_count = 0;
+ u16 algo = HASH_ALGO_MD5;
+ char *label = strrchr(path, '/') + 1;
+ int ret = 0, fd_rpm, i, dirname_len;
+
+ const unsigned char rpm_header_magic[8] = {
+ 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+ };
+
+ if (st->st_size < sizeof(*hdr)) {
+ printf("Missing RPM header\n");
+ return -EINVAL;
+ }
+
+ fd_rpm = open(path, O_RDONLY);
+ if (fd_rpm < 0) {
+ printf("Cannot access %s (%d)\n", path, -errno);
+ return -EACCES;
+ }
+
+ buf = bufp = mmap(NULL, st->st_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd_rpm, 0);
+
+ close(fd_rpm);
+
+ if (bufp == MAP_FAILED)
+ return -ENOMEM;
+
+ if (memcmp(bufp, rpm_header_magic, sizeof(rpm_header_magic))) {
+ printf("Invalid RPM header\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ hdr = (struct rpm_hdr *)bufp;
+ tags = __be32_to_cpu(hdr->tags);
+ datap = bufp + sizeof(*hdr) + tags * sizeof(struct rpm_entryinfo);
+ bufendp = bufp + st->st_size;
+ bufp += sizeof(*hdr);
+
+ for (i = 0; i < tags && (bufp + sizeof(*entry)) <= bufendp;
+ i++, bufp += sizeof(*entry)) {
+ entry = bufp;
+
+ switch (__be32_to_cpu(entry->tag)) {
+ case RPMTAG_FILEDIGESTS:
+ digests = datap + __be32_to_cpu(entry->offset);
+ digests_count = __be32_to_cpu(entry->count);
+ break;
+ case RPMTAG_FILEDIGESTALGO:
+ algo_buf = datap + __be32_to_cpu(entry->offset);
+ break;
+ case RPMTAG_DIRNAMES:
+ dirnames = datap + __be32_to_cpu(entry->offset);
+ dirnames_count = __be32_to_cpu(entry->count);
+ break;
+ case RPMTAG_BASENAMES:
+ basenames = datap + __be32_to_cpu(entry->offset);
+ break;
+ case RPMTAG_DIRINDEXES:
+ dirindexes = datap + __be32_to_cpu(entry->offset);
+ break;
+ case RPMTAG_FILEMODES:
+ modes = datap + __be32_to_cpu(entry->offset);
+ break;
+ case RPMTAG_FILESIZES:
+ sizes = datap + __be32_to_cpu(entry->offset);
+ break;
+
+ if (digests && algo_buf && modes && dirnames && basenames &&
+ dirindexes)
+ break;
+ }
+ }
+
+ if (!digests || !modes || !sizes || !dirnames || !basenames ||
+ !dirindexes) {
+ ret = 0;
+ goto out;
+ }
+
+ if (algo_buf && algo_buf + sizeof(u32) <= bufendp)
+ algo = pgp_algo_mapping[__be32_to_cpu(*(u32 *)algo_buf)];
+
+ digest_list = new_digest_list(algo, COMPACT_FILE, 0, digests_count);
+ if (!digest_list) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ digest_list_immutable = new_digest_list(algo, COMPACT_FILE,
+ (1 << COMPACT_MOD_IMMUTABLE),
+ digests_count);
+ if (!digest_list_immutable) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ dirnames_ptr = calloc(dirnames_count, sizeof(*dirnames_ptr));
+ if (!dirnames_ptr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < dirnames_count; i++) {
+ dirname_len = strlen(dirnames) + 1;
+
+ if (dirnames + dirname_len > bufendp) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dirnames_ptr[i] = dirnames;
+ dirnames += dirname_len;
+ }
+
+ for (i = 0; i < digests_count; i++) {
+ int digest_str_len = strlen(digests);
+ int basename_str_len = strlen(basenames);
+ u8 *digest_list_ptr = digest_list;
+ struct compact_list_hdr *cur_hdr;
+ u32 dirindex;
+ u32 size;
+ u16 mode;
+
+ if (digests + digest_str_len * 2 + 1 > bufendp) {
+ printf("RPM header read at invalid offset\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (basenames + basename_str_len + 1 > bufendp) {
+ printf("RPM header read at invalid offset\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (dirindexes + sizeof(u32) > bufendp) {
+ printf("RPM header read at invalid offset\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (sizes + sizeof(u32) > bufendp) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (modes + sizeof(u16) > bufendp) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!digest_str_len) {
+ digests += digest_str_len + 1;
+ basenames += basename_str_len + 1;
+ dirindexes += sizeof(u32);
+ sizes += sizeof(u32);
+ modes += sizeof(u16);
+ continue;
+ }
+
+ dirindex = __be32_to_cpu(*(u32 *)dirindexes);
+ size = __be32_to_cpu(*(u32 *)sizes);
+ mode = __be16_to_cpu(*(u16 *)modes);
+
+ if (((mode & 0111) || !(mode & 0222)) && size)
+ digest_list_ptr = digest_list_immutable;
+
+ if ((strstr(dirnames_ptr[dirindex], "/lib/modules") &&
+ strncmp(basenames, "modules.", 8)) ||
+ strstr(dirnames_ptr[dirindex], "/lib/firmware") ||
+ strstr(dirnames_ptr[dirindex], "/usr/libexec/sudo"))
+ digest_list_ptr = digest_list_immutable;
+
+ if (only_immutable && digest_list_ptr != digest_list_immutable)
+ continue;
+
+ cur_hdr = (struct compact_list_hdr *)digest_list_ptr;
+
+ ret = _hex2bin(digest_list_ptr + sizeof(*cur_hdr) +
+ cur_hdr->count * hash_digest_size[algo],
+ digests, digest_str_len / 2);
+ if (ret < 0)
+ goto out;
+
+ digests += digest_str_len + 1;
+ basenames += basename_str_len + 1;
+ dirindexes += sizeof(u32);
+ sizes += sizeof(u32);
+ modes += sizeof(u16);
+
+ cur_hdr->count++;
+ cur_hdr->datalen += hash_digest_size[algo];
+ }
+
+ ret = upload_digest_list(add_del_fd, label_fd, digest_list, label);
+ if (ret < 0) {
+ printf("Failed to upload digest list\n");
+ goto out;
+ }
+
+ ret = upload_digest_list(add_del_fd, label_fd, digest_list_immutable,
+ label);
+ if (ret < 0)
+ printf("Failed to upload digest list\n");
+out:
+ munmap(buf, st->st_size);
+ free(digest_list);
+ free(digest_list_immutable);
+ free(dirnames_ptr);
+ return ret;
+}
+
+int parse_digest_list(char *name)
+{
+ char *type_start, *format_start, *format_end;
+
+ type_start = strchr(name, '-');
+ if (!type_start++)
+ return 0;
+
+ format_start = strchr(type_start, '-');
+ if (!format_start++)
+ return 0;
+
+ format_end = strchr(format_start, '-');
+ if (!format_end)
+ return 0;
+
+ if (format_end - format_start != 3 ||
+ strncmp(format_start, "rpm", 3))
+ return 0;
+
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ FTS *fts = NULL;
+ FTSENT *ftsent;
+ int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+ char *paths[2] = { NULL, NULL };
+ char *add_del_path = DIGEST_LIST_ADD;
+ char path[PATH_MAX], *path_ptr = path;
+ bool only_immutable = false;
+ struct stat st;
+ int ret, add_del_fd = -1, label_fd = -1;
+
+ if (argc < 3) {
+ printf("Usage: %s add|del <digest list path>\n",
+ argv[0]);
+ return -EINVAL;
+ }
+
+ if (stat(argv[2], &st) == -1)
+ return -ENOENT;
+
+ strncpy(path, argv[2], sizeof(path) - 1);
+
+ if (!S_ISDIR(st.st_mode)) {
+ path_ptr = strrchr(path, '/');
+ if (!path_ptr)
+ path_ptr = path;
+ else
+ path_ptr++;
+ } else {
+ path_ptr = path + strlen(path);
+ }
+
+ strncpy(path_ptr, "/.immutable", sizeof(path) - (path_ptr - path));
+ if (!stat(path, &st))
+ only_immutable = true;
+
+ paths[0] = argv[2];
+
+ if (!strcmp(argv[1], "del"))
+ add_del_path = DIGEST_LIST_DEL;
+
+ add_del_fd = open(add_del_path, O_WRONLY);
+ if (add_del_fd < 0) {
+ printf("Unable to open %s\n", add_del_path);
+ return -EACCES;
+ }
+
+ label_fd = open(DIGEST_LIST_LABEL, O_WRONLY);
+ if (label_fd < 0) {
+ printf("Unable to open %s\n", DIGEST_LIST_LABEL);
+ ret = -EACCES;
+ goto out;
+ }
+
+ fts = fts_open(paths, fts_flags, NULL);
+ if (!fts) {
+ printf("Unable to open %s\n", argv[2]);
+ ret = -EACCES;
+ goto out;
+ }
+
+ while ((ftsent = fts_read(fts)) != NULL) {
+ switch (ftsent->fts_info) {
+ case FTS_F:
+ if (!parse_digest_list(ftsent->fts_name))
+ break;
+
+ ret = parse_rpm(add_del_fd, label_fd,
+ ftsent->fts_path, ftsent->fts_statp,
+ only_immutable);
+ if (ret < 0)
+ printf("Cannot parse %s\n", ftsent->fts_path);
+ break;
+ default:
+ break;
+ }
+ }
+
+out:
+ if (add_del_fd >= 0)
+ close(add_del_fd);
+ if (label_fd >= 0)
+ close(label_fd);
+ if (fts)
+ fts_close(fts);
+
+ return 0;
+}
--
2.25.1

2021-09-15 16:38:48

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH 3/9] diglim: LSM

Introduce a new LSM to keep track of the operations performed by digest
list parsers to convert the digest lists and upload them to the kernel.

The motivation and its behavior is described more in detail in
Documentation/security/diglim/lsm.rst.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/security/diglim/index.rst | 1 +
Documentation/security/diglim/lsm.rst | 65 ++++
MAINTAINERS | 1 +
security/integrity/diglim/Makefile | 2 +-
security/integrity/diglim/diglim.h | 27 ++
security/integrity/diglim/fs.c | 3 +
security/integrity/diglim/hooks.c | 436 ++++++++++++++++++++++++
7 files changed, 534 insertions(+), 1 deletion(-)
create mode 100644 Documentation/security/diglim/lsm.rst
create mode 100644 security/integrity/diglim/hooks.c

diff --git a/Documentation/security/diglim/index.rst b/Documentation/security/diglim/index.rst
index d4ba4ce50a59..bf3cc4a9a91d 100644
--- a/Documentation/security/diglim/index.rst
+++ b/Documentation/security/diglim/index.rst
@@ -12,3 +12,4 @@ Digest Lists Integrity Module (DIGLIM)
implementation
remote_attestation
tests
+ lsm
diff --git a/Documentation/security/diglim/lsm.rst b/Documentation/security/diglim/lsm.rst
new file mode 100644
index 000000000000..ce979e6c6dfd
--- /dev/null
+++ b/Documentation/security/diglim/lsm.rst
@@ -0,0 +1,65 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+LSM
+===
+
+When digest lists (in compact format) are directly uploaded by the kernel,
+determining their integrity is straightforward, as a file open is the only
+operation performed.
+
+However, if digest lists are first processed by a user space parser, many
+operations occur before the converted digest list is uploaded to the
+kernel, and any of them may affect the result of the conversion. In this
+case, the integrity of all files involved must be evaluated to ensure that
+the output is the expected one.
+
+The new DIGLIM LSM has been introduced with two goals: the first is to
+identify user space parsers as soon as they are loaded, in order to monitor
+the operations they perform; the second is to avoid interference from other
+processes, which are assumed as untrusted.
+
+Regarding the first goal, user space parsers are identified by calculating
+the digest of their executable and searching it in the DIGLIM hash table.
+An executable is successfully recognized as a digest list parser if its
+digest is found and the associated type is COMPACT_PARSER. Once a parser
+has been identified, DIGLIM LSM monitors the integrity of opened files. In
+addition, it also denies access to ld.so.cache, to avoid an unknown
+measurement or appraisal failure, and to files without content measurable
+by IMA (e.g. character devices).
+
+The integrity status of the parser, a set of flags representing the
+operations performed by IMA, is kept in the credentials of the process
+identified as parser. Initially, the flags are set from the operations done
+on the executable and they are AND-ed with the flags retrieved at each file
+open (which themselves are set from the operations done by IMA on that
+file). This ensures that even if one file was not processed, this is
+reflected in the global integrity status of the parser. Given that the AND
+operation prevents the cleared flag to be set again, the only way to upload
+a converted digest list with that flag is to restart the parser.
+
+The flags still set in the process credentials at the time the parser
+uploads the converted digest lists are then copied to the converted lists
+themselves, so that they can be retrieved by DIGLIM users during a digest
+query and evaluated (the query result might be discarded). This mechanism
+is reliable against LSM misconfiguration: if for any reason DIGLIM LSM is
+turned off, no flags will be set in the converted digest list.
+
+Regarding the second goal, avoiding interference from other user space
+processes is necessary if they are assumed to be untrusted. This threat
+model applies if the system is supposed to enforce a mandatory policy where
+only files shipped by software vendors are allowed to be accessed. The
+mandatory policy could be also defined by system administrators (they could
+decide the set of approved software vendors).
+
+To avoid interference to the user space parsers from other processes, the
+following countermeasures are implemented. First, files accessed by user
+space parsers are exclusively write-locked until the parsers finish to use
+them. A failure when write-locking a file (if the file was already opened
+for writing by another process) will result in the file access to be denied
+to the parser. Second, ptraces on the parsers are also denied as they might
+influence their execution.
+
+Other than these two limitations (not being able to access files
+write-locked by the parsers and to ptrace the parsers), processes which are
+not identified as parsers are not subject to the policy enforcement by
+DIGLIM LSM.
diff --git a/MAINTAINERS b/MAINTAINERS
index 0ffceb271803..94220e40b7e2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5511,6 +5511,7 @@ F: Documentation/security/diglim/architecture.rst
F: Documentation/security/diglim/implementation.rst
F: Documentation/security/diglim/index.rst
F: Documentation/security/diglim/introduction.rst
+F: Documentation/security/diglim/lsm.rst
F: Documentation/security/diglim/remote_attestation.rst
F: Documentation/security/diglim/tests.rst
F: include/linux/diglim.h
diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile
index ae79a3317ec8..37fa6ef2a73c 100644
--- a/security/integrity/diglim/Makefile
+++ b/security/integrity/diglim/Makefile
@@ -5,4 +5,4 @@

obj-$(CONFIG_DIGLIM) += diglim.o

-diglim-y := methods.o parser.o ima.o fs.o loader.o
+diglim-y := methods.o parser.o ima.o fs.o loader.o hooks.o
diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h
index c597c2e7a52a..b53de803a63c 100644
--- a/security/integrity/diglim/diglim.h
+++ b/security/integrity/diglim/diglim.h
@@ -21,6 +21,7 @@
#include <crypto/hash_info.h>
#include <linux/hash_info.h>
#include <linux/diglim.h>
+#include <linux/lsm_hooks.h>

#include "../integrity.h"

@@ -28,6 +29,13 @@
#define HASH_BITS 10
#define DIGLIM_HTABLE_SIZE (1 << HASH_BITS)

+#define FLAG_PARSER_EXEC 0x01
+#define FLAG_PARSER_FILE_ACCESS 0x02
+#define FLAG_PARSER_FILE_ACCESS_DENY 0x04
+
+extern struct lsm_blob_sizes diglim_lsm_blob_sizes;
+extern int diglim_lsm_enabled;
+
/**
* struct digest_list_item - a digest list loaded into the kernel
*
@@ -229,4 +237,23 @@ int diglim_ima_get_info(struct file *file, u8 *buffer, size_t buffer_len,
enum hash_algo *algo, u8 *actions);

ssize_t digest_list_read(struct path *root, char *path, enum ops op);
+static inline u8 *diglim_cred_actions(const struct cred *cred)
+{
+ return cred->security + diglim_lsm_blob_sizes.lbs_cred;
+}
+
+static inline u8 *diglim_cred_flags(const struct cred *cred)
+{
+ return diglim_cred_actions(cred) + 1;
+}
+
+static inline u8 *diglim_inode(const struct inode *inode)
+{
+ return inode->i_security + diglim_lsm_blob_sizes.lbs_inode;
+}
+
+static inline u8 *diglim_file(const struct file *file)
+{
+ return file->f_security + diglim_lsm_blob_sizes.lbs_file;
+}
#endif /*__DIGLIM_INTERNAL_H*/
diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c
index 467ff4f7c0ce..56c6f7ff2b3c 100644
--- a/security/integrity/diglim/fs.c
+++ b/security/integrity/diglim/fs.c
@@ -577,6 +577,9 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
enum hash_algo algo;
u8 actions = 0;

+ if (diglim_lsm_enabled)
+ actions = *diglim_cred_actions(current_cred());
+
/* No partial writes. */
result = -EINVAL;
if (*ppos != 0)
diff --git a/security/integrity/diglim/hooks.c b/security/integrity/diglim/hooks.c
new file mode 100644
index 000000000000..f186ffbac628
--- /dev/null
+++ b/security/integrity/diglim/hooks.c
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Functions to evaluate the integrity of converted digest lists.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/cred.h>
+#include <linux/lsm_hooks.h>
+#include <linux/binfmts.h>
+#include <linux/xattr.h>
+#include <linux/file.h>
+#include <linux/fdtable.h>
+#include <linux/sched/mm.h>
+#include <linux/ima.h>
+#include <uapi/linux/magic.h>
+
+#include "diglim.h"
+
+int diglim_lsm_enabled;
+
+/**
+ * diglim_file_read_with_ima - read the file with IMA
+ * @file: file descriptor of the file being accessed
+ *
+ * This function opens the file again so that it can pass the file descriptor to
+ * IMA for reading the file. It also marks the inode so that diglim_file_open()
+ * recognizes that the open was made by DIGLIM LSM and does not process it.
+ *
+ * NOTE: the second open might be not necessary, depending on the IMA policy;
+ * however, since the second open is always performed with type
+ * READING_DIGEST_LIST, it can be easily monitored by IMA with a rule with
+ * func=DIGEST_LIST_CHECK
+ *
+ * Return: 0 on success, a negative value on error
+ */
+static int diglim_file_read_with_ima(struct file *file)
+{
+ struct inode *inode = file_inode(file);
+ u8 *inode_flags = diglim_inode(inode);
+ void *datap = NULL;
+ struct file *f;
+ int ret, flags;
+
+ /* Taken from ima_crypto.c. */
+ flags = file->f_flags & ~(O_WRONLY | O_APPEND | O_TRUNC | O_CREAT |
+ O_NOCTTY | O_EXCL);
+ flags |= O_RDONLY;
+
+ /* Signal to diglim_file_open() to not evaluate the second open. */
+ *inode_flags |= FLAG_PARSER_FILE_ACCESS;
+ /* Open an additional file descriptor to use with ima_read_file(). */
+ f = dentry_open(&file->f_path, flags, file->f_cred);
+ *inode_flags &= ~FLAG_PARSER_FILE_ACCESS;
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ /*
+ * Pass the file descriptor to IMA with file type READING_DIGEST_LIST,
+ * so that this operation can be more easily identified with an IMA rule
+ * with func=DIGEST_LIST_CHECK.
+ */
+ ret = kernel_read_file(f, 0, &datap, INT_MAX, NULL,
+ READING_DIGEST_LIST);
+ if (ret >= 0)
+ vfree(datap);
+
+ fput(f);
+ return ret;
+}
+
+/**
+ * diglim_identify_parser - identify a digest list parser from the executable
+ * @cred: credentials of the child process
+ * @file: file descriptor of the child process executable
+ *
+ * This function first identifies a digest list parser from the executable, by
+ * searching the executable digest in the DIGLIM hash table. A parser is
+ * successfully identified if the digest is found in the hash table and if the
+ * type of the found digest is COMPACT_PARSER.
+ *
+ * If a parser has been successfully identified, this function also sets the
+ * actions performed by IMA on the executable in the process credentials. These
+ * initial actions will be AND-ed with the actions performed by IMA on each
+ * regular file opened by the parser. A missing action for a file will cause
+ * the corresponding action in the process credentials to be irreversibly
+ * cleared.
+ *
+ * Updating the actions in the process credentials in this way makes it clear
+ * whether or not the parser is suitable for use for a particular integrity goal
+ * (measurement or appraisal). If an action is set, it means that the executable
+ * and the process inputs have been evaluated for a particular goal, and from
+ * this it can be inferred that the output of the process is also correct.
+ *
+ * Return: 0 if a digest list parser has been successfully identified, a
+ * negative value otherwise
+ */
+static int diglim_identify_parser(const struct cred *cred, struct file *file)
+{
+ struct integrity_iint_cache *iint;
+ struct inode *inode = file_inode(file);
+ u8 *parser_actions = diglim_cred_actions(cred);
+ u8 digest[IMA_MAX_DIGEST_SIZE];
+ enum hash_algo algo = HASH_ALGO__LAST;
+ u16 modifiers = 0;
+ u8 digest_list_actions = 0, file_actions = 0;
+ int ret = -ENOENT;
+
+ /* Read the file with IMA. */
+ ret = diglim_file_read_with_ima(file);
+ if (ret < 0)
+ return ret;
+
+ iint = integrity_iint_find(inode);
+ if (!iint)
+ return ret;
+
+ /*
+ * Since executables are write-protected, information obtained from IMA
+ * (digest and actions performed on the executable) are safe to use
+ * without the risk of races with writers.
+ */
+ mutex_lock(&iint->mutex);
+ if (!(iint->flags & IMA_COLLECTED)) {
+ mutex_unlock(&iint->mutex);
+ goto out;
+ }
+
+ /* Query the executable digest to determine if it is a parser. */
+ ret = diglim_digest_get_info(iint->ima_hash->digest,
+ iint->ima_hash->algo, COMPACT_PARSER,
+ &modifiers, &digest_list_actions);
+
+ mutex_unlock(&iint->mutex);
+
+ if (ret < 0)
+ goto out;
+
+ /* Obtain the flags from IMA operations on the executable. */
+ ret = diglim_ima_get_info(file, NULL, 0, NULL, digest, sizeof(digest),
+ &algo, &file_actions);
+ if (!ret)
+ *parser_actions |= file_actions;
+
+ pr_debug("%s: task: %d(%s), parser initial actions: %d\n", __func__,
+ current->pid, current->comm, *parser_actions);
+out:
+ return ret;
+}
+
+/**
+ * diglim_bprm_committing_creds - implement the bprm_committing_creds hook
+ * @bprm: linux_binprm structure of the file being executed
+ *
+ * This function implements the bprm_committing_creds hook, to identify a digest
+ * list parser from the digest of the executable. After a successful
+ * identification, the FLAG_PARSER_EXEC flag is set in the process credentials,
+ * so that the diglim_file_open() hook below knows that it should enforce the
+ * parser policy.
+ */
+static void diglim_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ u8 *parser_flags = diglim_cred_flags(bprm->cred);
+ int ret;
+
+ /*
+ * Try to identify the parsers if the parent directory is named diglim.
+ */
+ if (strcmp(file_dentry(bprm->file)->d_parent->d_name.name, "diglim"))
+ return;
+
+ ret = diglim_identify_parser(bprm->cred, bprm->file);
+ if (ret < 0)
+ return;
+
+ *parser_flags |= FLAG_PARSER_EXEC;
+}
+
+/**
+ * diglim_file_open_check - check file access and if IMA eval is required
+ * @file: file descriptor of the file being accessed
+ *
+ * This function checks a file access and determines whether or not it is safe
+ * for the parser to access the file. Access (read) is considered safe if the
+ * file is in a trusted filesystem (procfs, securityfs), does not have content
+ * to be read or it has content that can be measured/appraised by IMA, and there
+ * are no concurrent writes.
+ *
+ * NOTE: access to ld.so.cache, although it can be allowed, is instead denied to
+ * avoid an unknown measurement in the measurement list or appraisal
+ * failure.
+ *
+ * This function also determines whether an IMA evaluation is required.
+ *
+ * The following table summarizes the policy enforced on the parsers.
+ *
+ * Not parser parser
+ * +-----------+--------------+-----------------------+-----------------------+
+ * | operation | file type | | |
+ * +-----------+--------------+ | |
+ * | | ld.so.cache | allow | allow |
+ * | | reg | allow [1] | allow |
+ * | write | dir/link | allow | allow |
+ * | | procfs/secfs | allow | allow |
+ * | | char/block/ | allow | allow |
+ * | | socket/fifo | | |
+ * +-----------+--------------+-----------------------+-----------------------+
+ * | | ld.so.cache | allow | deny |
+ * | | reg | allow | allow [2] + IMA eval |
+ * | read | dir/link | allow | allow |
+ * | | procfs/secfs | allow | allow |
+ * | | char/block/ | allow | deny |
+ * | | socket/fifo | | |
+ * +-----------+--------------+-----------------------+-----------------------+
+ *
+ * [1]: if not write-locked by the parser
+ * [2]: if there are no concurrent writes
+ *
+ * Return: 0 if access is allowed but IMA eval is not required, -EPERM if access
+ * is denied and 1 if access is allowed and IMA eval is required.
+ */
+static int diglim_file_open_check(struct file *file)
+{
+ struct inode *inode = file_inode(file);
+
+ /* Skip non-read operations. */
+ if (!(file->f_mode & FMODE_READ))
+ return 0;
+
+ /* Deny access to ld.so.cache. */
+ if (!strcmp(file_dentry(file)->d_name.name, "ld.so.cache"))
+ return -EPERM;
+
+ /* Deny access to files that can have content but cannot be measured. */
+ if (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode) &&
+ !S_ISDIR(inode->i_mode))
+ return -EPERM;
+
+ /* Allow access to dirs and symlinks. */
+ if (!S_ISREG(inode->i_mode))
+ return 0;
+
+ /* Allow access to files in procfs and securityfs. */
+ if (inode->i_sb->s_magic == PROC_SUPER_MAGIC ||
+ inode->i_sb->s_magic == SECURITYFS_MAGIC)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * diglim_file_lock - write-lock the file before retrieving IMA actions
+ * @file: file descriptor of the file being locked
+ *
+ * This function write-locks the file being accessed in order to safely retrieve
+ * IMA actions and ensure that there are no concurrent writes, which would cause
+ * the retrieved actions to be outdated.
+ *
+ * Since IMA eventually resets the actions only when the file is closed, getting
+ * the exclusive write-lock ensures that the actions are up to date (for the
+ * exclusive write-lock to succeed there must be no pending writes, which means
+ * that IMA already updated the actions in ima_check_last_writer()).
+ *
+ * Return: 0 on success, -ETXTBSY if the file cannot be write-locked
+ */
+static int diglim_file_lock(struct file *file)
+{
+ u8 *file_flags = diglim_file(file);
+ int ret;
+
+ /* Write-lock the file until the parser finishes to use it. */
+ ret = deny_write_access(file);
+ if (ret < 0)
+ return ret;
+
+ /* Grab a reference to the inode to avoid free before unlocking it. */
+ igrab(file_inode(file));
+
+ /* Record deny_write_access() call in the file descriptor. */
+ *file_flags |= FLAG_PARSER_FILE_ACCESS_DENY;
+ return 0;
+}
+
+/**
+ * diglim_file_unlock - unlock the file locked by DIGLIM LSM
+ * @file: file descriptor of the file being unlocked
+ *
+ * This function first checks if the file was locked by DIGLIM LSM, and if yes,
+ * unlocks it.
+ */
+static void diglim_file_unlock(struct file *file)
+{
+ u8 *file_flags;
+
+ file_flags = diglim_file(file);
+
+ /* File not write-locked by DIGLIM LSM. */
+ if (!(*file_flags & FLAG_PARSER_FILE_ACCESS_DENY))
+ return;
+
+ /* Release write-lock. */
+ allow_write_access(file);
+
+ /* Clear flag. */
+ *file_flags &= ~FLAG_PARSER_FILE_ACCESS_DENY;
+
+ /* Release reference taken in diglim_file_lock(). */
+ iput(file_inode(file));
+}
+
+/**
+ * diglim_file_open - check file access and update process actions
+ * @file: file descriptor of the file being accessed
+ *
+ * This function checks the file being accessed by the parser and does an AND
+ * of the actions in the parser process credentials with the actions done by IMA
+ * on that file.
+ *
+ * The actions in the parser process credentials will be then copied to the
+ * converted digest lists uploaded by the parser.
+ *
+ * Return: 0 if access is allowed, a negative value if access is denied
+ */
+static int diglim_file_open(struct file *file)
+{
+ u8 file_actions = 0;
+ u8 *parser_flags = diglim_cred_flags(current_cred());
+ u8 *parser_actions = diglim_cred_actions(current_cred());
+ u8 initial_parser_actions = *parser_actions;
+ struct inode *inode = file_inode(file);
+ u8 *inode_flags = diglim_inode(inode);
+ int ret;
+
+ /* Skip processes that are not the parser. */
+ if (!(*parser_flags & FLAG_PARSER_EXEC))
+ return 0;
+
+ /* Skip calls due to dentry_open() in diglim_file_read_with_ima(). */
+ if (*inode_flags & FLAG_PARSER_FILE_ACCESS)
+ return 0;
+
+ /* Check file access. */
+ ret = diglim_file_open_check(file);
+ if (ret <= 0)
+ return ret;
+
+ /* Try to write-lock the file. */
+ ret = diglim_file_lock(file);
+ if (ret < 0)
+ return ret;
+
+ /* Read the file with IMA. */
+ ret = diglim_file_read_with_ima(file);
+ if (ret < 0) {
+ diglim_file_unlock(file);
+ return ret;
+ }
+
+ /* Retrieve the actions performed by IMA on the file. */
+ diglim_ima_get_info(file, NULL, 0, NULL, NULL, 0, NULL, &file_actions);
+
+ /* Do an AND of the parser process actions with the file actions. */
+ *parser_actions &= file_actions;
+
+ if (*parser_actions != initial_parser_actions)
+ pr_err("%s: task: %d(%s), cleared parser actions, file: %s, old flags: %d, new flags: %d\n",
+ __func__, current->pid, current->comm,
+ file_dentry(file)->d_name.name, initial_parser_actions,
+ *parser_actions);
+
+ return 0;
+}
+
+/**
+ * diglim_file_free - unlock files accessed by the parser
+ * @file: file descriptor of the file being monitored
+ *
+ * This function unlocks files accessed by the parser.
+ */
+void diglim_file_free(struct file *file)
+{
+ diglim_file_unlock(file);
+}
+
+/**
+ * diglim_ptrace_access_check - deny ptraces on the parser process
+ * @child: task being ptraced
+ * @mode: ptrace mode
+ *
+ * This function denies ptraces on the parser process.
+ *
+ * Return: 0 if the ptrace is not done on the parser process, -EACCES otherwise
+ */
+static int diglim_ptrace_access_check(struct task_struct *child,
+ unsigned int mode)
+{
+ const struct cred *cred = get_task_cred(child);
+ u8 *flags = diglim_cred_flags(cred);
+
+ /* Deny ptraces to the parser. */
+ if (*flags & FLAG_PARSER_EXEC)
+ return -EACCES;
+
+ return 0;
+}
+
+static struct security_hook_list diglim_lsm_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(bprm_committing_creds, diglim_bprm_committing_creds),
+ LSM_HOOK_INIT(file_open, diglim_file_open),
+ LSM_HOOK_INIT(file_free_security, diglim_file_free),
+ LSM_HOOK_INIT(ptrace_access_check, diglim_ptrace_access_check),
+};
+
+static int __init diglim_lsm_init(void)
+{
+ diglim_lsm_enabled = 1;
+ security_add_hooks(diglim_lsm_hooks, ARRAY_SIZE(diglim_lsm_hooks),
+ "diglim");
+ return 0;
+}
+
+struct lsm_blob_sizes diglim_lsm_blob_sizes __lsm_ro_after_init = {
+ .lbs_cred = 2 * sizeof(u8),
+ .lbs_inode = sizeof(u8),
+ .lbs_file = sizeof(u8),
+};
+
+DEFINE_LSM(diglim) = {
+ .name = "diglim",
+ .init = diglim_lsm_init,
+ .blobs = &diglim_lsm_blob_sizes,
+};
--
2.25.1