From: Roberto Sassu <[email protected]>

Add support for sending a value N between 1 and ULONG_MAX to the IMA
original measurement interface. This value represents the number of
measurements that should be deleted from the current measurements list. In
this case, measurements are staged in an internal non-user visible list,
and immediately deleted.

This staging method allows the remote attestation agents to easily separate
the measurements that were verified (staged and deleted) from those that
weren't due to the race between taking a TPM quote and reading the
measurements list.

In order to minimize the locking time of ima_extend_list_mutex, deleting
N records is realized by doing a lockless walk in the current measurements
list to determine the N-th entry to cut, to cut the current measurements
list under the lock, and by deleting the excess records after releasing the
lock.

Flushing the hash table is not supported for N records, since it would
require removing the N records one by one from the hash table under the
ima_extend_list_mutex lock, which would increase the locking time.

Link: https://github.com/linux-integrity/linux/issues/1
Co-developed-by: Steven Chen <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
 security/integrity/ima/Kconfig     |  3 ++
 security/integrity/ima/ima.h       |  1 +
 security/integrity/ima/ima_fs.c    | 32 +++++++++++++--
 security/integrity/ima/ima_queue.c | 63 ++++++++++++++++++++++++++++++
 4 files changed, 96 insertions(+), 3 deletions(-)

diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 02436670f746..f4d25e045808 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -341,6 +341,9 @@ config IMA_STAGING
          It allows user space to stage the measurements list for deletion and
          to delete the staged measurements after confirmation.
 
+         Or, alternatively, it allows user space to specify N measurements
+         records to stage internally, so that they can be immediately deleted.
+
          On kexec, staging is aborted and any staged measurement records are
          copied to the secondary kernel.
 
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index d2e740c8ff75..7a1b2d6a8b59 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -320,6 +320,7 @@ struct ima_template_desc *lookup_template_desc(const char 
*name);
 bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
 int ima_queue_stage(void);
 int ima_queue_staged_delete_all(void);
+int ima_queue_delete_partial(unsigned long req_value);
 int ima_restore_measurement_entry(struct ima_template_entry *entry);
 int ima_restore_measurement_list(loff_t bufsize, void *buf);
 int ima_measurements_show(struct seq_file *m, void *v);
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 96d7503a605b..174a94740da1 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -28,6 +28,7 @@
  * Requests:
  * 'A\n': stage the entire measurements list
  * 'D\n': delete all staged measurements
+ * '[1, ULONG_MAX]\n' delete N measurements records
  */
 #define STAGED_REQ_LENGTH 21
 
