diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst
index 85a34a5ad7ce..8f245fae6825 100644
--- a/Documentation/security/digest_lists.rst
+++ b/Documentation/security/digest_lists.rst
@@ -672,3 +672,14 @@ policy, to measure it.
 ``digest_list_del`` can be used to upload a digest list and delete the
 digests from the hash table; data are interpreted in the same way as
 described for ``digest_list_add``.
+
+
+``digest_lists_loaded``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+``digest_lists_loaded`` is a directory containing two files for each
+loaded digest list: one shows the digest list in binary format, and the
+other (with .ascii prefix) shows the digest list in ASCII format.
+
+Files are added and removed at the same time digest lists are added and
+removed.
diff --git a/security/integrity/digest_lists/fs.c b/security/integrity/digest_lists/fs.c
index b407c5f7b659..f665ef063df7 100644
--- a/security/integrity/digest_lists/fs.c
+++ b/security/integrity/digest_lists/fs.c
@@ -29,9 +29,250 @@
 
 #include "digest_lists.h"
 
+#define HDR_ASCII_FMT \
+	"actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n"
+
 static struct dentry *digest_lists_dir;
+static struct dentry *digest_lists_loaded_dir;
 static struct dentry *digest_list_add_dentry;
 static struct dentry *digest_list_del_dentry;
