@@ -48,6 +59,255 @@ static struct dentry *digest_list_add_dentry;
static struct dentry *digest_list_del_dentry;
char digest_list_label[NAME_MAX + 1];
+static int parse_digest_list_filename(const char *digest_list_filename,
+ u8 *digest, enum hash_algo *algo)
+{
+ u8 *sep;
+ int i;
+
+ sep = strchr(digest_list_filename, '-');
+ if (!sep)
+ return -EINVAL;
+
+ *sep = '\0';
+ i = match_string(hash_algo_name, HASH_ALGO__LAST, digest_list_filename);
+ *sep = '-';
+
+ if (i < 0)
+ return -ENOENT;
+
+ *algo = i;
+ return hex2bin(digest, sep + 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, hdr_orig;
+ 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) {
+ memcpy(&hdr_orig, v, sizeof(hdr_orig));
+ hdr_orig.type = cpu_to_le16(hdr_orig.type);
+ hdr_orig.modifiers = cpu_to_le16(hdr_orig.modifiers);
+ hdr_orig.algo = cpu_to_le16(hdr_orig.algo);
+ hdr_orig.count = cpu_to_le32(hdr_orig.count);
+ hdr_orig.datalen = cpu_to_le32(hdr_orig.datalen);
+ digest_list_putc(m, &hdr_orig, sizeof(hdr_orig));
+ } 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;
+}
+
/*
* check_modsig: detect appended signature
*/@@ -129,9 +390,30 @@ ssize_t digest_list_read(struct path *root, char *path, enum ops op)
goto out_vfree;
}
- 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) {
+ pr_err("unable to create securityfs entries for %s (%d)\n",
+ path, rc);
+ goto out_vfree;
+ }
+
+ rc = digest_list_parse(size, data, op, actions, digest, algo,
+ dentry->d_name.name);
if (rc < 0 && rc != -EEXIST)
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 && rc != -EEXIST && op == DIGEST_LIST_ADD) ||
+ (rc == size && op == DIGEST_LIST_DEL)) {
+ securityfs_remove(dentry);
+ securityfs_remove(dentry_ascii);
+ }
out_vfree:
vfree(data);
out_allow_write:@@ -201,12 +483,36 @@ static ssize_t digest_list_write(struct file *file, const char __user *buf,
goto out_kfree;
}
+ result = digest_list_get_secfs_files(
+ digest_list_label[0] != '\0' ?
+ digest_list_label : "parser",
+ digest, algo, op, &dentry,
+ &dentry_ascii);
+ if (result < 0) {
+ pr_err("unable to create securityfs entries for buffer (%ld)\n",
+ result);
+ goto out_kfree;
+ }
+
memset(digest_list_label, 0, sizeof(digest_list_label));
result = digest_list_parse(datalen, data, op, actions, digest,
- algo, "");
+ algo, dentry->d_name.name);
if (result < 0 && result != -EEXIST)
pr_err("unable to upload generated digest list\n");
+
+ /* Release reference taken in digest_list_get_secfs_files(). */
+ if (op == DIGEST_LIST_DEL) {
+ dput(dentry);
+ dput(dentry_ascii);
+ }
+
+ if ((result < 0 && result != -EEXIST &&
+ op == DIGEST_LIST_ADD) ||
+ (result == datalen && op == DIGEST_LIST_DEL)) {
+ securityfs_remove(dentry);
+ securityfs_remove(dentry_ascii);
+ }
}
out_kfree:
kfree(data);