Blob Blame History Raw
From 449b4c3612d117118108bc87b3978a966b1cc985 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Fri, 26 May 2017 16:27:58 -0700
Subject: [PATCH 21/65] apparmor: add policy revision file interface
Git-commit: d9bf2c268be6064ae0c9980e4c37fdd262c7effc
Patch-mainline: v4.13-rc1
References: FATE#323500

Add a policy revision file to find the current revision of a ns's policy.
There is a revision file per ns, as well as a virtualized global revision
file in the base apparmor fs directory. The global revision file when
opened will provide the revision of the opening task namespace.

The revision file can be waited on via select/poll to detect apparmor
policy changes from the last read revision of the opened file. This
means that the revision file must be read after the select/poll other
wise update data will remain ready for reading.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Goldwyn Rodrigues <rgoldwyn@suse.com>
---
 security/apparmor/apparmorfs.c         | 113 ++++++++++++++++++++++++++++++++-
 security/apparmor/include/apparmorfs.h |   2 +
 security/apparmor/include/policy_ns.h  |   1 +
 security/apparmor/policy_ns.c          |   1 +
 4 files changed, 116 insertions(+), 1 deletion(-)

diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 570d6b58b159..8c413333726b 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -23,6 +23,7 @@
 #include <linux/capability.h>
 #include <linux/rcupdate.h>
 #include <linux/fs.h>
+#include <linux/poll.h>
 #include <uapi/linux/major.h>
 #include <uapi/linux/magic.h>
 
@@ -31,6 +32,7 @@
 #include "include/audit.h"
 #include "include/context.h"
 #include "include/crypto.h"
+#include "include/policy_ns.h"
 #include "include/policy.h"
 #include "include/policy_ns.h"
 #include "include/resource.h"
@@ -498,11 +500,101 @@ static const struct file_operations aa_fs_profile_remove = {
 	.llseek = default_llseek,
 };
 
+struct aa_revision {
+	struct aa_ns *ns;
+	long last_read;
+};
+
+/* revision file hook fn for policy loads */
+static int ns_revision_release(struct inode *inode, struct file *file)
+{
+	struct aa_revision *rev = file->private_data;
+
+	if (rev) {
+		aa_put_ns(rev->ns);
+		kfree(rev);
+	}
+
+	return 0;
+}
+
+static ssize_t ns_revision_read(struct file *file, char __user *buf,
+				size_t size, loff_t *ppos)
+{
+	struct aa_revision *rev = file->private_data;
+	char buffer[32];
+	long last_read;
+	int avail;
+
+	mutex_lock(&rev->ns->lock);
+	last_read = rev->last_read;
+	if (last_read == rev->ns->revision) {
+		mutex_unlock(&rev->ns->lock);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		if (wait_event_interruptible(rev->ns->wait,
+					     last_read !=
+					     READ_ONCE(rev->ns->revision)))
+			return -ERESTARTSYS;
+		mutex_lock(&rev->ns->lock);
+	}
+
+	avail = sprintf(buffer, "%ld\n", rev->ns->revision);
+	if (*ppos + size > avail) {
+		rev->last_read = rev->ns->revision;
+		*ppos = 0;
+	}
+	mutex_unlock(&rev->ns->lock);
+
+	return simple_read_from_buffer(buf, size, ppos, buffer, avail);
+}
+
+static int ns_revision_open(struct inode *inode, struct file *file)
+{
+	struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL);
+
+	if (!rev)
+		return -ENOMEM;
+
+	rev->ns = aa_get_ns(inode->i_private);
+	if (!rev->ns)
+		rev->ns = aa_get_current_ns();
+	file->private_data = rev;
+
+	return 0;
+}
+
+static unsigned int ns_revision_poll(struct file *file, poll_table *pt)
+{
+	struct aa_revision *rev = file->private_data;
+	unsigned int mask = 0;
+
+	if (rev) {
+		mutex_lock(&rev->ns->lock);
+		poll_wait(file, &rev->ns->wait, pt);
+		if (rev->last_read < rev->ns->revision)
+			mask |= POLLIN | POLLRDNORM;
+		mutex_unlock(&rev->ns->lock);
+	}
+
+	return mask;
+}
+
 void __aa_bump_ns_revision(struct aa_ns *ns)
 {
 	ns->revision++;
+	wake_up_interruptible(&ns->wait);
 }
 
+static const struct file_operations aa_fs_ns_revision_fops = {
+	.owner		= THIS_MODULE,
+	.open		= ns_revision_open,
+	.poll		= ns_revision_poll,
+	.read		= ns_revision_read,
+	.llseek		= generic_file_llseek,
+	.release	= ns_revision_release,
+};
+
 /**
  * query_data - queries a policy and writes its data to buf
  * @buf: the resulting data is stored here (NOT NULL)
@@ -1280,6 +1372,10 @@ void __aafs_ns_rmdir(struct aa_ns *ns)
 		sub = d_inode(ns_subremove(ns))->i_private;
 		aa_put_ns(sub);
 	}
+	if (ns_subrevision(ns)) {
+		sub = d_inode(ns_subrevision(ns))->i_private;
+		aa_put_ns(sub);
+	}
 
 	for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) {
 		aafs_remove(ns->dents[i]);
@@ -1305,6 +1401,13 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir)
 		return PTR_ERR(dent);
 	ns_subdata_dir(ns) = dent;
 
+	dent = aafs_create_file("revision", 0444, dir, ns,
+				&aa_fs_ns_revision_fops);
+	if (IS_ERR(dent))
+		return PTR_ERR(dent);
+	aa_get_ns(ns);
+	ns_subrevision(ns) = dent;
+
 	dent = aafs_create_file(".load", 0640, dir, ns,
 				      &aa_fs_profile_load);
 	if (IS_ERR(dent))
@@ -1930,11 +2033,19 @@ static int __init aa_create_aafs(void)
 	}
 	ns_subremove(root_ns) = dent;
 
+	dent = securityfs_create_file("revision", 0444, aa_sfs_entry.dentry,
+				      NULL, &aa_fs_ns_revision_fops);
+	if (IS_ERR(dent)) {
+		error = PTR_ERR(dent);
+		goto error;
+	}
+	ns_subrevision(root_ns) = dent;
+
+	/* policy tree referenced by magic policy symlink */
 	mutex_lock(&root_ns->lock);
 	error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy",
 				aafs_mnt->mnt_root);
 	mutex_unlock(&root_ns->lock);
-
 	if (error)
 		goto error;
 
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 071a59a1f056..bd689114bf93 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -74,6 +74,7 @@ enum aafs_ns_type {
 	AAFS_NS_LOAD,
 	AAFS_NS_REPLACE,
 	AAFS_NS_REMOVE,
+	AAFS_NS_REVISION,
 	AAFS_NS_COUNT,
 	AAFS_NS_MAX_COUNT,
 	AAFS_NS_SIZE,
@@ -102,6 +103,7 @@ enum aafs_prof_type {
 #define ns_subload(X) ((X)->dents[AAFS_NS_LOAD])
 #define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE])
 #define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE])
+#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION])
 
 #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
 #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h
index d7a07ac96168..23e7cb770226 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -69,6 +69,7 @@ struct aa_ns {
 	long uniq_id;
 	int level;
 	long revision;
+	wait_queue_head_t wait;
 
 	struct list_head rawdata_list;
 
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 7d7c23705be2..f3418a9e59b1 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -101,6 +101,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
 	INIT_LIST_HEAD(&ns->sub_ns);
 	INIT_LIST_HEAD(&ns->rawdata_list);
 	mutex_init(&ns->lock);
+	init_waitqueue_head(&ns->wait);
 
 	/* released by aa_free_ns() */
 	ns->unconfined = aa_alloc_profile("unconfined", GFP_KERNEL);
-- 
2.12.3