@@ -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).
@@ -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
@@ -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);
@@ -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
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 <roberto.sassu@huawei.com> --- 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(-)