lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 29 Sep 2009 07:10:10 +0000
From:	Andy Spencer <andy753421@...il.com>
To:	linux-security-module@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [RFC][PATCH] Permission masking security module (was dpriv)

Changes since the previous patch (dpriv-p0):

- Change the name to pmask (Permission Masking)

- Use recursive and non-recursive variants for setting masks 
  $ echo self $perm $path > $stage # Set perms for $path only
  $ echo kids $perm $path > $stage # Set perms for children of $path
  $ echo both $perm $path > $stage # Set perms for $path and children

- Add a quota of 500 permission lines per policy

- Improve securityfs parsing.

- Change '-' to '.' in permissions string to avoid confusion with
  `chmod -rwx foo'

- Many syntax/naming/etc bug fixes

Signed-off-by: Andy Spencer <andy753421@...il.com>
---
 Documentation/pmask.txt |  102 ++++++++++++++
 lib/vsprintf.c          |    2 +-
 security/Kconfig        |    1 +
 security/Makefile       |    2 +
 security/pmask/Kconfig  |    8 +
 security/pmask/Makefile |    1 +
 security/pmask/fs.c     |  299 +++++++++++++++++++++++++++++++++++++++++
 security/pmask/pmask.c  |  144 ++++++++++++++++++++
 security/pmask/policy.c |  337 +++++++++++++++++++++++++++++++++++++++++++++++
 security/pmask/policy.h |  230 ++++++++++++++++++++++++++++++++
 10 files changed, 1125 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/pmask.txt
 create mode 100644 security/pmask/Kconfig
 create mode 100644 security/pmask/Makefile
 create mode 100644 security/pmask/fs.c
 create mode 100644 security/pmask/pmask.c
 create mode 100644 security/pmask/policy.c
 create mode 100644 security/pmask/policy.h

diff --git a/Documentation/pmask.txt b/Documentation/pmask.txt
new file mode 100644
index 0000000..522cb30
--- /dev/null
+++ b/Documentation/pmask.txt
@@ -0,0 +1,102 @@
+Source code
+-----------
+  policy.[ch] - policy datatypes
+  pmask.c     - security/credentials hooks
+  fs.c        - securityfs hooks
+
+
+TODO
+----
+  - Check for race conditions
+
+
+Overview
+--------
+1. Each process keeps a list of inode -> priv mappings:
+   - i.e. the security policy
+
+2. Caching possibilities (todo?)
+   - Processes keeps a list of open fds to prevent recursing up the FS tree?
+   - Store the most recent processes access in each inode?
+
+Privs:
+  - read/write/exec/sticky/setuid/setgui
+  - All permissions are recursive
+  - Permissions for dirs and file are separate
+    - This prevents recursion problems
+    - e.g. you can set noexec for files without smashing directories
+  - Notation
+     (rwx) = specified permission (inode in policy)
+    ~(rwx) = implied permission (parent(s) in policy)
+
+Things to do when:
+  1. Setting privs
+     - Add policy line(s) for given path?
+     - Update privs on open inodes that are children of policy line?
+  2. Loading inode
+     - Cache privs from parent(s)?
+  3. Namespace modification (mv,ln,bind,etc)
+     - OR
+       - Keep policy for inode the same        (policy = old      )
+       - Merge policy for both locations       (policy = old & new)
+       - Change policy to reflect new location (policy =       new)
+     - If mv, and including old implied policy:
+       - need to write new (combined) policy line
+
+
+Security FS
+-----------
+files:
+  -rw-rw-rw- /securityfs/pmask/stage
+  -r--r--r-- /securityfs/pmask/policy
+  --w--w--w- /securityfs/pmask/control
+
+stage:
+  read:  print staged policy
+  write: set inode in staged policy to given perms OR
+         add inode to staged policy with given perms
+         > staged[inode] = perms
+
+  In the stage, order does not matter, adding a line simply writes or
+  overwrites the location with no regard to the rest of the policy.
+
+policy:
+  read: print active policy
+
+control:
+  write:
+    "commit" - merge staged policy into policy
+      > for (inode in policy, staged):
+      >     new[inode] =
+      >         implied_privs(policy, inode) &
+      >         implied_privs(staged, inode)
+      > clear(staged)
+
+    When committing, privilages can only be revoked.
+
+
+Examples
+--------
+Example 1:
+  set /src/     (rw-)
+  set /dst/     (r-x)
+
+  $ mv /src/foo /dst
+
+  get /src/     (rw-)
+  get /dst/     (r-x)
+  OR:
+    get /dst/foo  (rw-)
+    get /dst/foo ~(r-x)
+    get /dst/foo  (rw-) & ~(r-x) = (r--)
+
+Example 2:
+  $ ln /src/foo /dst
+
+  set /src/     (rw-)
+  set /dst/     (rwx)
+
+  get /src/     (rw-)
+  get /dst/     (rwx)
+  get /src/foo ~(rw-) & ~(rwx) = ~(rw-)
+  get /dst/foo ~(rw-) & ~(rwx) = ~(rw-)
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index b91839e..33bed5e 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1771,7 +1771,7 @@ int vsscanf(const char * buf, const char * fmt, va_list args)
 		 * advance both strings to next white space
 		 */
 		if (*fmt == '*') {
-			while (!isspace(*fmt) && *fmt)
+			while (!isspace(*fmt) && *fmt != '%' && *fmt)
 				fmt++;
 			while (!isspace(*str) && *str)
 				str++;
diff --git a/security/Kconfig b/security/Kconfig
index fb363cd..d4521b5 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -159,6 +159,7 @@ config LSM_MMAP_MIN_ADDR
 	  this low address space will need the permission specific to the
 	  systems running LSM.
 
+source security/pmask/Kconfig
 source security/selinux/Kconfig
 source security/smack/Kconfig
 source security/tomoyo/Kconfig
diff --git a/security/Makefile b/security/Makefile
index 95ecc06..f8c5b26 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -3,6 +3,7 @@
 #
 
 obj-$(CONFIG_KEYS)			+= keys/
+subdir-$(CONFIG_SECURITY_PERM_MASKING)  += pmask
 subdir-$(CONFIG_SECURITY_SELINUX)	+= selinux
 subdir-$(CONFIG_SECURITY_SMACK)		+= smack
 subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
@@ -14,6 +15,7 @@ obj-y		+= commoncap.o min_addr.o
 obj-$(CONFIG_SECURITY)			+= security.o capability.o
 obj-$(CONFIG_SECURITYFS)		+= inode.o
 # Must precede capability.o in order to stack properly.
+obj-$(CONFIG_SECURITY_PERM_MASKING)	+= pmask/built-in.o
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/built-in.o
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/built-in.o
 obj-$(CONFIG_AUDIT)			+= lsm_audit.o
diff --git a/security/pmask/Kconfig b/security/pmask/Kconfig
new file mode 100644
index 0000000..a8ca887
--- /dev/null
+++ b/security/pmask/Kconfig
@@ -0,0 +1,8 @@
+config SECURITY_PERM_MASKING
+	bool "Permission masking"
+	depends on SECURITY
+	select SECURITYFS
+	default n
+	help
+	  This enabled support for masking filesystem permissions.
+	  If you are unsure how to answer this question, answer N.
diff --git a/security/pmask/Makefile b/security/pmask/Makefile
new file mode 100644
index 0000000..39d5b27
--- /dev/null
+++ b/security/pmask/Makefile
@@ -0,0 +1 @@
+obj-y = pmask.o policy.o fs.o
diff --git a/security/pmask/fs.c b/security/pmask/fs.c
new file mode 100644
index 0000000..84a6515
--- /dev/null
+++ b/security/pmask/fs.c
@@ -0,0 +1,299 @@
+/**
+ * pmask/fs.c -- Security FS interface for privilege dropping
+ *
+ * Copyright (C) 2009 Andy Spencer <spenceal@...e-hulman.edu>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/security.h>
+#include <linux/fs.h>
+#include <linux/seq_file.h>
+#include <linux/ctype.h>
+#include <linux/uaccess.h>
+
+#include "policy.h"
+
+/***************************
+ * Generic policy iterator *
+ ***************************/
+/* Use this for reading form any policy file */
+static void *pmask_seq_start(struct seq_file *sf, loff_t *pos)
+{
+	struct pmask_policy *policy = sf->private;
+	down_read(&policy->privs_lock);
+	return seq_list_start(&policy->privs, *pos);
+}
+
+static void *pmask_seq_next(struct seq_file *sf, void *seq, loff_t *pos)
+{
+	struct pmask_policy *policy = sf->private;
+	return seq_list_next(seq, &policy->privs, pos);
+}
+
+static void pmask_seq_stop(struct seq_file *sf, void *seq)
+{
+	struct pmask_policy *policy = sf->private;
+	up_read(&policy->privs_lock);
+}
+
+static int pmask_seq_show(struct seq_file *sf, void *seq)
+{
+	struct pmask_line *line = list_entry(seq, struct pmask_line, list);
+	char perm_str[PMASK_PERM_BITS+1] = {};
+	if (pmask_isset(line->self_perm) &&
+			line->self_perm == line->kids_perm) {
+		pmask_perm_to_str(line->self_perm, perm_str);
+		seq_printf(sf, "both %s %s\n", perm_str, line->path);
+	} else {
+		if (pmask_isset(line->self_perm)) {
+			pmask_perm_to_str(line->self_perm, perm_str);
+			seq_printf(sf, "self %s %s\n", perm_str, line->path);
+		}
+		if (pmask_isset(line->kids_perm)) {
+			pmask_perm_to_str(line->kids_perm, perm_str);
+			seq_printf(sf, "kids %s %s\n", perm_str, line->path);
+		}
+	}
+	return 0;
+}
+
+static const struct seq_operations pmask_seq_ops = {
+	.start = pmask_seq_start,
+	.next  = pmask_seq_next,
+	.stop  = pmask_seq_stop,
+	.show  = pmask_seq_show,
+};
+
+static int pmask_seq_open(struct file *file, struct pmask_policy *policy)
+{
+	/* From __seq_open_private
+	 * Not sure if this is correct way to store private data */
+	struct seq_file *sf;
+	if (seq_open(file, &pmask_seq_ops) < 0) {
+		pr_warning("Out of memory opening PMask sequence\n");
+		return -ENOMEM;
+	}
+	sf = file->private_data;
+	sf->private = policy;
+	return 0;
+};
+
+
+
+/**************
+ * Stage file *
+ **************/
+static int pmask_stage_open(struct inode *inode, struct file *file)
+{
+	return pmask_seq_open(file, pmask_cur_stage);
+};
+
+/**
+ * Parse policy lines one at a time.
+ * Format: /\s*([rwxsguRWXSGU\-]*)\s*(.*)(\s*)?/
+ *   \1: See pmask_str_to_perm() for discussion
+ *   \2: A file path, \3 trailing whitespace is optional
+ */
+static ssize_t pmask_stage_write(struct file *filp, const char *ubuffer,
+		size_t length, loff_t *off)
+{
+	struct file *file;
+	int err, rval, perm, scope;
+	char *kbuffer, *cmd_str, *perm_str, *path_str;
+	int cmd_start, cmd_end, perm_start, perm_end, path_start;
+
+	if (length > (size_t)~0LL)
+		return -EINVAL;;
+	kbuffer = kmalloc(length+1, GFP_KERNEL);
+	if (!kbuffer)
+		return -ENOMEM;
+	kbuffer[length] = '\0';
+
+	if (copy_from_user(kbuffer, ubuffer, length))
+		goto fail_fault;
+
+	/* Parse input */
+	path_start = -1;
+	sscanf(kbuffer, " %n%*s%n %n%*s%n %n", &cmd_start, &cmd_end,
+			&perm_start, &perm_end, &path_start);
+	if (path_start == -1)
+		goto fail_inval;
+	cmd_str  = kbuffer+cmd_start;  kbuffer[cmd_end]  = '\0';
+	perm_str = kbuffer+perm_start; kbuffer[perm_end] = '\0';
+	path_str = kbuffer+path_start;
+
+	/* Check and convert cmd/scope */
+	if (!strcmp(cmd_str, "self"))
+		scope = PMASK_SELF;
+	else if (!strcmp(cmd_str, "kids"))
+		scope = PMASK_KIDS;
+	else if (!strcmp(cmd_str, "both"))
+		scope = PMASK_BOTH;
+	else
+		goto fail_inval;
+
+	/* Check and convert perm */
+	if (perm_str[0] == '\0')
+		goto fail_inval;
+	perm = pmask_str_to_perm(perm_str);
+	if (perm < 0)
+		goto fail_inval;
+
+	/* Check and open path */
+	if (path_str[0] == '\0')
+		goto fail_inval;
+	file = filp_open(path_str, 0, 0);
+	if (IS_ERR(file)) {
+		/* file not found, try trimming trailing spaces */
+		strstrip(path_str);
+		if (path_str[0] == '\0')
+			goto fail_inval;
+		file = filp_open(path_str, 0, 0);
+		if (IS_ERR(file))
+			goto fail_noent;
+	}
+
+	path_str = kstrdup(path_str, GFP_KERNEL);
+	if (!path_str)
+		goto fail_nomem;
+
+	err = pmask_policy_set_perm(pmask_cur_stage,
+			file->f_dentry->d_inode, path_str, perm, scope);
+	if (err) {
+		kfree(path_str);
+		rval = err;
+		goto out;
+	}
+
+	pr_debug("pmask_task=%p pid=%d perm=%o[%s] path=%p[%s]\n",
+		pmask_cur_task, current->pid, perm, perm_str, file, path_str);
+
+	rval = length;
+	goto out; /* Success */
+
+fail_inval: rval = -EINVAL; goto out;
+fail_nomem: rval = -ENOMEM; goto out;
+fail_fault: rval = -EFAULT; goto out;
+fail_noent: rval = -ENOENT; goto out;
+out:
+	kfree(kbuffer);
+	/* if (rval < 0) abort task ? */
+	return rval;
+}
+
+static const struct file_operations pmask_stage_fops = {
+	.open    = pmask_stage_open,
+	.write   = pmask_stage_write,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release,
+};
+
+
+
+/***************
+ * Policy file *
+ ***************/
+static int pmask_policy_open(struct inode *inode, struct file *file)
+{
+	return pmask_seq_open(file, pmask_cur_policy);
+};
+
+static const struct file_operations pmask_policy_fops = {
+	.open    = pmask_policy_open,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release,
+};
+
+
+
+/****************
+ * Control file *
+ ****************/
+/**
+ * Read various commands from the user
+ * Format: /(\w+).* /
+ * Commands:
+ *   commit: copy stage to the policy and reset stage
+ */
+static ssize_t pmask_control_write(struct file *filp, const char *buffer,
+		size_t length, loff_t *off)
+{
+	int rval, err;
+	char *command;
+
+	command = kzalloc(length+1, GFP_KERNEL);
+	if (!command)
+		return -ENOMEM;
+
+	if (copy_from_user(command, buffer, length)) {
+		rval = -EFAULT;
+		goto out;
+	}
+
+	strstrip(command);
+
+	if (!strcmp("commit", command)) {
+		pr_debug("committing stage for pid=%d\n", current->pid);
+		err = pmask_policy_commit(pmask_cur_stage, pmask_cur_policy);
+		if (err) {
+			rval = err;
+			goto out;
+		}
+		pmask_policy_clear(pmask_cur_stage);
+	} else {
+		pr_debug("unimplemented control coomand `%s'\n", command);
+		rval = -EINVAL;
+		goto out;
+	}
+
+	rval = length; /* success */
+	goto out;
+
+out:
+	kfree(command);
+	return rval;
+}
+
+static const struct file_operations pmask_control_fops = {
+	.write = pmask_control_write,
+};
+
+
+
+/****************
+ * Registration *
+ ****************/
+static int __init pmask_fs_init(void)
+{
+	struct dentry *pmask_dir;
+	if (!pmask_loaded)
+		return 0;
+	pmask_dir = securityfs_create_dir("pmask", NULL);
+	securityfs_create_file("stage",
+			0666, pmask_dir, NULL, &pmask_stage_fops);
+	securityfs_create_file("policy",
+			0444, pmask_dir, NULL, &pmask_policy_fops);
+	securityfs_create_file("control",
+			0222, pmask_dir, NULL, &pmask_control_fops);
+	pr_info("PMask FS initialized\n");
+	return 0;
+}
+
+fs_initcall(pmask_fs_init);
diff --git a/security/pmask/pmask.c b/security/pmask/pmask.c
new file mode 100644
index 0000000..c64e4bc
--- /dev/null
+++ b/security/pmask/pmask.c
@@ -0,0 +1,144 @@
+/**
+ * pmask/pmask.c -- Linux Security Module interface for privilege dropping
+ *
+ * Copyright (C) 2009 Andy Spencer <spenceal@...e-hulman.edu>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/security.h>
+#include <linux/sched.h>
+
+#include "policy.h"
+
+int pmask_loaded;
+
+/* Credentials */
+static void pmask_cred_free(struct cred *cred)
+{
+	pmask_task_free(cred->security);
+	cred->security = NULL;
+}
+
+static int pmask_cred_prepare(struct cred *new, const struct cred *old,
+		gfp_t gfp)
+{
+	new->security = pmask_task_dup(old->security, gfp);
+	if (!new->security) {
+		pr_warning("Out of memory while preparing PMask task\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static int pmask_dentry_open(struct file *file, const struct cred *cred)
+{
+	u16 perm, need;
+
+	/* Set parent link */
+	if (!IS_ROOT(file->f_dentry))
+		file->f_dentry->d_inode->i_security =
+				file->f_dentry->d_parent->d_inode;
+	else
+		file->f_dentry->d_inode->i_security = NULL;
+
+
+	/* Check privs */
+	perm = pmask_policy_get_perm(pmask_cur_policy,
+			file->f_dentry->d_inode);
+	need = pmask_flags_to_mode(file->f_flags);
+	need = pmask_imode_to_perm(need, file->f_dentry->d_inode);
+	if (unlikely(pmask_denied(perm, need))) {
+		char *path = kzalloc(PATH_MAX, GFP_KERNEL);
+		if (path) {
+			path = d_path(&file->f_path, path, sizeof(path));
+			pr_debug("denied perm=%o:%o path=%s\n",
+					perm, need, path);
+			kfree(path);
+		} else {
+			pr_warning("Out of memory getting path\n");
+			pr_debug("denied perm=%o:%o path=%s\n",
+					perm, need, "-ENOMEM");
+		}
+		return -EACCES;
+	}
+	return 0;
+}
+
+/* Mostly for directory walking */
+static int pmask_inode_permission(struct inode *inode, int mask)
+{
+	u16 perm = pmask_policy_get_perm(pmask_cur_policy, inode);
+	u16 need = pmask_imode_to_perm(mask, inode);
+	if (unlikely(pmask_denied(perm, need))) {
+		pr_debug("denied perm=%o:%o:%o inode=%p\n",
+				perm, need, mask, inode);
+		return -EACCES;
+	}
+	return 0;
+}
+
+/* TODO: Use these to store the multiple pointers? */
+/*
+static int pmask_inode_alloc_security(struct inode *inode)
+{
+	return 0;
+}
+static int pmask_inode_init_security(struct inode *inode, struct inode *dir,
+		char **name, void **value, size_t *len)
+{
+	return 0;
+}
+static void pmask_inode_free_security(struct inode *inode)
+{
+}
+*/
+
+/* Registration */
+static struct security_operations pmask_security_ops = {
+	.name                 = "pmask",
+	.cred_prepare         = pmask_cred_prepare,
+	.cred_free            = pmask_cred_free,
+	.dentry_open          = pmask_dentry_open,
+	.inode_permission     = pmask_inode_permission,
+	/*
+	.inode_alloc_security = pmask_inode_alloc_security,
+	.inode_init_security  = pmask_inode_init_security,
+	.inode_free_security  = pmask_inode_free_security,
+	*/
+	/* TODO: add path operations and update the policies when the
+	 * filesystem layout changes */
+};
+
+static int __init pmask_init(void)
+{
+	struct cred *cred = (struct cred *)current_cred();
+
+	if (!security_module_enable(&pmask_security_ops))
+		return 0;
+	if (register_security(&pmask_security_ops))
+		panic("Failure registering PMask");
+	cred->security = pmask_task_new(GFP_KERNEL);
+	if (!cred->security)
+		panic("Out of memory while initializing pmask");
+	pr_info("PMask initialized\n");
+	pmask_loaded = 1;
+	return 0;
+}
+
+security_initcall(pmask_init);
diff --git a/security/pmask/policy.c b/security/pmask/policy.c
new file mode 100644
index 0000000..90d0606
--- /dev/null
+++ b/security/pmask/policy.c
@@ -0,0 +1,337 @@
+/**
+ * pmask/policy.c -- Privilege dropping core functionality
+ *
+ * Copyright (C) 2009 Andy Spencer <spenceal@...e-hulman.edu>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/security.h>
+#include <linux/sched.h>
+#include <linux/ctype.h>
+#include <linux/fs.h>
+
+#include "policy.h"
+
+/*******************
+ * Permission bits *
+ *******************/
+static char pmask_perm_bit_list[] = "rwxsguRWXSGU";
+static u16  pmask_perm_bit_table['z'] = {
+	['x'] PMASK_EXEC,
+	['w'] PMASK_WRITE,
+	['r'] PMASK_READ,
+	['s'] PMASK_KEEPSWP,
+	['g'] PMASK_SETGID,
+	['u'] PMASK_SETUID,
+	['X'] PMASK_WALK,
+	['W'] PMASK_CREATE,
+	['R'] PMASK_LIST,
+	['S'] PMASK_STICKY,
+	['G'] PMASK_PASSGID,
+	['U'] PMASK_PASSUID,
+	['.'] 0,
+}; /* plus 0.25k .. */
+
+u16 pmask_flags_to_mode(unsigned int flags)
+{
+	u16 mode = 0;
+	if (flags & FMODE_READ)  mode |= PMASK_READ;
+	if (flags & FMODE_WRITE) mode |= PMASK_WRITE;
+	if (flags & FMODE_EXEC)  mode |= PMASK_EXEC;
+	if (flags & O_CREAT)     mode |= PMASK_CREATE;
+	return mode;
+}
+
+int pmask_str_to_perm(const char *str)
+{
+	int perm = 0;
+	for (; *str; str++) {
+		if ((!isalpha(*str) || !pmask_perm_bit_table[(int)*str]) &&
+				*str != '.')
+			return -1;
+		perm |= pmask_perm_bit_table[(int)*str];
+	}
+	return perm;
+}
+
+void pmask_perm_to_str(u16 perm, char *str)
+{
+	char *c = pmask_perm_bit_list;
+	for (; *c; c++, str++)
+		*str = (perm & pmask_perm_bit_table[(int)*c]) ?  *c : '.';
+	*str = '\0';
+}
+
+
+
+/**************
+ * PMask Line *
+ **************/
+/**
+ * Allocate and initalize a new pmask_line
+ * @indoe, @path, @perm: fileds to store en line
+ */
+static struct pmask_line *pmask_line_new(const struct inode *inode,
+		const char *path, gfp_t gfp)
+{
+	struct pmask_line *line;
+	line = kzalloc(sizeof(struct pmask_line), gfp);
+	if (!line)
+		return NULL;
+	line->inode = inode;
+	line->path  = path;
+	line->self_perm = PMASK_IGNORE;
+	line->kids_perm = PMASK_IGNORE;
+	return line;
+}
+
+
+
+/****************
+ * PMask Policy *
+ ****************/
+/* Return the line from @policy->privs that matches @inode */
+static struct pmask_line *pmask_policy_get_line(
+		const struct pmask_policy *policy, const struct inode *inode)
+{
+	struct pmask_line *line;
+	list_for_each_entry(line, &policy->privs, list)
+		if (line->inode == inode)
+			return line;
+	return NULL;
+}
+
+/* Create and add a line to to @policy while checking for errors and updating
+ * the quota */
+static struct pmask_line *pmask_policy_add_line(struct pmask_policy *policy,
+		const struct inode *inode, const char *path, gfp_t gfp)
+{
+	struct pmask_line *line;
+	if (atomic_read(&policy->privs_count) >= PMASK_MAX_LINES)
+		return NULL;
+	atomic_inc(&policy->privs_count);
+	line = pmask_line_new(inode, path, GFP_KERNEL);
+	if (!line)
+		return NULL;
+	list_add_tail(&line->list, &policy->privs);
+	return line;
+}
+
+/* Create and add a line to to @policy while checking for errors and updating
+ * the quota */
+static struct pmask_line *pmask_policy_ensure_line(struct pmask_policy *policy,
+		const struct inode *inode, const char *path, gfp_t gfp)
+{
+	struct pmask_line *line = pmask_policy_get_line(policy, inode);
+	if (!line)
+		line = pmask_policy_add_line(policy, inode, path, gfp);
+	return line;
+}
+
+
+/* Delete a line form @policy and update the quota */
+static void pmask_policy_del_line(struct pmask_policy *policy,
+		struct pmask_line *line)
+{
+	list_del(&line->list);
+	kfree(line);
+	atomic_dec(&policy->privs_count);
+}
+
+/* Do a semi-deep copy, that is, copy enough that the policies are distinct,
+ * but without duplicating conostant data such as paths and dentries */
+static int pmask_policy_append(struct pmask_policy *from,
+		struct pmask_policy *to, gfp_t gfp)
+{
+	struct pmask_line *old_line, *new_line;
+	list_for_each_entry(old_line, &from->privs, list) {
+		new_line = pmask_policy_add_line(to,
+				old_line->inode, old_line->path, gfp);
+		if (!new_line)
+			return -ENOMEM;
+		new_line->self_perm = old_line->self_perm;
+		new_line->kids_perm = old_line->kids_perm;
+	}
+	return 0;
+}
+
+/* Initialize a blank @policy */
+static void pmask_policy_init(struct pmask_policy *policy)
+{
+	INIT_LIST_HEAD(&policy->privs);
+	init_rwsem(&policy->privs_lock);
+	atomic_set(&policy->privs_count, 0);
+}
+
+void pmask_policy_clear(struct pmask_policy *policy)
+{
+	struct pmask_line *line, *n;
+	list_for_each_entry_safe(line, n, &policy->privs, list)
+		pmask_policy_del_line(policy, line);
+}
+
+
+static u16 pmask_policy_get_perm_rec(const struct pmask_policy *policy,
+		const struct inode *inode)
+{
+	struct pmask_line *line;
+
+	/* Allow everything if we've reach the root without finding perms */
+	/* TODO: recurse to parent filesystems */
+	if (inode == NULL)
+		return USHORT_MAX;
+
+	line = pmask_policy_get_line(policy, inode);
+
+	if (line && pmask_isset(line->kids_perm))
+		return line->kids_perm;
+
+	/* Check parents for recursive permissions */
+	/* TODO: Check for multiple parents */
+	return pmask_policy_get_perm_rec(policy, inode->i_security);
+	/*
+	 * perm = USHORT_MAX;
+	 * foreach parent:
+	 * 	perm &= pmask_policy_get_perm(policy, inode->d_parent);
+	 * return perm;
+	 */
+}
+
+u16 pmask_policy_get_perm(const struct pmask_policy *policy,
+		const struct inode *inode)
+{
+	/* Stop if a permissions is found for current node */
+	struct pmask_line *line = pmask_policy_get_line(policy, inode);
+	if (line && pmask_isset(line->self_perm))
+		return line->self_perm;
+	return pmask_policy_get_perm_rec(policy, inode->i_security);
+}
+
+/* We need the inode and path so we can create the line if it doesn't exist */
+int pmask_policy_set_perm(struct pmask_policy *policy,
+		const struct inode *inode, const char *path,
+		u16 perm, int scope)
+{
+	struct pmask_line *line = pmask_policy_ensure_line(policy,
+			inode, path, GFP_KERNEL);
+	if (!line)
+		return -ENOMEM;
+	if (scope == PMASK_BOTH || scope == PMASK_SELF)
+		line->self_perm = perm;
+	if (scope == PMASK_BOTH || scope == PMASK_KIDS)
+		line->kids_perm = perm;
+	return 0;
+}
+
+static void pmask_policy_merge_line(const struct inode *inode,
+		struct pmask_policy *pl, struct pmask_policy *pr,
+		struct pmask_line *to)
+{
+	struct pmask_line *ll = pmask_policy_get_line(pl, inode);
+	struct pmask_line *lr = pmask_policy_get_line(pr, inode);
+	if ((ll && pmask_isset(ll->self_perm)) ||
+	    (lr && pmask_isset(lr->self_perm))) {
+		to->self_perm  = pmask_policy_get_perm(pl, inode);
+		to->self_perm &= pmask_policy_get_perm(pr, inode);
+	}
+	if ((ll && pmask_isset(ll->kids_perm)) ||
+	    (lr && pmask_isset(lr->kids_perm))) {
+		to->kids_perm  = pmask_policy_get_perm_rec(pl, inode);
+		to->kids_perm &= pmask_policy_get_perm_rec(pr, inode);
+	}
+}
+
+int pmask_policy_commit(struct pmask_policy *from, struct pmask_policy *to)
+{
+	struct pmask_line *line, *merge_line, *n;
+	struct pmask_policy merge;
+	pmask_policy_init(&merge);
+
+	/* Merge paths from @to into merge */
+	list_for_each_entry(line, &to->privs, list) {
+		merge_line = pmask_policy_ensure_line(&merge,
+				line->inode, line->path, GFP_KERNEL);
+		if (!merge_line)
+			goto fail;
+		pmask_policy_merge_line(line->inode, from, to, merge_line);
+	}
+
+	/* Merge paths from @from into merge */
+	list_for_each_entry(line, &from->privs, list) {
+		merge_line = pmask_policy_ensure_line(&merge,
+				line->inode, line->path, GFP_KERNEL);
+		if (!merge_line)
+			goto fail;
+		pmask_policy_merge_line(line->inode, from, to, merge_line);
+	}
+
+	/* Free old entries, and move to ones to @to */
+	pmask_policy_clear(to);
+	list_for_each_entry_safe(line, n, &merge.privs, list)
+		list_move_tail(&line->list, &to->privs);
+
+	return 0;
+
+fail:
+	pmask_policy_clear(&merge);
+	return -ENOMEM;
+}
+
+
+
+/**************
+ * PMask Task *
+ **************/
+struct pmask_task *pmask_task_new(gfp_t gfp)
+{
+	struct pmask_task *task;
+	task = kzalloc(sizeof(struct pmask_task), gfp);
+	if (!task)
+		return NULL;
+
+	pmask_policy_init(&task->stage);
+	pmask_policy_init(&task->policy);
+
+	return task;
+}
+
+void pmask_task_free(struct pmask_task *task)
+{
+	pmask_policy_clear(&task->stage);
+	pmask_policy_clear(&task->policy);
+	kfree(task);
+}
+
+struct pmask_task *pmask_task_dup(struct pmask_task *task, gfp_t gfp)
+{
+	struct pmask_task *copy = pmask_task_new(gfp);
+	if (!copy)
+		return NULL;
+
+	/* Copy policies */
+	if (pmask_policy_append(&task->stage,  &copy->stage,  gfp))
+		goto fail;
+	if (pmask_policy_append(&task->policy, &copy->policy, gfp))
+		goto fail;
+
+	return copy;
+
+fail:
+	pmask_task_free(copy);
+	return NULL;
+}
diff --git a/security/pmask/policy.h b/security/pmask/policy.h
new file mode 100644
index 0000000..bc2d526
--- /dev/null
+++ b/security/pmask/policy.h
@@ -0,0 +1,230 @@
+/**
+ * pmask/policy.h -- Privilege dropping core functionality
+ *
+ * Copyright (C) 2009 Andy Spencer <spenceal@...e-hulman.edu>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PMASK_POLICY_H__
+#define __PMASK_POLICY_H__
+
+/* Set to 1 when pmask is initialized */
+extern int pmask_loaded;
+
+/**
+ * Terminology
+ *   mode   = `Unix' mode (u16 use for filesyste mode bits)
+ *   perm   = PMask permission bits (see below)
+ *   privs  = List of files and associated perm
+ *   policy = Privs + whatever else
+ */
+
+#define pmask_cur_task   ((struct pmask_task *)current_security())
+#define pmask_cur_stage  ((struct pmask_policy *)&pmask_cur_task->stage)
+#define pmask_cur_policy ((struct pmask_policy *)&pmask_cur_task->policy)
+
+
+/*******************
+ * Permission bits *
+ *******************/
+/* File bits */
+#define PMASK_EXEC    (1u<<0)  /* x */
+#define PMASK_WRITE   (1u<<1)  /* w */
+#define PMASK_READ    (1u<<2)  /* r */
+#define PMASK_KEEPSWP (1u<<3)  /* s (ignored) */
+#define PMASK_SETGID  (1u<<4)  /* g */
+#define PMASK_SETUID  (1u<<5)  /* u */
+
+/* Directory bits */
+#define PMASK_WALK    (1u<<6)  /* X */
+#define PMASK_CREATE  (1u<<7)  /* W */
+#define PMASK_LIST    (1u<<8)  /* R */
+#define PMASK_STICKY  (1u<<9)  /* S */
+#define PMASK_PASSGID (1u<<10) /* G */
+#define PMASK_PASSUID (1u<<11) /* U (ignored) */
+
+/* Special bits */
+#define PMASK_IGNORE  (1u<<12) /* Permissions unset */
+
+/* Meta bits/masks */
+#define PMASK_PERM_BITS 12
+#define PMASK_MASK      0b111111111111
+#define PMASK_FILE_MASK 0b000000111111
+#define PMASK_DIR_MASK  0b111111000000
+
+/* Scope of permission */
+enum {
+	PMASK_SELF, /* Permissions only affect the inode */
+	PMASK_KIDS, /* Permissions only affect the inodes children */
+	PMASK_BOTH, /* Permissions affect inode and children */
+};
+
+/* Determine if a permission is set or ignored */
+static inline bool pmask_isset(u16 perm)
+{
+	return !(perm & PMASK_IGNORE);
+}
+
+/* Mode conversion functions */
+static inline bool pmask_denied(u16 perm, u16 request)
+{
+	return perm >= 0 && ~perm & request;
+}
+
+/* Convert from a unix directory mode to a perm */
+static inline u16 pmask_dmode_to_perm(u16 mode)
+{
+	return mode << 6;
+}
+
+/* Convert from a unix file mode to a perm */
+static inline u16 pmask_fmode_to_perm(u16 mode)
+{
+	return mode;
+}
+
+/* Convert from a unix perm to a mode based on inode type */
+static inline u16 pmask_imode_to_perm(u16 mode, struct inode *inode)
+{
+	return S_ISDIR(inode->i_mode) ?
+		 pmask_dmode_to_perm(mode) :
+		 pmask_fmode_to_perm(mode);
+}
+
+/**
+ * Convert struct file->f_flags to a Unix mode
+ * <x>mode_to_perm should probably be called on the resulting mode
+ */
+u16 pmask_flags_to_mode(unsigned int flags);
+
+/**
+ * Parse a permission string into a perm
+ * @str:
+ *  - Format is "rwxsguRWXSGU" (see Permission bits)
+ *  - Order does not matter
+ *  - '.' is ignored, any other character is invalid
+ *  - return -1 on invalid str
+ */
+int pmask_str_to_perm(const char *str);
+
+/**
+ * Convert a perm to a string for printing
+ */
+void pmask_perm_to_str(u16 perm, char *str);
+
+
+
+/**************
+ * PMask Line *
+ **************/
+/**
+ * An entry in the policy
+ *
+ * Example:
+ *   /var/tmp (rw-)
+ *
+ * @list:      list_head for stroing in policy
+ * @inode:     Some point in the filesystem, topically an inode
+ * @path:      Path given when the line was created, debugging only
+ * @self_perm: Permissions given to the location
+ * @kids_perm: Permissions given to the location's kids
+ */
+struct pmask_line {
+	struct list_head    list;
+	const struct inode *inode;
+	const char         *path;
+	u16                 self_perm;
+	u16                 kids_perm;
+};
+
+
+
+/****************
+ * PMask Policy *
+ ****************/
+#define PMASK_MAX_LINES 500
+
+/**
+ * Contains permisisons and operations allowed for given security policy
+ *
+ * @privs:      List of pmask_lines for filesystem privilages
+ * @privs_lock: Used for printing (maybe other?)
+ *
+ * Example:
+ *   privs:
+ *     /         (r--)
+ *     /bin/     (r-x)
+ *     /tmp/     (rw-)
+ */
+struct pmask_policy {
+	struct list_head privs;
+	struct rw_semaphore privs_lock;
+	atomic_t privs_count;
+	/* TODO: add other security things */
+};
+
+/* Clear/free data from @policy */
+void pmask_policy_clear(struct pmask_policy *policy);
+
+/* Recursivly lookup perm for @inode in @policy */
+u16 pmask_policy_get_perm(const struct pmask_policy *policy,
+		const struct inode *inode);
+
+/* Set perm for @inode in @policy to @perm, create new line if necessasiary */
+int pmask_policy_set_perm(struct pmask_policy *policy,
+		const struct inode *inode, const char *path,
+		u16 perm, int scope);
+
+/* Copy lines from @from to @to making sure that no additional oeratoins are
+ * allowed in @to after the commit is performed */
+int pmask_policy_commit(struct pmask_policy *from, struct pmask_policy *to);
+
+
+
+/**************
+ * PMask Task *
+ **************/
+/**
+ * Contains information for a given task, including the security policy, stage,
+ * and cache information.
+ *
+ * @stage:
+ *   The modifialbe policy, privilages can be allowed or denied in the stage
+ * @policy:
+ *   The effective policy, used to determines whether an action is allowed
+ *
+ * @policy can only be modified by commiting @stage to @policy. When this is
+ * done, it is insured that no additional operations will be allowed by @policy
+ * after the commit.
+ *
+ * Example:
+ *   stage:  (see pmask_policy)
+ *   policy: (see pmask_policy)
+ */
+struct pmask_task {
+	struct pmask_policy stage;
+	struct pmask_policy policy;
+};
+
+/* Allocate a blank task */
+struct pmask_task *pmask_task_new(gfp_t gfp);
+
+/* Free a task and data associated with it */
+void pmask_task_free(struct pmask_task *task);
+
+/* Create a semi-deep copy of @task, suitable for passing to a child on exec */
+struct pmask_task *pmask_task_dup(struct pmask_task *task, gfp_t gfp);
+
+#endif

Content of type "application/pgp-signature" skipped

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