[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <4AC414EF.8030901@schaufler-ca.com>
Date: Wed, 30 Sep 2009 19:33:19 -0700
From: Casey Schaufler <casey@...aufler-ca.com>
To: Andy Spencer <andy753421@...il.com>
CC: linux-security-module@...r.kernel.org,
linux-kernel@...r.kernel.org,
Casey Schaufler <casey@...aufler-ca.com>
Subject: Re: [RFC][PATCH] Permission masking security module (was dpriv)
Andy Spencer wrote:
> 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
>
I have some comments below. Nothing that should be a big surprise.
I would really like to see more and bigger comments, especially describing
the inner workings of the hooks.
So what is the use for this? It's sort of intuitive, but not completely.
Is there a particular problem it solves, or is it a general "it feels like
it ought to help" sort of thing?
What's your intent regarding paths that follow symlinks, go through
mount points,
that kind of thing.
> 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++;
>
Since this isn't core to the task at hand you shouldn't include it here.
I understand you're pursuing this in another thread, so you should not
include it here, as was discussed elsewhere.
> 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 */
>
I'll pick the nit. s/form/from/
> +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:
>
This is why goto's got a bad reputation. Set rval in the code above and
"goto out:". Your "fail_noent" is especially painful as you have a goto
to the next line.
> + 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, ©->stage, gfp))
> + goto fail;
> + if (pmask_policy_append(&task->policy, ©->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
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists