diff --git a/security/integrity/diglim/fs.c b/security/integrity/diglim/fs.c
index 07a0d75a0e33..caf2923c9146 100644
--- a/security/integrity/diglim/fs.c
+++ b/security/integrity/diglim/fs.c
@@ -25,6 +25,17 @@
#define MAX_DIGEST_LIST_SIZE (64 * 1024 * 1024 - 1)
static struct dentry *diglim_dir;
+/**
+ * DOC: 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.
+ */
+static struct dentry *digest_lists_loaded_dir;
/**
* DOC: digest_list_add
*
@@ -47,6 +58,245 @@ 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]);
+}
+
+/* *pos is the offset of the digest list data to show. */
+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 digest_list_item *digest_list;
+ int ret;
+
+ if (m->private) {
+ digest_list = (struct digest_list_item *)m->private;
+
+ if (*pos == digest_list->size)
+ return NULL;
+
+ return digest_list->buf + *pos;
+ }
+
+ 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;
+
+ digest_list = list_first_entry(&d->refs,
+ struct digest_list_item_ref, list)->digest_list;
+ m->private = digest_list;
+ return digest_list->buf;
+}
+
+static void *digest_list_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct compact_list_hdr *hdr;
+ struct digest_list_item *digest_list =
+ (struct digest_list_item *)m->private;
+ void *bufp = digest_list->buf;
+ bool is_header = false;
+
+ /* Determine if v points to a header or a digest. */
+ while (bufp <= v) {
+ hdr = (struct compact_list_hdr *)bufp;
+ if (bufp == v) {
+ is_header = true;
+ break;
+ }
+
+ bufp += sizeof(*hdr) + hdr->datalen;
+ }
+
+ if (is_header)
+ *pos += sizeof(*hdr);
+ else
+ *pos += hash_digest_size[hdr->algo];
+
+ if (*pos == digest_list->size)
+ return NULL;
+
+ return digest_list->buf + *pos;
+}
+
+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 compact_list_hdr *hdr;
+ struct digest_list_item *digest_list =
+ (struct digest_list_item *)m->private;
+ void *bufp = digest_list->buf;
+ bool is_header = false;
+
+ /* Determine if v points to a header or a digest. */
+ while (bufp <= v) {
+ hdr = (struct compact_list_hdr *)bufp;
+ if (bufp == v) {
+ is_header = true;
+ break;
+ }
+
+ bufp += sizeof(*hdr) + hdr->datalen;
+ }
+
+ if (is_header) {
+ if (binary)
+ digest_list_putc(m, v, sizeof(*hdr));
+ else
+ seq_printf(m,
+ "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n",
+ digest_list->actions, hdr->version,
+ hash_algo_name[hdr->algo], hdr->type,
+ hdr->modifiers, hdr->count, hdr->datalen);
+ return 0;
+ }
+
+ if (binary) {
+ digest_list_putc(m, v, hash_digest_size[hdr->algo]);
+ } else {
+ print_digest(m, v, hash_digest_size[hdr->algo]);
+ seq_puts(m, "\n");
+ }
+
+ 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;
+}
+
/*
* digest_list_read: read and parse the digest list from the path
*/@@ -60,6 +310,7 @@ static ssize_t digest_list_read(char *path, enum ops op)
char event_name[NAME_MAX + 9 + 1];
u8 digest[IMA_MAX_DIGEST_SIZE] = { 0 };
enum hash_algo algo;
+ struct dentry *dentry, *dentry_ascii;
int rc, pathlen = strlen(path);
/* Remove \n. */@@ -95,9 +346,27 @@ static ssize_t digest_list_read(char *path, enum ops op)
if (!rc || rc == -EEXIST)
actions |= (1 << 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);
+
+ /* Release reference taken in digest_list_get_secfs_files(). */
+ if (op == DIGEST_LIST_DEL) {
+ dput(dentry);
+ dput(dentry_ascii);
+ }
+
+ if ((rc < 0 && op == DIGEST_LIST_ADD) ||
+ (rc == size && op == DIGEST_LIST_DEL)) {
+ securityfs_remove(dentry);
+ securityfs_remove(dentry_ascii);
+ }
out_vfree:
vfree(data);
out:@@ -116,9 +385,10 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
size_t datalen, loff_t *ppos)
{
char *data;
+ char *digest_label_ptr;
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];
char event_name[NAME_MAX + 11 + 1];
enum hash_algo algo;@@ -147,6 +417,10 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
if (data[0] == '/') {
result = digest_list_read(data, op);
} else {
+ /* Remove \n. */
+ digest_label_ptr = digest_label;
+ strsep(&digest_label_ptr, "\n");
+
snprintf(event_name, sizeof(event_name), "%s_buffer_%s",
op == DIGEST_LIST_ADD ? "add" : "del", digest_label);
@@ -164,12 +438,33 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
if (!result || result == -EEXIST)
actions |= (1 << 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;
}
+
+ /* Release reference taken in digest_list_get_secfs_files(). */
+ if (op == DIGEST_LIST_DEL) {
+ dput(dentry);
+ dput(dentry_ascii);
+ }
+
+ if ((result < 0 && op == DIGEST_LIST_ADD) ||
+ (result == datalen && op == DIGEST_LIST_DEL)) {
+ securityfs_remove(dentry);
+ securityfs_remove(dentry_ascii);
+ }
}
out_kfree:
kfree(data);@@ -216,6 +511,11 @@ static int __init diglim_fs_init(void)
if (IS_ERR(diglim_dir))
return -1;
+ digest_lists_loaded_dir = securityfs_create_dir("digest_lists_loaded",
+ diglim_dir);
+ if (IS_ERR(digest_lists_loaded_dir))
+ goto out;
+
digest_list_add_dentry = securityfs_create_file("digest_list_add", 0200,
diglim_dir, NULL,
&digest_list_upload_ops);@@ -232,6 +532,7 @@ static int __init diglim_fs_init(void)
out:
securityfs_remove(digest_list_del_dentry);
securityfs_remove(digest_list_add_dentry);
+ securityfs_remove(digest_lists_loaded_dir);
securityfs_remove(diglim_dir);
return -1;
}
--
2.25.1