@@ -343,6 +344,7 @@ static ssize_t _ima_measurements_write(struct file *file,
                                       loff_t *ppos, bool staged_interface)
 {
        char req[STAGED_REQ_LENGTH];
+       unsigned long req_value;
        int ret;
 
        if (datalen < 2 || datalen > STAGED_REQ_LENGTH)
@@ -370,7 +372,24 @@ static ssize_t _ima_measurements_write(struct file *file,
                ret = ima_queue_staged_delete_all();
                break;
        default:
-               ret = -EINVAL;
+               if (staged_interface)
+                       return -EINVAL;
+
+               if (ima_flush_htable) {
+                       pr_debug("Deleting staged N measurements not supported 
when flushing the hash table is requested\n");
+                       return -EINVAL;
+               }
+
+               ret = kstrtoul(req, 10, &req_value);
+               if (ret < 0)
+                       return ret;
+
+               if (req_value == 0) {
+                       pr_debug("Must delete at least one entry\n");
+                       return -EINVAL;
+               }
+
+               ret = ima_queue_delete_partial(req_value);
        }
 
        if (ret < 0)
@@ -379,6 +398,12 @@ static ssize_t _ima_measurements_write(struct file *file,
        return datalen;
 }
 
+static ssize_t ima_measurements_write(struct file *file, const char __user 
*buf,
+                                     size_t datalen, loff_t *ppos)
+{
+       return _ima_measurements_write(file, buf, datalen, ppos, false);
+}
+
 static ssize_t ima_measurements_staged_write(struct file *file,
                                             const char __user *buf,
                                             size_t datalen, loff_t *ppos)
@@ -389,6 +414,7 @@ static ssize_t ima_measurements_staged_write(struct file 
*file,
 static const struct file_operations ima_measurements_ops = {
        .open = ima_measurements_open,
        .read = seq_read,
+       .write = ima_measurements_write,
        .llseek = seq_lseek,
        .release = ima_measurements_release,
 };
@@ -470,6 +496,7 @@ static int ima_ascii_measurements_open(struct inode *inode, 
struct file *file)
 static const struct file_operations ima_ascii_measurements_ops = {
        .open = ima_ascii_measurements_open,
        .read = seq_read,
+       .write = ima_measurements_write,
        .llseek = seq_lseek,
        .release = ima_measurements_release,
 };
@@ -603,14 +630,13 @@ static int __init 
create_securityfs_measurement_lists(bool staging)
 {
        const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
        const struct file_operations *binary_ops = &ima_measurements_ops;
-       umode_t permissions = (S_IRUSR | S_IRGRP);
+       umode_t permissions = (S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP);
        const char *file_suffix = "";
        int count = NR_BANKS(ima_tpm_chip);
 
        if (staging) {
                ascii_ops = &ima_ascii_measurements_staged_ops;
                binary_ops = &ima_measurements_staged_ops;
-               permissions |= (S_IWUSR | S_IWGRP);
                file_suffix = "_staged";
        }
 
diff --git a/security/integrity/ima/ima_queue.c 
b/security/integrity/ima/ima_queue.c
index af0502f27d57..718991ba8bcd 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -405,6 +405,69 @@ int ima_queue_staged_delete_all(void)
        return 0;
 }
 
+/**
+ * ima_queue_delete_partial - Delete current measurements
+ * @req_value: Number of measurements to delete
+ *
+ * Delete the requested number of measurements from the current measurements
+ * list, and update the number of records and the binary run-time size
+ * accordingly.
+ *
+ * Refuse to delete current measurements if measurement is suspended, so that
+ * dump can be done in a lockless way and user space is notified about current
+ * measurements being carried over to the secondary kernel, so that it does not
+ * save them twice.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_delete_partial(unsigned long req_value)
+{
+       unsigned long req_value_copy = req_value;
+       unsigned long size_to_remove = 0, num_to_remove = 0;
+       LIST_HEAD(ima_measurements_trim);
+       struct ima_queue_entry *qe;
+       int ret = 0;
+
+       /*
+        * list_for_each_entry_rcu() without rcu_read_lock() is fine because
+        * only list append can happen concurrently. No list replace due to the
+        * staging/delete writers mutual exclusion.
+        */
+       list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
+               size_to_remove += get_binary_runtime_size(qe->entry);
+               num_to_remove++;
+
+               if (--req_value_copy == 0)
+                       break;
+       }
+
+       /* Not enough records to delete. */
+       if (req_value_copy > 0)
+               return -ENOENT;
+
+       mutex_lock(&ima_extend_list_mutex);
+       if (ima_measurements_suspended) {
+               mutex_unlock(&ima_extend_list_mutex);
+               return -ESTALE;
+       }
+
+       /*
+        * qe remains valid because ima_fs.c enforces single-writer exclusion.
+        */
+       __list_cut_position(&ima_measurements_trim, &ima_measurements,
+                           &qe->later);
+
+       atomic_long_sub(num_to_remove, &ima_num_records[BINARY]);
+
+       if (IS_ENABLED(CONFIG_IMA_KEXEC))
+               binary_runtime_size[BINARY] -= size_to_remove;
+
+       mutex_unlock(&ima_extend_list_mutex);
+
+       ima_queue_delete(&ima_measurements_trim, false);
+       return ret;
+}
+
 /**
  * ima_queue_delete - Delete measurements
  * @head: List head measurements are deleted from
-- 
2.43.0


Reply via email to