+char digest_label[NAME_MAX + 1];
+
+static int parse_digest_list_filename(const char *digest_list_filename,
+				      u8 *digest, enum hash_algo *algo)
+{
+	int i;
+
+	for (i = 0; i < HASH_ALGO__LAST; i++)
+		if (!strncmp(digest_list_filename, hash_algo_name[i],
+			     strlen(hash_algo_name[i])))
+			break;
+
+	if (i == HASH_ALGO__LAST)
+		return -ENOENT;
+
+	*algo = i;
+	return hex2bin(digest, strchr(digest_list_filename, '-') + 1,
+		       hash_digest_size[*algo]);
+}
+
+/* returns pointer to hlist_node */
+static void *digest_list_start(struct seq_file *m, loff_t *pos)
+{
+	struct digest_item *d;
+	u8 digest[IMA_MAX_DIGEST_SIZE];
+	enum hash_algo algo;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf, *bufp, *bufendp;
+	int ret;
+
+	ret = parse_digest_list_filename(file_dentry(m->file)->d_name.name,
+					 digest, &algo);
+	if (ret < 0)
+		return NULL;
+
+	d = digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL);
+	if (!d)
+		return NULL;
+
+	bufp = buf = d->refs[0].digest_list->buf;
+	bufendp = bufp + d->refs[0].digest_list->size;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+		count += hdr->count;
+		bufp += sizeof(*hdr) + hdr->datalen;
+	}
+
+	return *pos <= count ? d : NULL;
+}
+
+static void *digest_list_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct digest_item *d = (struct digest_item *)v;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf = d->refs[0].digest_list->buf;
+	void *bufp = buf;
+	void *bufendp = bufp + d->refs[0].digest_list->size;
+
+	(*pos)++;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+		count += hdr->count;
+		bufp += sizeof(*hdr) + hdr->datalen;
+	}
+
+	return *pos <= count ? d : NULL;
+}
+
+static void digest_list_stop(struct seq_file *m, void *v)
+{
+}
+
+static void print_digest(struct seq_file *m, u8 *digest, u32 size)
+{
+	u32 i;
+
+	for (i = 0; i < size; i++)
+		seq_printf(m, "%02x", *(digest + i));
+}
+
+static void digest_list_putc(struct seq_file *m, void *data, int datalen)
+{
+	while (datalen--)
+		seq_putc(m, *(char *)data++);
+}
+
+static int digest_list_show_common(struct seq_file *m, void *v, bool binary)
+{
+	struct digest_item *d = (struct digest_item *)v;
+	struct compact_list_hdr *hdr;
+	u32 count = 0;
+	void *buf = d->refs[0].digest_list->buf;
+	void *bufp = buf;
+	void *bufendp = bufp + d->refs[0].digest_list->size;
+
+	while (bufp < bufendp) {
+		hdr = (struct compact_list_hdr *)bufp;
+
+		if (m->index >= count + hdr->count) {
+			bufp += sizeof(*hdr) + hdr->datalen;
+			count += hdr->count;
+			continue;
+		}
+
+		if (count == m->index) {
+			if (binary)
+				digest_list_putc(m, (void *)hdr, sizeof(*hdr));
+			else
+				seq_printf(m, HDR_ASCII_FMT,
+					   d->refs[0].digest_list->actions,
+					   hdr->version,
+					   hash_algo_name[hdr->algo], hdr->type,
+					   hdr->modifiers, hdr->count,
+					   hdr->datalen);
+		}
+
+		if (binary) {
+			digest_list_putc(m, bufp + sizeof(*hdr) +
+					 (m->index - count) *
+					 hash_digest_size[hdr->algo],
+					 hash_digest_size[hdr->algo]);
+		} else {
+			print_digest(m, bufp + sizeof(*hdr) +
+				     (m->index - count) *
+				     hash_digest_size[hdr->algo],
+				     hash_digest_size[hdr->algo]);
+			seq_puts(m, "\n");
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+static int digest_list_show(struct seq_file *m, void *v)
+{
+	return digest_list_show_common(m, v, true);
+}
+
+static int digest_list_ascii_show(struct seq_file *m, void *v)
+{
+	return digest_list_show_common(m, v, false);
+}
+
+static const struct seq_operations digest_list_seqops = {
+	.start = digest_list_start,
+	.next = digest_list_next,
+	.stop = digest_list_stop,
+	.show = digest_list_show
+};
+
+static int digest_list_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &digest_list_seqops);
+}
+
+static const struct file_operations digest_list_ops = {
+	.open = digest_list_seq_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static const struct seq_operations digest_list_ascii_seqops = {
+	.start = digest_list_start,
+	.next = digest_list_next,
+	.stop = digest_list_stop,
+	.show = digest_list_ascii_show
+};
+
+static int digest_list_ascii_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &digest_list_ascii_seqops);
+}
+
+static const struct file_operations digest_list_ascii_ops = {
+	.open = digest_list_ascii_seq_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+static int digest_list_get_secfs_files(char *label, u8 *digest,
+				       enum hash_algo algo, enum ops op,
+				       struct dentry **dentry,
+				       struct dentry **dentry_ascii)
+{
+	char digest_list_filename[NAME_MAX + 1] = { 0 };
+	u8 digest_str[IMA_MAX_DIGEST_SIZE * 2 + 1] = { 0 };
+	char *dot, *label_ptr;
+
+	label_ptr = strrchr(label, '/');
+	if (label_ptr)
+		label = label_ptr + 1;
+
+	bin2hex(digest_str, digest, hash_digest_size[algo]);
+
+	snprintf(digest_list_filename, sizeof(digest_list_filename),
+		 "%s-%s-%s.ascii", hash_algo_name[algo], digest_str, label);
+
+	dot = strrchr(digest_list_filename, '.');
+
+	*dot = '\0';
+	if (op == DIGEST_LIST_ADD)
+		*dentry = securityfs_create_file(digest_list_filename, 0440,
+						 digest_lists_loaded_dir, NULL,
+						 &digest_list_ops);
+	else
+		*dentry = lookup_positive_unlocked(digest_list_filename,
+						digest_lists_loaded_dir,
+						strlen(digest_list_filename));
+	*dot = '.';
+	if (IS_ERR(*dentry))
+		return PTR_ERR(*dentry);
+
+	if (op == DIGEST_LIST_ADD)
+		*dentry_ascii = securityfs_create_file(digest_list_filename,
+						0440, digest_lists_loaded_dir,
+						NULL, &digest_list_ascii_ops);
+	else
+		*dentry_ascii = lookup_positive_unlocked(digest_list_filename,
+						digest_lists_loaded_dir,
+						strlen(digest_list_filename));
+	if (IS_ERR(*dentry_ascii)) {
+		if (op == DIGEST_LIST_ADD)
+			securityfs_remove(*dentry);
+
+		return PTR_ERR(*dentry_ascii);
+	}
+
+	return 0;
+}
 
 static ssize_t digest_list_read(char *path, enum ops op)
 {
@@ -43,6 +284,7 @@ static ssize_t digest_list_read(char *path, enum ops op)
 	struct file *file;
 	u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
 	enum hash_algo algo = HASH_ALGO__LAST;
+	struct dentry *dentry, *dentry_ascii;
 	int rc, pathlen = strlen(path);
 
 	/* remove \n */
@@ -74,9 +316,27 @@ static ssize_t digest_list_read(char *path, enum ops op)
 	if (measured)
 		actions |= COMPACT_ACTION_IMA_MEASURED;
 
-	rc = digest_list_parse(size, data, op, actions, digest, algo, "");
+	rc = digest_list_get_secfs_files(path, digest, algo, op, &dentry,
+					 &dentry_ascii);
+	if (rc < 0)
+		goto out_vfree;
+
+	rc = digest_list_parse(size, data, op, actions, digest, algo,
+			       dentry->d_name.name);
 	if (rc < 0)
 		pr_err("unable to upload digest list %s (%d)\n", path, rc);
+
+	if ((rc < 0 && op == DIGEST_LIST_ADD) ||
+	    (rc == size && op == DIGEST_LIST_DEL)) {
+		/* Release reference taken in digest_list_get_secfs_files(). */
+		if (op == DIGEST_LIST_DEL) {
+			dput(dentry);
+			dput(dentry_ascii);
+		}
+
+		securityfs_remove(dentry);
+		securityfs_remove(dentry_ascii);
+	}
 out_vfree:
 	vfree(data);
 out:
@@ -94,7 +354,7 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
 	char *data;
 	ssize_t result;
 	enum ops op = DIGEST_LIST_ADD;
-	struct dentry *dentry = file_dentry(file);
+	struct dentry *dentry = file_dentry(file), *dentry_ascii;
 	u8 digest[IMA_MAX_DIGEST_SIZE];
 	enum hash_algo algo = HASH_ALGO__LAST;
 	u8 actions = 0;
@@ -135,12 +395,36 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
 		if (measured)
 			actions |= COMPACT_ACTION_IMA_MEASURED;
 
+		result = digest_list_get_secfs_files(digest_label[0] != '\0' ?
+						     digest_label : "parser",
+						     digest, algo, op,
+						     &dentry, &dentry_ascii);
+		if (result < 0)
+			goto out_kfree;
+
+		memset(digest_label, 0, sizeof(digest_label));
+
 		result = digest_list_parse(datalen, data, op, actions, digest,
-					   algo, "");
+					   algo, dentry->d_name.name);
 		if (result != datalen) {
 			pr_err("unable to upload generated digest list\n");
 			result = -EINVAL;
 		}
+
+		if ((result < 0 && op == DIGEST_LIST_ADD) ||
+		    (result == datalen && op == DIGEST_LIST_DEL)) {
+			/*
+			 * Release reference taken in
+			 * digest_list_get_secfs_files().
+			 */
+			if (op == DIGEST_LIST_DEL) {
+				dput(dentry);
+				dput(dentry_ascii);
+			}
+
+			securityfs_remove(dentry);
+			securityfs_remove(dentry_ascii);
+		}
 	}
 out_kfree:
 	kfree(data);
@@ -181,12 +465,17 @@ static const struct file_operations digest_list_upload_ops = {
 	.llseek = generic_file_llseek,
 };
 
-int __init digest_lists_fs_init(void)
+static int __init digest_lists_fs_init(void)
 {
 	digest_lists_dir = securityfs_create_dir("digest_lists", integrity_dir);
 	if (IS_ERR(digest_lists_dir))
 		return -1;
 
+	digest_lists_loaded_dir = securityfs_create_dir("digest_lists_loaded",
+							digest_lists_dir);
+	if (IS_ERR(digest_lists_loaded_dir))
+		goto out;
+
 	digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
 						digest_lists_dir, NULL,
 						&digest_list_upload_ops);
@@ -203,6 +492,7 @@ int __init digest_lists_fs_init(void)
 out:
 	securityfs_remove(digest_list_del_dentry);
 	securityfs_remove(digest_list_add_dentry);
+	securityfs_remove(digest_lists_loaded_dir);
 	securityfs_remove(digest_lists_dir);
 	return -1;
 }
