2006-08-23 19:05:38

by Kylene Jo Hall

[permalink] [raw]
Subject: [PATCH 3/7] SLIM main patch

SLIM is an LSM module which provides an enhanced low water-mark
integrity and high water-mark secrecy mandatory access control
model.

For simplicity, this version:
- does not require stacker
- uses the integrity service (dummy provider)

Major fixes based on prior review comments:
- more cleanup based on review comments
- now only supports builtin
- more locking cleanups
- cleanups in the demotion code

SLIM now performs a generic revocation operation, including revoking
mmap and shared memory access. Note that during demotion or promotion
of a process, SLIM needs only revoke write access to files with higher
integrity, or lower secrecy. Read and execute permissions are blocked
as needed, not revoked. SLIM hopefully uses d_instantiate correctly now.

SLIM inherently deals with dynamic labels, which is a feature not
currently available in selinux. While it might be possible to
add support for this to selinux, it would not appear to be simple,
and it is not clear if the added complexity would be desirable
just to support this one model. (Isn't choice what LSM is all about? :-)

Comments on the model:

Some of the prior comments questioned the usefulness of the
low water-mark model itself. Two major questions raised concerned
a potential progression of the entire system to a fully demoted
state, and the security issues surrounding the guard processes.

In normal operation, the system seems to stabilize with a roughly
equal mixture of SYSTEM, USER, and UNTRUSTED processes. Most
applications seem to do a fixed set of operations in a fixed domain,
and stabilize at their appropriate level. Some applications, like
firefox and evolution, which inherently deal with untrusted data,
immediately go to the UNTRUSTED level, which is where they belong.
In a couple of cases, including cups and Notes, the applications
did not handle their demotions well, as they occured well into their
startup. For these applications, we simply force them to start up
as UNTRUSTED, so demotion is not an issue. The one application
that does tend to get demoted over time are shells, such as bash.
These are not problems, as new ones can be created with the
windowing system, or with su, as needed. To help with the associated
user interface issue, the user space package README shows how to
display the SLIM level in window titles, so it is always clear at
what level the process is currently running.

As for the issue of guard processes, SLIM defines three types of
guard processes: Unlimited Guards, Limited Guards, and Untrusted
Guards. Unlimited Guards are the most security sensitive, as they
allow less trusted process to acquire a higher level of trust.
On my current system there are two unlimited guards, passwd and
userhelper. These two applications inherently have to be trusted
this way regardless of the MAC model used. In SLIM, the policy
clearly and simply labels them as having this level of trust.

Limited Guards are programs which cannot give away higher
trust, but which can keep their existing level despite reading
less trusted data. On my system I have seven limited guards:
yum, which is trusted to verify the signature on an (untrusted)
downloaded RPM file, and to install it, login and sshd, which read
untrusted user supplied login data, for authentication, dhclient
which reads untrusted network data, and updates they system
file /etc/resolv.conf, dbus-daemon, which accepts data from
potentially untrusted processes, Xorg, which has to accept data
from all Xwindow clients, regardless of level, and postfix which
delivers untrusted mail. Again, these applications inherently
must cross trust levels, and SLIM properly identifies them.

As mentioned earlier, cupsd and notes are applications which are
always run directly in untrusted mode, regardless of the level of
the invoking process.

The bottom line is that SLIM guard programs inherently do security
sensitive things, and have to be trusted. There are only a small
number of them, and they are clearly identified by their labels.

Signed-off-by: Mimi Zohar <[email protected]>
Signed-off-by: Kylene Hall <[email protected]>
---
security/slim/slm_main.c | 1511 +++++++++++++++++++++++++++++++++++++
1 files changed, 1511 insertions(+)

--- linux-2.6.18/security/slim/slm_main.c 1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.18-rc3-working/security/slim/slm_main.c 2006-08-22 16:17:13.000000000 -0500
@@ -0,0 +1,1511 @@
+/*
+ * SLIM - Simple Linux Integrity Module
+ *
+ * Copyright (C) 2005,2006 IBM Corporation
+ * Author: Mimi Zohar <[email protected]>
+ * Kylene Hall <[email protected]>
+ *
+ * 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, version 2 of the License.
+ */
+
+#include <linux/mman.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/security.h>
+#include <linux/integrity.h>
+#include <linux/proc_fs.h>
+#include <linux/socket.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/mm.h>
+#include <linux/shm.h>
+#include <linux/ipc.h>
+#include <linux/errno.h>
+#include <linux/xattr.h>
+#include <net/sock.h>
+
+#include "slim.h"
+
+#define XATTR_NAME "security.slim.level"
+
+#define ZERO_STR "0"
+#define UNTRUSTED_STR "UNTRUSTED"
+#define USER_STR "USER"
+#define SYSTEM_STR "SYSTEM"
+
+char *slm_iac_str[] = { ZERO_STR,
+ UNTRUSTED_STR,
+ USER_STR,
+ SYSTEM_STR
+};
+
+#define PUBLIC_STR "PUBLIC"
+#define USER_SENS_STR "USER-SENSITIVE"
+#define SYSTEM_SENS_STR "SYSTEM-SENSITIVE"
+
+static char *slm_sac_str[] = { ZERO_STR,
+ PUBLIC_STR,
+ USER_STR,
+ USER_SENS_STR,
+ SYSTEM_SENS_STR
+};
+
+
+static char *get_token(char *buf_start, char *buf_end, char delimiter,
+ int *token_len)
+{
+ char *bufp = buf_start;
+ char *token = NULL;
+
+ while (!token && (bufp < buf_end)) { /* Get start of token */
+ switch (*bufp) {
+ case ' ':
+ case '\n':
+ case '\t':
+ bufp++;
+ break;
+ case '#':
+ while ((*bufp != '\n') && (bufp++ < buf_end)) ;
+ bufp++;
+ break;
+ default:
+ token = bufp;
+ break;
+ }
+ }
+ if (!token)
+ return NULL;
+
+ *token_len = 0;
+ while ((*token_len == 0) && (bufp <= buf_end)) {
+ if ((*bufp == delimiter) || (*bufp == '\n'))
+ *token_len = bufp - token;
+ if (bufp == buf_end)
+ *token_len = bufp - token;
+ bufp++;
+ }
+ if (*token_len == 0)
+ token = NULL;
+ return token;
+}
+
+static int is_guard_integrity(struct slm_file_xattr *level)
+{
+ if ((level->guard.iac_r != SLM_IAC_NOTDEFINED)
+ && (level->guard.iac_wx != SLM_IAC_NOTDEFINED))
+ return 1;
+ return 0;
+}
+
+static int is_guard_secrecy(struct slm_file_xattr *level)
+{
+ if ((level->guard.sac_rx != SLM_SAC_NOTDEFINED)
+ && (level->guard.sac_w != SLM_SAC_NOTDEFINED))
+ return 1;
+ return 0;
+}
+
+static int is_lower_integrity(struct slm_file_xattr *task_level,
+ struct slm_file_xattr *obj_level)
+{
+ if (task_level->iac_level < obj_level->iac_level)
+ return 1;
+ return 0;
+}
+static int is_isec_defined(struct slm_isec_data *isec)
+{
+ if (isec && isec->level.iac_level != SLM_IAC_NOTDEFINED)
+ return 1;
+ return 0;
+}
+
+/*
+ * Called with current->files->file_lock. There is not a great lock to grab
+ * for demotion of this type. The only place f_mode is changed after install
+ * is in mark_files_ro in the filesystem code. That function is also changing
+ * taking away write rights so even if we race the outcome is the same.
+ */
+static inline void do_revoke_file_wperm(struct file *file,
+ struct slm_file_xattr *cur_level)
+{
+ struct inode *inode;
+ struct slm_isec_data *isec;
+
+ inode = file->f_dentry->d_inode;
+ if (!S_ISREG(inode->i_mode) || !(file->f_mode && FMODE_WRITE))
+ return;
+
+ isec = inode->i_security;
+ spin_lock(&isec->lock);
+ if (is_lower_integrity(cur_level, &isec->level))
+ file->f_mode &= ~FMODE_WRITE;
+ spin_unlock(&isec->lock);
+}
+
+/*
+ * Revoke write permission on an open file.
+ */
+static void revoke_file_wperm(struct slm_file_xattr *cur_level)
+{
+ int i, j = 0;
+ struct files_struct *files = current->files;
+ unsigned long fd = 0;
+ struct fdtable *fdt;
+ struct file *file;
+
+ if (!files || !cur_level)
+ return;
+
+ spin_lock(&files->file_lock);
+ fdt = files_fdtable(files);
+
+ for (;;) {
+ i = j * __NFDBITS;
+ if (i >= fdt->max_fdset || i >= fdt->max_fds)
+ break;
+ fd = fdt->open_fds->fds_bits[j++];
+ while (fd) {
+ if (fd & 1) {
+ file = fdt->fd[i++];
+ if (file)
+ do_revoke_file_wperm(file, cur_level);
+ }
+ fd >>= 1;
+ }
+ }
+ spin_unlock(&files->file_lock);
+}
+
+static inline void do_revoke_mmap_wperm(struct vm_area_struct *mpnt,
+ struct slm_isec_data *isec,
+ struct slm_file_xattr *cur_level)
+{
+ unsigned long start = mpnt->vm_start;
+ unsigned long end = mpnt->vm_end;
+ size_t len = end - start;
+
+ if ((mpnt->vm_flags & (VM_WRITE | VM_MAYWRITE))
+ && (mpnt->vm_flags & VM_SHARED)
+ && (cur_level->iac_level < isec->level.iac_level))
+ do_mprotect(start, len, PROT_READ);
+}
+
+/*
+ * Revoke write permission to underlying mmap file (MAP_SHARED)
+ */
+static void revoke_mmap_wperm(struct slm_file_xattr *cur_level)
+{
+ struct vm_area_struct *mpnt;
+ struct file *file;
+ struct dentry *dentry;
+ struct slm_isec_data *isec;
+
+ flush_cache_mm(current->mm);
+
+ down_write(&current->mm->mmap_sem);
+ for (mpnt = current->mm->mmap; mpnt; mpnt = mpnt->vm_next) {
+ file = mpnt->vm_file;
+ if (!file)
+ continue;
+
+ dentry = file->f_dentry;
+ if (!dentry || !dentry->d_inode)
+ continue;
+
+ isec = dentry->d_inode->i_security;
+ do_revoke_mmap_wperm(mpnt, isec, cur_level);
+ }
+ up_write(&current->mm->mmap_sem);
+}
+
+/*
+ * Revoke write permissions and demote threads using shared memory
+ */
+static void revoke_permissions(struct slm_file_xattr *cur_level)
+{
+ if (!is_kernel_thread(current)) {
+ revoke_mmap_wperm(cur_level);
+ revoke_file_wperm(cur_level);
+ }
+}
+
+#define EXEMPT_STR "EXEMPT"
+static enum slm_iac_level parse_iac(char *token)
+{
+ int iac;
+
+ if (strncmp(token, EXEMPT_STR, strlen(EXEMPT_STR)) == 0)
+ return SLM_IAC_EXEMPT;
+ for (iac = 0; iac < sizeof(slm_iac_str) / sizeof(char *); iac++) {
+ if (strncmp(token, slm_iac_str[iac], strlen(slm_iac_str[iac]))
+ == 0)
+ return iac;
+ }
+ return SLM_IAC_ERROR;
+}
+
+static enum slm_sac_level parse_sac(char *token)
+{
+ int sac;
+
+ if (strncmp(token, EXEMPT_STR, strlen(EXEMPT_STR)) == 0)
+ return SLM_SAC_EXEMPT;
+ for (sac = 0; sac < sizeof(slm_sac_str) / sizeof(char *); sac++) {
+ if (strncmp(token, slm_sac_str[sac], strlen(slm_sac_str[sac]))
+ == 0)
+ return sac;
+ }
+ return SLM_SAC_ERROR;
+}
+
+#define UNLIMITED_STR "UNLIMITED"
+static inline int set_bounds(char *token)
+{
+ if (strncmp(token, UNLIMITED_STR, strlen(UNLIMITED_STR)) == 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * Get the 7 access class levels from the extended attribute
+ * Format: TIMESTAMP INTEGRITY SECRECY [INTEGRITY_GUARD INTEGRITY_GUARD] [SECRECY_GUARD SECRECY_GUARD] [GUARD_TYPE]
+ */
+static int slm_parse_xattr(char *xattr_value, int xattr_len,
+ struct slm_file_xattr *level)
+{
+ char *token;
+ int token_len;
+ char *buf, *buf_end;
+ int fieldno = 0;
+ int rc = 0;
+
+ buf = xattr_value;
+ buf_end = xattr_value + xattr_len;
+
+ while ((token = get_token(buf, buf_end, ' ', &token_len)) != NULL) {
+ buf = token + token_len;
+ switch (++fieldno) {
+ case 1:
+ level->iac_level = parse_iac(token);
+ if (level->iac_level == SLM_IAC_ERROR) {
+ rc = -EINVAL;
+ level->iac_level = SLM_IAC_UNTRUSTED;
+ }
+ break;
+ case 2:
+ level->sac_level = parse_sac(token);
+ if (level->sac_level == SLM_SAC_ERROR) {
+ rc = -EINVAL;
+ level->sac_level = SLM_SAC_PUBLIC;
+ }
+ break;
+ case 3:
+ level->guard.iac_r = parse_iac(token);
+ if (level->guard.iac_r == SLM_IAC_ERROR) {
+ rc = -EINVAL;
+ level->iac_level = SLM_IAC_UNTRUSTED;
+ }
+ break;
+ case 4:
+ level->guard.iac_wx = parse_iac(token);
+ if (level->guard.iac_wx == SLM_IAC_ERROR) {
+ rc = -EINVAL;
+ level->iac_level = SLM_IAC_UNTRUSTED;
+ }
+ break;
+ case 5:
+ level->guard.unlimited = set_bounds(token);
+ level->guard.sac_w = parse_sac(token);
+ if (level->guard.sac_w == SLM_SAC_ERROR) {
+ rc = -EINVAL;
+ level->sac_level = SLM_SAC_PUBLIC;
+ }
+ break;
+ case 6:
+ level->guard.sac_rx = parse_sac(token);
+ if (level->guard.sac_rx == SLM_SAC_ERROR) {
+ rc = -EINVAL;
+ level->sac_level = SLM_SAC_PUBLIC;
+ }
+ break;
+ case 7:
+ level->guard.unlimited = set_bounds(token);
+ default:
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+ * Possible return codes: INTEGRITY_PASS, INTEGRITY_FAIL, INTEGRITY_NOLABEL,
+ * or -EINVAL
+ */
+static int slm_get_xattr(struct dentry *dentry,
+ struct slm_file_xattr *level, int *status)
+{
+ int xattr_len;
+ char *xattr_value = NULL;
+ int rc;
+
+ memset(level, 0, sizeof(struct slm_file_xattr));
+ rc = integrity_verify_metadata(dentry, XATTR_NAME,
+ &xattr_value, &xattr_len, status);
+
+ if (rc >=0 && *status == INTEGRITY_PASS && xattr_value) {
+ rc = slm_parse_xattr(xattr_value, xattr_len, level);
+ kfree(xattr_value);
+ if (rc == 0 && level->iac_level != SLM_IAC_UNTRUSTED)
+ rc = integrity_verify_data(dentry, status);
+ }
+ return rc;
+}
+
+/* Caller responsible for necessary locking */
+static inline void set_level(struct slm_file_xattr *level,
+ enum slm_iac_level iac, enum slm_sac_level sac)
+{
+ level->iac_level = iac;
+ level->sac_level = sac;
+}
+static inline void set_level_exempt(struct slm_file_xattr *level)
+{
+ set_level(level, SLM_IAC_EXEMPT, SLM_SAC_EXEMPT);
+}
+
+static inline void set_level_untrusted(struct slm_file_xattr *level)
+{
+ set_level(level, SLM_IAC_UNTRUSTED, SLM_SAC_PUBLIC);
+}
+
+static inline void set_level_tsec_write(struct slm_file_xattr *level,
+ struct slm_tsec_data *tsec)
+{
+ set_level(level, tsec->iac_wx, tsec->sac_w);
+}
+
+static inline void set_level_tsec_read(struct slm_file_xattr *level,
+ struct slm_tsec_data *tsec)
+{
+ set_level(level, tsec->iac_r, tsec->sac_rx);
+}
+
+static void update_sock_level(struct dentry *dentry, struct slm_file_xattr *level)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ int rc, status = 0;
+
+ rc = slm_get_xattr(dentry, level, &status);
+ if (rc == -EOPNOTSUPP)
+ set_level_exempt(level);
+ else
+ set_level_tsec_read(level, cur_tsec);
+}
+
+static void update_level(struct dentry *dentry, struct slm_file_xattr *level)
+{
+ int rc, status = 0;
+
+ rc = slm_get_xattr(dentry, level, &status);
+ if (rc < 0) {
+ switch (rc) {
+ case -EOPNOTSUPP:
+ set_level_exempt(level);
+ break;
+ case -EINVAL: /* improperly formatted */
+ default:
+ set_level_untrusted(level);
+ break;
+ }
+ } else {
+ switch(status) {
+ case INTEGRITY_FAIL:
+ case INTEGRITY_NOLABEL:
+ set_level_untrusted(level);
+ break;
+ }
+ }
+}
+
+static struct slm_isec_data *slm_alloc_security(gfp_t flags)
+{
+ struct slm_isec_data *isec;
+
+ isec = kzalloc(sizeof(struct slm_isec_data), flags);
+ if (!isec)
+ return NULL;
+
+ spin_lock_init(&isec->lock);
+ return isec;
+}
+
+/*
+ * Exempt objects without extended attribute support
+ * for fastpath. Others will be handled generically
+ * by the other functions.
+ */
+static int is_exempt_fastpath(struct inode *inode)
+{
+ if ((inode->i_sb->s_magic == PROC_SUPER_MAGIC)
+ || S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
+ return 1;
+ return 0;
+}
+
+/*
+ * All directories with xattr support should be labeled, but just in case
+ * recursively traverse path (dentry->parent) until level info is found.
+ */
+static void slm_get_level(struct dentry *dentry, struct slm_file_xattr *level)
+{
+ struct inode *inode = dentry->d_inode;
+ struct slm_isec_data *isec = inode->i_security;
+
+ if (is_isec_defined(isec)) {
+ spin_lock(&isec->lock);
+ memcpy(level, &isec->level, sizeof(struct slm_file_xattr));
+ spin_unlock(&isec->lock);
+ return;
+ }
+
+ if (is_exempt_fastpath(inode)) {
+ memset(level, 0, sizeof(struct slm_file_xattr));
+ set_level_exempt(level);
+ } else if (S_ISSOCK(inode->i_mode))
+ update_sock_level(dentry, level);
+ else
+ update_level(dentry, level);
+
+ spin_lock(&isec->lock);
+ memcpy(&isec->level, level, sizeof(struct slm_file_xattr));
+ spin_unlock(&isec->lock);
+}
+
+/*
+ * new tsk->security inherits from current->security
+ */
+static struct slm_tsec_data *slm_init_task(struct task_struct *tsk, gfp_t flags)
+{
+ struct slm_tsec_data *tsec, *cur_tsec = current->security;
+
+ tsec = kzalloc(sizeof(struct slm_tsec_data), flags);
+ if (!tsec)
+ return NULL;
+ tsec->lock = SPIN_LOCK_UNLOCKED;
+ if (!cur_tsec) {
+ tsec->iac_r = SLM_IAC_HIGHEST - 1;
+ tsec->iac_wx = SLM_IAC_HIGHEST - 1;
+ tsec->sac_w = SLM_SAC_NOTDEFINED + 1;
+ tsec->sac_rx = SLM_SAC_NOTDEFINED + 1;
+ } else
+ memcpy(tsec, cur_tsec, sizeof(struct slm_tsec_data));
+
+ return tsec;
+}
+
+static int is_iac_level_exempt(struct slm_file_xattr *level)
+{
+ if (level->iac_level == SLM_IAC_EXEMPT)
+ return 1;
+ return 0;
+}
+
+static int is_sac_level_exempt(struct slm_file_xattr *level)
+{
+ if (level->sac_level == SLM_SAC_EXEMPT)
+ return 1;
+ return 0;
+}
+
+static int is_iac_less_than_or_exempt(struct slm_file_xattr *level,
+ enum slm_iac_level iac)
+{
+ if (iac <= level->iac_level)
+ return 1;
+ return is_iac_level_exempt(level);
+}
+
+static int is_iac_greater_than_or_exempt(struct slm_file_xattr *level,
+ enum slm_iac_level iac)
+{
+ if (iac >= level->iac_level)
+ return 1;
+ return is_iac_level_exempt(level);
+}
+
+static int is_sac_less_than_or_exempt(struct slm_file_xattr *level,
+ enum slm_sac_level sac)
+{
+ if (sac <= level->sac_level)
+ return 1;
+ return is_sac_level_exempt(level);
+}
+
+static int is_sac_greater_than_or_exempt(struct slm_file_xattr *level,
+ enum slm_sac_level sac)
+{
+ if (sac >= level->sac_level)
+ return 1;
+ return is_sac_level_exempt(level);
+}
+
+/*
+ * enforce: IRAC(process) <= IAC(object)
+ * Permit process to read file of equal or greater integrity
+ * otherwise, demote the process.
+ */
+static void enforce_integrity_read(struct slm_file_xattr *level)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+
+ spin_lock(&cur_tsec->lock);
+ if (!is_iac_less_than_or_exempt(level, cur_tsec->iac_r)) {
+ /* Reading lower integrity, demote process */
+ /* Even in the case of a integrity guard process. */
+ cur_tsec->iac_r = level->iac_level;
+ cur_tsec->iac_wx = level->iac_level;
+ spin_unlock(&cur_tsec->lock);
+
+ revoke_permissions(level);
+ return;
+ }
+ spin_unlock(&cur_tsec->lock);
+}
+
+/*
+ * enforce: SRXAC(process) >= SAC(object)
+ * Permit process to read file of equal or lesser secrecy;
+ * otherwise, promote the process.
+ */
+static void enforce_secrecy_read(struct slm_file_xattr *level)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+
+ spin_lock(&cur_tsec->lock);
+ if (!is_sac_greater_than_or_exempt(level, cur_tsec->sac_rx)) {
+ /* Reading higher secrecy, promote process */
+ /* Even in the case of a secrecy guard process. */
+ cur_tsec->sac_rx = level->sac_level;
+ cur_tsec->sac_w = level->sac_level;
+ spin_unlock(&cur_tsec->lock);
+
+ /* Working item: revoke write permission to lower secrecy
+ * files. Prototyped but insufficiently tested for release
+ * current code will only allow authorized user to release
+ * sensitive data */
+ return;
+ }
+ spin_unlock(&cur_tsec->lock);
+}
+
+static void do_task_may_read(struct slm_file_xattr *level)
+{
+ enforce_integrity_read(level);
+ enforce_secrecy_read(level);
+}
+
+/*
+ * enforce: IWXAC(process) >= IAC(object)
+ * Permit process to write a file of equal or lesser integrity.
+ */
+static int enforce_integrity_write(struct slm_file_xattr *level)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ int rc = 0;
+
+ spin_lock(&cur_tsec->lock);
+ if (!(is_iac_greater_than_or_exempt(level, cur_tsec->iac_wx)
+ || (level->iac_level == SLM_IAC_NOTDEFINED)))
+ /* can't write higher integrity */
+ rc = -EACCES;
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+/*
+ * enforce: SWAC(process) <= SAC(process)
+ * Permit process to write a file of equal or greater secrecy
+ */
+static int enforce_secrecy_write(struct slm_file_xattr *level)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ int rc = 0;
+
+ spin_lock(&cur_tsec->lock);
+ if (!is_sac_less_than_or_exempt(level, cur_tsec->sac_w))
+ /* can't write lower secrecy */
+ rc = -EACCES;
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+static int do_task_may_write(struct slm_file_xattr *level)
+{
+ int rc;
+
+ rc = enforce_integrity_write(level);
+ if (rc < 0)
+ return rc;
+
+ return enforce_secrecy_write(level);
+}
+
+static int slm_set_taskperm(int mask, struct slm_file_xattr *level)
+{
+ int rc = 0;
+
+ if (mask & MAY_READ)
+ do_task_may_read(level);
+ if ((mask & MAY_WRITE) || (mask & MAY_APPEND))
+ rc = do_task_may_write(level);
+
+ return rc;
+}
+
+/*
+ * file changes invalidate isec
+ */
+static int slm_file_permission(struct file *file, int mask)
+{
+ struct slm_isec_data *isec = file->f_dentry->d_inode->i_security;
+
+ if (((mask & MAY_WRITE) || (mask & MAY_APPEND)) && isec) {
+ spin_lock(&isec->lock);
+ isec->level.iac_level = SLM_IAC_NOTDEFINED;
+ spin_unlock(&isec->lock);
+ }
+ return 0;
+}
+
+static int is_untrusted_blk_access(struct inode *inode)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ int rc = 0;
+
+ spin_lock(&cur_tsec->lock);
+ if (cur_tsec && (cur_tsec->iac_wx == SLM_IAC_UNTRUSTED)
+ && S_ISBLK(inode->i_mode))
+ rc = 1;
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+/*
+ * Premise:
+ * Can't write or execute higher integrity, can't read lower integrity
+ * Can't read or execute higher secrecy, can't write lower secrecy
+ */
+static int slm_inode_permission(struct inode *inode, int mask,
+ struct nameidata *nd)
+{
+ struct dentry *dentry = NULL;
+ struct slm_file_xattr level;
+
+ if (S_ISDIR(inode->i_mode) && (mask & MAY_WRITE))
+ return 0;
+
+ dentry = (!nd || !nd->dentry) ? d_find_alias(inode) : nd->dentry;
+ if (!dentry)
+ return 0;
+
+ if (is_untrusted_blk_access(inode))
+ return -EPERM;
+
+ slm_get_level(dentry, &level);
+
+ /* measure all SYSTEM level integrity objects */
+ if (level.iac_level == SLM_IAC_SYSTEM)
+ integrity_measure(dentry, NULL, mask);
+
+ return slm_set_taskperm(mask, &level);
+}
+
+/*
+ * This hook is called holding the inode mutex.
+ */
+static int slm_inode_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct slm_file_xattr level;
+
+ if (!dentry || !dentry->d_name.name)
+ return 0;
+
+ slm_get_level(dentry, &level);
+ return slm_set_taskperm(MAY_WRITE, &level);
+}
+
+static void slm_inode_free_security(struct inode *inode)
+{
+ struct slm_isec_data *isec = inode->i_security;
+
+ inode->i_security = NULL;
+ kfree(isec);
+}
+
+/*
+ * Check integrity permission to create a regular file.
+ */
+static int slm_inode_create(struct inode *parent_dir,
+ struct dentry *dentry, int mask)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_isec_data *parent_isec = parent_dir->i_security;
+ struct slm_file_xattr *parent_level = &parent_isec->level;
+ int rc = 0;
+
+ /*
+ * enforce: IWXAC(process) >= IAC(object)
+ * Permit process to write a file of equal or lesser integrity.
+ */
+ spin_lock(&cur_tsec->lock);
+ spin_lock(&parent_isec->lock);
+ if (!is_iac_greater_than_or_exempt(parent_level, cur_tsec->iac_wx))
+ rc = -EPERM;
+ spin_unlock(&parent_isec->lock);
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+#define MAX_XATTR_SIZE 76
+
+static int slm_set_xattr(struct slm_file_xattr *level,
+ char **name, void **value, size_t * value_len)
+{
+ int len;
+ int xattr_len;
+ char buf[MAX_XATTR_SIZE];
+ char *bufp = buf;
+ char *xattr_val = buf;
+ char *xattr_name;
+
+ if (!level)
+ return 0;
+
+ memset(buf, 0, sizeof(buf));
+
+ if (is_iac_level_exempt(level)) {
+ memcpy(bufp, EXEMPT_STR, strlen(EXEMPT_STR));
+ bufp += strlen(EXEMPT_STR);
+ } else {
+ len = strlen(slm_iac_str[level->iac_level]);
+ memcpy(bufp, slm_iac_str[level->iac_level], len);
+ bufp += len;
+ }
+ *bufp++ = ' ';
+ if (is_sac_level_exempt(level)) {
+ memcpy(bufp, EXEMPT_STR, strlen(EXEMPT_STR));
+ bufp += strlen(EXEMPT_STR);
+ } else {
+ len = strlen(slm_sac_str[level->sac_level]);
+ memcpy(bufp, slm_sac_str[level->sac_level], len);
+ bufp += len;
+ }
+ xattr_len = bufp - buf;
+
+ /* point after 'security.' */
+ xattr_name = strchr(XATTR_NAME, '.');
+ if (xattr_name)
+ *name = kstrdup(xattr_name + 1, GFP_KERNEL);
+ *value = kmalloc(xattr_len + 1, GFP_KERNEL);
+ if (!*value) {
+ kfree(name);
+ return -ENOMEM;
+ }
+ memcpy(*value, xattr_val, xattr_len);
+ *value_len = xattr_len;
+ return 0;
+}
+
+/* Create the security.slim.level extended attribute */
+static int slm_inode_init_security(struct inode *inode, struct inode *dir,
+ char **name, void **value, size_t * len)
+{
+ struct slm_isec_data *isec = inode->i_security, *parent_isec =
+ dir->i_security;
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_file_xattr level;
+ struct xattr_data *data;
+ int rc;
+
+ if (!name || !value || !len)
+ return 0;
+
+ memset(&level, 0, sizeof(struct slm_file_xattr));
+
+ if (is_isec_defined(parent_isec)) {
+ spin_lock(&parent_isec->lock);
+ memcpy(&level, &parent_isec->level,
+ sizeof(struct slm_file_xattr));
+ spin_unlock(&parent_isec->lock);
+ }
+
+ spin_lock(&cur_tsec->lock);
+ /* low integrity process wrote into a higher level directory */
+ if (cur_tsec->iac_wx < level.iac_level)
+ set_level_tsec_write(&level, cur_tsec);
+ /* if directory is exempt, then use process level */
+ if (is_iac_level_exempt(&level)) {
+ /* When a guard process creates a directory */
+ if (S_ISDIR(inode->i_mode)
+ && (cur_tsec->iac_wx != cur_tsec->iac_r))
+ set_level_exempt(&level);
+ else
+ set_level_tsec_write(&level, cur_tsec);
+ }
+
+ /* if a guard process creates a UNIX socket, then EXEMPT it */
+ if (S_ISSOCK(inode->i_mode)
+ && (cur_tsec->iac_wx != cur_tsec->iac_r))
+ set_level_exempt(&level);
+ spin_unlock(&cur_tsec->lock);
+
+ spin_lock(&isec->lock);
+ memcpy(&isec->level, &level, sizeof(struct slm_file_xattr));
+ spin_unlock(&isec->lock);
+
+ data = kmalloc(sizeof(struct xattr_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* set levels, based on parent */
+ rc = slm_set_xattr(&level, &data->name, &data->value, &data->len);
+ if (rc < 0) {
+ kfree(data);
+ return rc;
+ }
+
+ *name = data->name;
+ *value = data->value;
+ *len = data->len;
+ return 0;
+}
+
+static void slm_d_instantiate(struct dentry *dentry, struct inode *inode)
+{
+ struct slm_isec_data *isec;
+ struct slm_file_xattr level;
+
+ if (!inode)
+ return;
+
+ isec = inode->i_security;
+ if (is_exempt_fastpath(inode)) {
+ memset(&level, 0, sizeof(struct slm_file_xattr));
+ set_level_exempt(&level);
+ } else if (S_ISSOCK(inode->i_mode))
+ memset(&level, 0, sizeof(struct slm_file_xattr));
+ else
+ update_level(dentry, &level);
+
+ spin_lock(&isec->lock);
+ memcpy(&isec->level, &level, sizeof(struct slm_file_xattr));
+ spin_unlock(&isec->lock);
+}
+
+/*
+ * Check permissions to create a new directory in the existing directory
+ * associated with inode structure @dir.
+ */
+static int slm_inode_mkdir(struct inode *parent_dir,
+ struct dentry *dentry, int mask)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_isec_data *parent_isec = parent_dir->i_security;
+ struct slm_file_xattr *parent_level = &parent_isec->level;
+ int rc = 0;
+
+ spin_lock(&cur_tsec->lock);
+ spin_lock(&parent_isec->lock);
+ if (cur_tsec->iac_wx < parent_level->iac_level
+ && parent_level->iac_level == SLM_IAC_SYSTEM)
+ rc = -EACCES;
+ spin_unlock(&parent_isec->lock);
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+static int slm_inode_rename(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry)
+{
+ struct slm_file_xattr old_level, parent_level;
+ struct dentry *parent_dentry;
+
+ if (old_dir == new_dir)
+ return 0;
+
+ slm_get_level(old_dentry, &old_level);
+
+ parent_dentry = dget_parent(new_dentry);
+ slm_get_level(parent_dentry, &parent_level);
+ dput(parent_dentry);
+
+ if (is_lower_integrity(&old_level, &parent_level))
+ return -EPERM;
+ return 0;
+}
+
+/*
+ * Limit the integrity value of an object to be no greater than that
+ * of the current process. This is especially important for objects
+ * being promoted.
+*/
+int slm_inode_setxattr(struct dentry *dentry, char *name, void *value,
+ size_t size, int flags)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ char *data = value;
+ enum slm_iac_level iac;
+
+ if (strncmp(name, XATTR_NAME, strlen(XATTR_NAME)) != 0)
+ return 0;
+
+ if (!value)
+ return -EINVAL;
+
+ spin_lock(&cur_tsec->lock);
+ iac = cur_tsec->iac_wx;
+ spin_unlock(&cur_tsec->lock);
+
+ switch (iac) {
+ case SLM_IAC_USER:
+ if ((strncmp(data, USER_STR, strlen(USER_STR)) != 0) &&
+ (strncmp(data, UNTRUSTED_STR, strlen(UNTRUSTED_STR)) != 0))
+ return -EPERM;
+ break;
+ case SLM_IAC_SYSTEM:
+ if ((strncmp(data, SYSTEM_STR, strlen(SYSTEM_STR)) != 0) &&
+ (strncmp(data, USER_STR, strlen(USER_STR)) != 0) &&
+ (strncmp(data, UNTRUSTED_STR, strlen(UNTRUSTED_STR)) != 0)
+ && (strncmp(data, EXEMPT_STR, strlen(EXEMPT_STR)) != 0))
+ return -EPERM;
+ break;
+ default:
+ return -EPERM;
+ }
+ return 0;
+}
+
+/*
+ * SLIM extended attribute was modified, update isec.
+ */
+static void slm_inode_post_setxattr(struct dentry *dentry, char *name,
+ void *value, size_t size, int flags)
+{
+ struct slm_isec_data *slm_isec;
+ struct slm_file_xattr level;
+ int rc, status = 0;
+
+ if (strncmp(name, XATTR_NAME, strlen(XATTR_NAME)) != 0)
+ return;
+
+ rc = slm_get_xattr(dentry, &level, &status);
+ slm_isec = dentry->d_inode->i_security;
+ spin_lock(&slm_isec->lock);
+ memcpy(&slm_isec->level, &level, sizeof(struct slm_file_xattr));
+ spin_unlock(&slm_isec->lock);
+}
+
+static int slm_inode_removexattr(struct dentry *dentry, char *name)
+{
+ struct slm_isec_data *isec = dentry->d_inode->i_security;
+ struct slm_tsec_data *tsec = current->security;
+ enum slm_iac_level iac;
+ int rc = 0;
+
+ if (strncmp(name, XATTR_NAME, strlen(XATTR_NAME)) != 0)
+ return 0;
+
+ if (isec) {
+ spin_lock(&tsec->lock);
+ iac = tsec->iac_wx;
+ spin_unlock(&tsec->lock);
+
+ spin_lock(&isec->lock);
+ switch(iac) {
+ case SLM_IAC_SYSTEM:
+ isec->level.iac_level = SLM_IAC_NOTDEFINED;
+ break;
+ case SLM_IAC_USER:
+ if (isec->level.iac_level < SLM_IAC_NOTDEFINED ||
+ isec->level.iac_level > SLM_IAC_USER)
+ rc = -EPERM;
+ else
+ isec->level.iac_level = SLM_IAC_NOTDEFINED;
+ break;
+ default:
+ rc = -EPERM;
+ }
+ spin_unlock(&isec->lock);
+ }
+ return rc;
+}
+
+static int slm_inode_alloc_security(struct inode *inode)
+{
+ struct slm_isec_data *isec = slm_alloc_security(GFP_KERNEL);
+ if (!isec)
+ return -ENOMEM;
+
+ inode->i_security = isec;
+ return 0;
+}
+
+/*
+ * Opening a socket demotes the integrity of a process to untrusted.
+ */
+int slm_socket_create(int family, int type, int protocol, int kern)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_file_xattr level;
+
+ /* demoting all but UNIX and NETLINK sockets */
+ if ((family != AF_UNIX) && (family != AF_NETLINK)) {
+ spin_lock(&cur_tsec->lock);
+ if (cur_tsec->iac_r > SLM_IAC_UNTRUSTED) {
+ cur_tsec->iac_r = SLM_IAC_UNTRUSTED;
+ cur_tsec->iac_wx = SLM_IAC_UNTRUSTED;
+ spin_unlock(&cur_tsec->lock);
+
+ memset(&level, 0, sizeof(struct slm_file_xattr));
+ level.iac_level = SLM_IAC_UNTRUSTED;
+
+ revoke_permissions(&level);
+ return 0;
+ }
+ spin_unlock(&cur_tsec->lock);
+ }
+ return 0;
+}
+
+/*
+ * Didn't have the family type previously, so update the inode security now.
+ */
+static void slm_socket_post_create(struct socket *sock, int family,
+ int type, int protocol, int kern)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct inode *inode = SOCK_INODE(sock);
+ struct slm_isec_data *slm_isec = inode->i_security;
+
+ spin_lock(&slm_isec->lock);
+ if (family == PF_UNIX) {
+ if (cur_tsec->iac_wx != cur_tsec->iac_r) /* guard process */
+ set_level_exempt(&slm_isec->level);
+ else
+ set_level_tsec_write(&slm_isec->level, cur_tsec);
+ } else
+ set_level_untrusted(&slm_isec->level);
+ spin_unlock(&slm_isec->lock);
+}
+
+/*
+ * When a task gets allocated, it inherits the current IAC and SAC.
+ * Set the values and store them in p->security.
+ */
+static int slm_task_alloc_security(struct task_struct *tsk)
+{
+ struct slm_tsec_data *tsec = tsk->security;
+
+ if (!tsec) {
+ tsec = slm_init_task(tsk, GFP_KERNEL);
+ if (!tsec)
+ return -ENOMEM;
+ }
+ tsk->security = tsec;
+ return 0;
+}
+
+static void slm_task_free_security(struct task_struct *tsk)
+{
+ struct slm_tsec_data *tsec;
+
+ tsec = tsk->security;
+ tsk->security = NULL;
+ kfree(tsec);
+}
+
+/* init_task is an integrity guard */
+static int slm_task_init_alloc_security(struct task_struct *tsk)
+{
+ struct slm_tsec_data *tsec = kzalloc(sizeof(struct slm_tsec_data), GFP_KERNEL);
+
+ if (!tsec)
+ return -ENOMEM;
+
+ tsec->lock = SPIN_LOCK_UNLOCKED;
+
+ tsec->iac_r = SLM_IAC_UNTRUSTED;
+ tsec->iac_wx = SLM_IAC_SYSTEM;
+ tsec->sac_w = SLM_SAC_PUBLIC;
+ tsec->sac_rx = SLM_SAC_PUBLIC;
+
+ tsec->unlimited = 1;
+
+ tsk->security = tsec;
+ return 0;
+}
+
+static int slm_task_post_setuid(uid_t old_ruid, uid_t old_euid,
+ uid_t old_suid, int flags)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+
+ if (cur_tsec && flags == LSM_SETID_ID) {
+ /*set process to USER level integrity for everything but root */
+ spin_lock(&cur_tsec->lock);
+ if ((cur_tsec->iac_r == cur_tsec->iac_wx)
+ && (cur_tsec->iac_r == SLM_IAC_UNTRUSTED));
+ else if (current->suid != 0) {
+ cur_tsec->iac_r = SLM_IAC_USER;
+ cur_tsec->iac_wx = SLM_IAC_USER;
+ } else if ((current->uid == 0) && (old_ruid != 0)) {
+ cur_tsec->iac_r = SLM_IAC_SYSTEM;
+ cur_tsec->iac_wx = SLM_IAC_SYSTEM;
+ }
+ spin_unlock(&cur_tsec->lock);
+ }
+ return 0;
+}
+
+static inline int slm_setprocattr(struct task_struct *tsk,
+ char *name, void *value, size_t size)
+{
+ return -EACCES;
+
+}
+
+static inline int slm_getprocattr(struct task_struct *tsk,
+ char *name, void *value, size_t size)
+{
+ struct slm_tsec_data *tsec = tsk->security;
+ size_t len = 0;
+
+ if (is_kernel_thread(tsk))
+ len = snprintf(value, size, "KERNEL");
+ else {
+ spin_lock(&tsec->lock);
+ if (tsec->iac_wx != tsec->iac_r)
+ len = snprintf(value, size, "GUARD wx:%s r:%s",
+ slm_iac_str[tsec->iac_wx],
+ slm_iac_str[tsec->iac_r]);
+ else
+ len = snprintf(value, size, "%s",
+ slm_iac_str[tsec->iac_wx]);
+ spin_unlock(&tsec->lock);
+ }
+ return min(len, size);
+}
+
+/*
+ * enforce: IWXAC(process) <= IAC(object)
+ * Permit process to execute file of equal or greater integrity
+ */
+static void enforce_integrity_execute(struct linux_binprm *bprm,
+ struct slm_file_xattr *level,
+ struct slm_tsec_data *cur_tsec)
+{
+ spin_lock(&cur_tsec->lock);
+ if (is_iac_less_than_or_exempt(level, cur_tsec->iac_wx))
+ /* Being a guard process is not inherited */
+ cur_tsec->iac_r = cur_tsec->iac_wx;
+ else {
+ cur_tsec->iac_r = level->iac_level;
+ cur_tsec->iac_wx = level->iac_level;
+ spin_unlock(&cur_tsec->lock);
+
+ revoke_permissions(level);
+ return;
+ }
+ spin_unlock(&cur_tsec->lock);
+}
+
+static void enforce_guard_integrity_execute(struct linux_binprm *bprm,
+ struct slm_file_xattr *level,
+ struct slm_tsec_data *cur_tsec)
+{
+ if ((strcmp(bprm->filename, bprm->interp) != 0)
+ && (level->guard.unlimited))
+ level->guard.unlimited = 0;
+
+ spin_lock(&cur_tsec->lock);
+ if (level->guard.unlimited) {
+ cur_tsec->iac_r = level->guard.iac_r;
+ cur_tsec->iac_wx = level->guard.iac_wx;
+ } else {
+ if (cur_tsec->iac_r > level->guard.iac_r)
+ cur_tsec->iac_r = level->guard.iac_r;
+ if (cur_tsec->iac_wx > level->guard.iac_wx)
+ cur_tsec->iac_wx = level->guard.iac_wx;
+ }
+ spin_unlock(&cur_tsec->lock);
+}
+
+/*
+ * enforce: SRXAC(process) >= SAC(object)
+ * Permit process to execute file of equal or lesser secrecy
+ */
+static void enforce_secrecy_execute(struct linux_binprm *bprm,
+ struct slm_file_xattr *level,
+ struct slm_tsec_data *cur_tsec)
+{
+ spin_lock(&cur_tsec->lock);
+ if (is_sac_greater_than_or_exempt(level, cur_tsec->sac_rx))
+ /* Being a guard process is not inherited */
+ cur_tsec->sac_w = cur_tsec->sac_rx;
+ else {
+ cur_tsec->sac_rx = level->sac_level;
+ cur_tsec->sac_w = level->sac_level;
+
+ /* Working item: revoke write permission to lower secrecy
+ * files. Prototyped but insufficiently tested for release
+ * current code will only allow authorized user to release
+ * sensitive data */
+ }
+ spin_unlock(&cur_tsec->lock);
+}
+
+static void enforce_guard_secrecy_execute(struct linux_binprm *bprm,
+ struct slm_file_xattr *level,
+ struct slm_tsec_data *cur_tsec)
+{
+ /*
+ * set low write secrecy range,
+ * not less than current value, prevent leaking data
+ */
+ spin_lock(&cur_tsec->lock);
+ cur_tsec->sac_w = max(cur_tsec->sac_w, level->guard.sac_w);
+ /* limit secrecy range, never demote secrecy */
+ cur_tsec->sac_rx = max(cur_tsec->sac_rx, level->guard.sac_rx);
+ spin_unlock(&cur_tsec->lock);
+}
+
+/*
+ * Enforce process integrity & secrecy levels.
+ * - update integrity process level of integrity guard program
+ * - update secrecy process level of secrecy guard program
+ */
+static int slm_bprm_check_security(struct linux_binprm *bprm)
+{
+ struct dentry *dentry;
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_file_xattr level;
+ int rc, status;
+
+ /* Special case interpreters */
+ spin_lock(&cur_tsec->lock);
+ if (strcmp(bprm->filename, bprm->interp) != 0) {
+ if (!cur_tsec->script_dentry) {
+ spin_unlock(&cur_tsec->lock);
+ return 0;
+ } else
+ dentry = cur_tsec->script_dentry;
+ } else {
+ dentry = bprm->file->f_dentry;
+ cur_tsec->script_dentry = dentry;
+ }
+ spin_unlock(&cur_tsec->lock);
+
+ slm_get_level(dentry, &level);
+
+ /* slm_inode_permission measured all SYSTEM level integrity objects */
+ if (level.iac_level != SLM_IAC_SYSTEM)
+ integrity_measure(dentry, bprm->filename, MAY_EXEC);
+
+ /* Possible return codes: PERMIT, DENY, NOLABEL */
+ rc = integrity_verify_data(dentry, &status);
+ if (rc < 0)
+ return rc;
+
+ switch(status) {
+ case INTEGRITY_FAIL:
+ if (!is_kernel_thread(current))
+ return -EACCES;
+ break;
+ case INTEGRITY_NOLABEL:
+ level.iac_level = SLM_IAC_UNTRUSTED;
+ level.sac_level = SLM_SAC_PUBLIC;
+ }
+
+ enforce_integrity_execute(bprm, &level, cur_tsec);
+ if (is_guard_integrity(&level))
+ enforce_guard_integrity_execute(bprm, &level, cur_tsec);
+
+ enforce_secrecy_execute(bprm, &level, cur_tsec);
+ if (is_guard_secrecy(&level))
+ enforce_guard_secrecy_execute(bprm, &level, cur_tsec);
+
+ return 0;
+}
+
+static int slm_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct slm_file_xattr level;
+ int rc = 0;
+
+ slm_get_level(dentry, &level);
+ spin_lock(&cur_tsec->lock);
+ if (cur_tsec->iac_wx < level.iac_level)
+ rc = -EACCES;
+ spin_unlock(&cur_tsec->lock);
+ return rc;
+}
+
+static inline int slm_capable(struct task_struct *tsk, int cap)
+{
+ struct slm_tsec_data *tsec = tsk->security;
+ int rc = 0;
+
+ /* Derived from include/linux/sched.h:capable. */
+ if (cap_raised(tsk->cap_effective, cap)) {
+ spin_lock(&tsec->lock);
+ if (tsec->iac_wx == SLM_IAC_UNTRUSTED &&
+ cap == CAP_SYS_ADMIN)
+ rc = -EACCES;
+ spin_unlock(&tsec->lock);
+ return rc;
+ }
+ return -EPERM;
+}
+
+static int slm_ptrace(struct task_struct *parent, struct task_struct *child)
+{
+ struct slm_tsec_data *parent_tsec = parent->security,
+ *child_tsec = child->security;
+ int rc = 0;
+
+ if (is_kernel_thread(parent))
+ return 0;
+ spin_lock(&parent_tsec->lock);
+ if (parent_tsec->iac_wx < child_tsec->iac_wx)
+ rc = -EPERM;
+ spin_unlock(&parent_tsec->lock);
+ return rc;
+}
+
+static int slm_shm_alloc_security(struct shmid_kernel *shp)
+{
+ struct slm_tsec_data *cur_tsec = current->security;
+ struct kern_ipc_perm *perm = &shp->shm_perm;
+ struct slm_isec_data *isec = slm_alloc_security(GFP_KERNEL);
+
+ if (!isec)
+ return -ENOMEM;
+
+ spin_lock(&cur_tsec->lock);
+ if (cur_tsec->iac_wx != cur_tsec->iac_r) /* guard process */
+ set_level_exempt(&isec->level);
+ else
+ set_level_tsec_write(&isec->level, cur_tsec);
+ spin_unlock(&cur_tsec->lock);
+ perm->security = isec;
+
+ return 0;
+}
+
+static void slm_shm_free_security(struct shmid_kernel *shp)
+{
+ struct kern_ipc_perm *perm = &shp->shm_perm;
+ struct slm_isec_data *isec = perm->security;
+
+ perm->security = NULL;
+ kfree(isec);
+}
+
+/*
+ * When shp exists called holding perm->lock
+ */
+static int slm_shm_shmctl(struct shmid_kernel *shp, int cmd)
+{
+ struct kern_ipc_perm *perm;
+ struct slm_isec_data *perm_isec;
+ struct file *file;
+ struct dentry *dentry;
+ struct inode *inode;
+ int rc;
+
+ if (!shp)
+ return 0;
+
+ perm = &shp->shm_perm;
+ perm_isec = perm->security;
+ file = shp->shm_file;
+ dentry = file->f_dentry;
+ inode = dentry->d_inode;
+
+ spin_lock(&perm_isec->lock);
+ rc = slm_set_taskperm(MAY_READ | MAY_WRITE, &perm_isec->level);
+ spin_unlock(&perm_isec->lock);
+ return rc;
+}
+
+/*
+ * Called holding perm->lock
+ */
+static int slm_shm_shmat(struct shmid_kernel *shp,
+ char __user * shmaddr, int shmflg)
+{
+ int mask = MAY_READ;
+ int rc;
+ struct kern_ipc_perm *perm = &shp->shm_perm;
+ struct file *file = shp->shm_file;
+ struct dentry *dentry = file->f_dentry;
+ struct inode *inode = dentry->d_inode;
+ struct slm_isec_data *perm_isec = perm->security,
+ *isec = inode->i_security;
+
+ if (shmflg != SHM_RDONLY)
+ mask |= MAY_WRITE;
+
+ spin_lock(&perm_isec->lock);
+ rc = slm_set_taskperm(mask, &perm_isec->level);
+
+ spin_lock(&isec->lock);
+ memcpy(&isec->level, &perm_isec->level, sizeof(struct slm_file_xattr));
+ spin_unlock(&perm_isec->lock);
+ spin_unlock(&isec->lock);
+
+ return rc;
+}
+
+static struct security_operations slm_security_ops = {
+ .bprm_check_security = slm_bprm_check_security,
+ .file_permission = slm_file_permission,
+ .inode_permission = slm_inode_permission,
+ .inode_unlink = slm_inode_unlink,
+ .inode_create = slm_inode_create,
+ .inode_mkdir = slm_inode_mkdir,
+ .inode_rename = slm_inode_rename,
+ .inode_setattr = slm_inode_setattr,
+ .inode_setxattr = slm_inode_setxattr,
+ .inode_post_setxattr = slm_inode_post_setxattr,
+ .inode_removexattr = slm_inode_removexattr,
+ .inode_alloc_security = slm_inode_alloc_security,
+ .inode_free_security = slm_inode_free_security,
+ .inode_init_security = slm_inode_init_security,
+ .socket_create = slm_socket_create,
+ .socket_post_create = slm_socket_post_create,
+ .task_alloc_security = slm_task_alloc_security,
+ .task_free_security = slm_task_free_security,
+ .task_post_setuid = slm_task_post_setuid,
+ .capable = slm_capable,
+ .ptrace = slm_ptrace,
+ .shm_alloc_security = slm_shm_alloc_security,
+ .shm_free_security = slm_shm_free_security,
+ .shm_shmat = slm_shm_shmat,
+ .shm_shmctl = slm_shm_shmctl,
+ .getprocattr = slm_getprocattr,
+ .setprocattr = slm_setprocattr,
+ .d_instantiate = slm_d_instantiate
+};
+
+static int __init init_slm(void)
+{
+ slm_task_init_alloc_security(current);
+ return register_security(&slm_security_ops);
+}
+security_initcall(init_slm);



2006-08-23 19:27:49

by Benjamin LaHaise

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Wed, Aug 23, 2006 at 12:05:37PM -0700, Kylene Jo Hall wrote:
> +/*
> + * Called with current->files->file_lock. There is not a great lock to grab
> + * for demotion of this type. The only place f_mode is changed after install
> + * is in mark_files_ro in the filesystem code. That function is also changing
> + * taking away write rights so even if we race the outcome is the same.
> + */
> +static inline void do_revoke_file_wperm(struct file *file,
> + struct slm_file_xattr *cur_level)
> +{
> + struct inode *inode;
> + struct slm_isec_data *isec;
> +
> + inode = file->f_dentry->d_inode;
> + if (!S_ISREG(inode->i_mode) || !(file->f_mode && FMODE_WRITE))
> + return;
> +
> + isec = inode->i_security;
> + spin_lock(&isec->lock);
> + if (is_lower_integrity(cur_level, &isec->level))
> + file->f_mode &= ~FMODE_WRITE;
> + spin_unlock(&isec->lock);
> +}

This function does not do what you claim or hope it is supposed to do.
Looking at the races (you do nothing to shoot down writes that are in
progress) present, this does not instill confidence in the rest of the
code (as always seems to be the case with new security frameworks or
patches). Cheers,

-ben

2006-08-23 20:35:56

by Kylene Jo Hall

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Wed, 2006-08-23 at 15:27 -0400, Benjamin LaHaise wrote:
> On Wed, Aug 23, 2006 at 12:05:37PM -0700, Kylene Jo Hall wrote:
> > +/*
> > + * Called with current->files->file_lock. There is not a great lock to grab
> > + * for demotion of this type. The only place f_mode is changed after install
> > + * is in mark_files_ro in the filesystem code. That function is also changing
> > + * taking away write rights so even if we race the outcome is the same.
> > + */
> > +static inline void do_revoke_file_wperm(struct file *file,
> > + struct slm_file_xattr *cur_level)
> > +{
> > + struct inode *inode;
> > + struct slm_isec_data *isec;
> > +
> > + inode = file->f_dentry->d_inode;
> > + if (!S_ISREG(inode->i_mode) || !(file->f_mode && FMODE_WRITE))
> > + return;
> > +
> > + isec = inode->i_security;
> > + spin_lock(&isec->lock);
> > + if (is_lower_integrity(cur_level, &isec->level))
> > + file->f_mode &= ~FMODE_WRITE;
> > + spin_unlock(&isec->lock);
> > +}
>
> This function does not do what you claim or hope it is supposed to do.
> Looking at the races (you do nothing to shoot down writes that are in
> progress) present, this does not instill confidence in the rest of the
> code (as always seems to be the case with new security frameworks or
> patches). Cheers,
>
This function is called in the process of authorizing the current
process to do something which would remove its right to write to the
given file. So it hasn't done anything at the lower integrity level yet
and therefore if a write gets through it can't possibly be of low
integrity data.

Example: The current process is running at the USER level and writing to
a USER file in /home/user/. The process then attempts to read an
UNTRUSTED file. The current process will become UNTRUSTED and the read
allowed to proceed but first write access to all USER files is revoked
including the ones it has open.

Thanks,
Kylie

> -ben

2006-08-23 20:41:16

by Benjamin LaHaise

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Wed, Aug 23, 2006 at 01:35:56PM -0700, Kylene Jo Hall wrote:
> Example: The current process is running at the USER level and writing to
> a USER file in /home/user/. The process then attempts to read an
> UNTRUSTED file. The current process will become UNTRUSTED and the read
> allowed to proceed but first write access to all USER files is revoked
> including the ones it has open.

Don't threads share file tables? What is preventing malicious code from
starting another thread which continues writing to the file that the
revoke attempt is made on?

-ben
--
"Time is of no importance, Mr. President, only life is important."
Don't Email: <[email protected]>.

2006-08-23 22:20:05

by Kylene Jo Hall

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Wed, 2006-08-23 at 16:41 -0400, Benjamin LaHaise wrote:
> On Wed, Aug 23, 2006 at 01:35:56PM -0700, Kylene Jo Hall wrote:
> > Example: The current process is running at the USER level and writing to
> > a USER file in /home/user/. The process then attempts to read an
> > UNTRUSTED file. The current process will become UNTRUSTED and the read
> > allowed to proceed but first write access to all USER files is revoked
> > including the ones it has open.
>
> Don't threads share file tables? What is preventing malicious code from
> starting another thread which continues writing to the file that the
> revoke attempt is made on?

Well if they do share file tables then revoking write access from the
file in the file table will revoke access for all threads. It looks
like sharing or copying the file table is based on a flag to the clone
call and we are looking into whether you could exploit that situation.

Thanks,
Kylie
>
> -ben

2006-08-24 08:32:11

by Arjan van de Ven

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Wed, 2006-08-23 at 15:20 -0700, Kylene Jo Hall wrote:
> On Wed, 2006-08-23 at 16:41 -0400, Benjamin LaHaise wrote:
> > On Wed, Aug 23, 2006 at 01:35:56PM -0700, Kylene Jo Hall wrote:
> > > Example: The current process is running at the USER level and writing to
> > > a USER file in /home/user/. The process then attempts to read an
> > > UNTRUSTED file. The current process will become UNTRUSTED and the read
> > > allowed to proceed but first write access to all USER files is revoked
> > > including the ones it has open.
> >
> > Don't threads share file tables? What is preventing malicious code from
> > starting another thread which continues writing to the file that the
> > revoke attempt is made on?
>
> Well if they do share file tables then revoking write access from the
> file in the file table will revoke access for all threads. It looks
> like sharing or copying the file table is based on a flag to the clone
> call and we are looking into whether you could exploit that situation.


well there's many corner cases; like dup2(), or sending the fd over unix
domains (and having it pending there while the revoke happens, and later
accept it)....


--
if you want to mail me at work (you don't), use arjan (at) linux.intel.com

2006-08-24 11:05:29

by Alan

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Ar Mer, 2006-08-23 am 13:35 -0700, ysgrifennodd Kylene Jo Hall:
> Example: The current process is running at the USER level and writing to
> a USER file in /home/user/. The process then attempts to read an
> UNTRUSTED file. The current process will become UNTRUSTED and the read
> allowed to proceed but first write access to all USER files is revoked
> including the ones it has open.

Which really doesn't mean anything in many cases because there are many
ways to get data out of a file handle once you had it opened for write
including sharing via non file handle paths.

You also have to deal with existing mmap() mappings and outstanding I/O.

So here are some ways to break it

SysV shared memory
mmap

or just race it:

Open the USER file
create a new thread
thread #1 create a pipe to a new process ("receiver")
thread #1 fill pipe
thread #1 issue write of buffer that will hold secret data
[blocks after check for rights]

thread #2
wait for thread #1 to block
read secret data into buffer
send signal to "receiver"


receiver now empties the pipe, the write completes and I get the
goodies.

This is why you need a proper implementation of revoke(2) in Linux. You
can't really do it any more easily.


2006-08-24 13:33:00

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Quoting Alan Cox ([email protected]):
> Ar Mer, 2006-08-23 am 13:35 -0700, ysgrifennodd Kylene Jo Hall:
> > Example: The current process is running at the USER level and writing to
> > a USER file in /home/user/. The process then attempts to read an
> > UNTRUSTED file. The current process will become UNTRUSTED and the read
> > allowed to proceed but first write access to all USER files is revoked
> > including the ones it has open.
>
> Which really doesn't mean anything in many cases because there are many
> ways to get data out of a file handle once you had it opened for write
> including sharing via non file handle paths.
>
> You also have to deal with existing mmap() mappings and outstanding I/O.

That she does.

> So here are some ways to break it
>
> SysV shared memory

standard mmap controls should handle this, right?

> mmap

She handles these.

> or just race it:
>
> Open the USER file
> create a new thread
> thread #1 create a pipe to a new process ("receiver")
> thread #1 fill pipe
> thread #1 issue write of buffer that will hold secret data
> [blocks after check for rights]
> thread #2
> wait for thread #1 to block
> read secret data into buffer
> send signal to "receiver"
>
>
> receiver now empties the pipe, the write completes and I get the
> goodies.

thread #2 is reading data from a pipe which is at a secret level, so how
will it exploit that? It can't write it to a lower integrity file...

> This is why you need a proper implementation of revoke(2) in Linux. You
> can't really do it any more easily.

The revoke(2) isn't quite right semantically, because it would revoke
all users' access, right? Rather, we want one process' rights to all
files revoked, but other read/writers should still have access.

Certainly if it were implemented, I'd hope slim could share some of it's
code.

I did try another version of the revocation code which uses
change_protection() to remove the write access, then introduced a hook
on in front of page_mkwrite() to prevent making the page writeable
again. But I was thinking only of integrity, so actually the secrecy
concerns would not be addressed.

-serge

2006-08-24 13:37:41

by Benjamin LaHaise

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Thu, Aug 24, 2006 at 08:32:48AM -0500, Serge E. Hallyn wrote:
> > You also have to deal with existing mmap() mappings and outstanding I/O.
>
> That she does.

Outstanding I/O is not revoked. Any in-progress I/O continues.

-ben
--
"Time is of no importance, Mr. President, only life is important."
Don't Email: <[email protected]>.

2006-08-24 13:53:53

by Alan

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Ar Iau, 2006-08-24 am 08:32 -0500, ysgrifennodd Serge E. Hallyn:
> > You also have to deal with existing mmap() mappings and outstanding I/O.
>
> That she does.

I don't believe so from the patches.

> > SysV shared memory
>
> standard mmap controls should handle this, right?

No its rather independant of mmap

> > mmap
>
> She handles these.

I must have missed where it handles that.

> thread #2 is reading data from a pipe which is at a secret level, so how
> will it exploit that? It can't write it to a lower integrity file...

Ok my example isn't quite right - I can create the pipes and do the
blocking in other patterns to get the result I mean. The problem is that
I can be blocked in a driver write() method before you raise the
security level and no change at the VFS level will be early enough to
stop it.

Another example would be

Type ^S
thread #1
write(console, padding, internalbuffersize);
write(console, secret_buffer, data) [blocks]

thread #2
sleep to be sure #1 is blocked
open secret file
read(secret, secret_buffer, data);

Type ^Q

By the time you raise the security level due to the action of thread #2
I'm already blocked in tty_do_write() and have passed any vfs checks.

> The revoke(2) isn't quite right semantically, because it would revoke
> all users' access, right? Rather, we want one process' rights to all
> files revoked, but other read/writers should still have access.

The core is the same, the question of specifically what you revoke is
different.


2006-08-24 13:58:17

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Quoting Benjamin LaHaise ([email protected]):
> On Thu, Aug 24, 2006 at 08:32:48AM -0500, Serge E. Hallyn wrote:
> > > You also have to deal with existing mmap() mappings and outstanding I/O.
> >
> > That she does.
>
> Outstanding I/O is not revoked. Any in-progress I/O continues.

Yes, that should have read:

> > > You also have to deal with existing mmap() mappings
> >
> > That she does.
> >
> > > and outstanding I/O.

Still not certain this is needed. If you were able to write data to a
pipe, then even though data may be in the buffer, the other end
shouldn't be able to read it if it's own level were changed.

-serge

2006-08-24 14:01:07

by Benjamin LaHaise

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Thu, Aug 24, 2006 at 08:58:03AM -0500, Serge E. Hallyn wrote:
> Still not certain this is needed. If you were able to write data to a
> pipe, then even though data may be in the buffer, the other end
> shouldn't be able to read it if it's own level were changed.

Then what is the benefit of the supposed revoke if it can be trivially
bypassed? Security has not been improved. It is better not to provide
a supposed feature than to offer it up so riddled with holes as to make
it pointless.

-ben
--
"Time is of no importance, Mr. President, only life is important."
Don't Email: <[email protected]>.

2006-08-24 14:17:14

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Quoting Benjamin LaHaise ([email protected]):
> On Thu, Aug 24, 2006 at 08:58:03AM -0500, Serge E. Hallyn wrote:
> > Still not certain this is needed. If you were able to write data to a
> > pipe, then even though data may be in the buffer, the other end
> > shouldn't be able to read it if it's own level were changed.
>
> Then what is the benefit of the supposed revoke if it can be trivially
> bypassed?

How is it being bypassed?

If it can be shown how what is there is insufficient, then it'll have to
be fixed. I'm just not seeing it yet.

> Security has not been improved. It is better not to provide
> a supposed feature than to offer it up so riddled with holes as to make
> it pointless.

Some would say it's better to show what you've got, let others point out
the faults, and fix them. How else will anything ever get done...

-serge

2006-08-24 15:23:34

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Quoting Alan Cox ([email protected]):
> Ar Iau, 2006-08-24 am 08:32 -0500, ysgrifennodd Serge E. Hallyn:
> > > You also have to deal with existing mmap() mappings and outstanding I/O.
> >
> > That she does.
>
> I don't believe so from the patches.
>
> > > SysV shared memory
> >
> > standard mmap controls should handle this, right?
>
> No its rather independant of mmap

There are controls on shmat, and shm which has been mmap'ed will be
revoked.

> > > mmap
> >
> > She handles these.
>
> I must have missed where it handles that.
>
> > thread #2 is reading data from a pipe which is at a secret level, so how
> > will it exploit that? It can't write it to a lower integrity file...
>
> Ok my example isn't quite right - I can create the pipes and do the
> blocking in other patterns to get the result I mean. The problem is that
> I can be blocked in a driver write() method before you raise the
> security level and no change at the VFS level will be early enough to
> stop it.
>
> Another example would be
>
> Type ^S > thread #1
> write(console, padding, internalbuffersize);
> write(console, secret_buffer, data) [blocks]
>
> thread #2
> sleep to be sure #1 is blocked
> open secret file
> read(secret, secret_buffer, data);
>
> Type ^Q
>
> By the time you raise the security level due to the action of thread #2
> I'm already blocked in tty_do_write() and have passed any vfs checks.

So using the following two patches should solve that problem, by making
sure that the tty pages can't be made writeable again?

Or will the page associated with the tty already have the data, and this
really just needs to be fixed in the tty itself?

-serge

>From 413fe6a2d2563cfd0ab488b89c9a42043d1e698c Mon Sep 17 00:00:00 2001
From: Serge E. Hallyn <[email protected]>
Date: Tue, 8 Aug 2006 10:23:19 -0500
Subject: [PATCH 1/3] security: define vm_revoke_write

Define vm_revoke_write() function which may be used to revoke write
permission from a vma_area_struct. This can be used, for example,
by security modules wishing to revoke write permissions to a process
whose clearance has changed.

The first intended user for this function is the SLIM LSM, which
implements dynamic process integrity labels. As integrity labels
on a process lower, write permission may need to be revoked to
high integrity files.

Signed-off-by: Serge E. Hallyn <[email protected]>
---
include/linux/mm.h | 2 ++
mm/mprotect.c | 18 ++++++++++++++++++
2 files changed, 20 insertions(+), 0 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 990957e..cad1936 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1072,5 +1072,7 @@ #endif

const char *arch_vma_name(struct vm_area_struct *vma);

+extern void vm_revoke_write(struct vm_area_struct *vma);
+
#endif /* __KERNEL__ */
#endif /* _LINUX_MM_H */
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 638edab..21c322e 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -21,6 +21,7 @@ #include <linux/personality.h>
#include <linux/syscalls.h>
#include <linux/swap.h>
#include <linux/swapops.h>
+#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/cacheflush.h>
@@ -115,6 +116,23 @@ static void change_protection(struct vm_
flush_tlb_range(vma, start, end);
}

+void vm_revoke_write(struct vm_area_struct *vma)
+{
+ pgprot_t newprot;
+
+ down_write(&current->mm->mmap_sem);
+ if (!(vma->vm_flags & VM_SHARED))
+ goto out;
+ if (!(vma->vm_flags & (VM_WRITE|VM_MAYWRITE)))
+ goto out;
+ vma->vm_flags &= ~(VM_MAYWRITE|VM_WRITE);
+ newprot = protection_map[vma->vm_flags];
+ change_protection(vma, vma->vm_start, vma->vm_end, newprot);
+out:
+ up_write(&current->mm->mmap_sem);
+}
+EXPORT_SYMBOL_GPL(vm_revoke_write);
+
static int
mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
unsigned long start, unsigned long end, unsigned long newflags)
--
1.4.1.1

>From 195e53662e09bba81c59681eece3beedff2b7d1e Mon Sep 17 00:00:00 2001
From: Serge E. Hallyn <[email protected]>
Date: Tue, 8 Aug 2006 10:25:23 -0500
Subject: [PATCH 2/3] security: define security_page_mkwrite

Define a security_page_mkwrite() hook which is called before making
a page writable. This can be used by security modules after revoking
write access to a vma, in order to avoid reacquiring write access,
without having to manually change the file->f_mode.

The intended user of this hook is the SLIM security module, which
needs the ability to revoke write access to an open file when a
process' integrity level is lowered.

Signed-off-by: Serge E. Hallyn <[email protected]>
---
include/linux/security.h | 21 +++++++++++++++++++++
mm/memory.c | 11 +++++++++++
security/dummy.c | 7 +++++++
3 files changed, 39 insertions(+), 0 deletions(-)

diff --git a/include/linux/security.h b/include/linux/security.h
index f753038..5515188 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -410,6 +410,14 @@ #ifdef CONFIG_SECURITY
* the size of the buffer required.
* Returns number of bytes used/required on success.
*
+ * Security hooks for page operations
+ *
+ * @page_mkwrite:
+ * Check for permission to make a page table entry writable.
+ * @vma is the vma to which the page belongs
+ * @p is the struct page wanting to be made writable
+ * Return 0 if permission is granted.
+ *
* Security hooks for file operations
*
* @file_permission:
@@ -1198,6 +1206,7 @@ struct security_operations {
int (*inode_setsecurity)(struct inode *inode, const char *name, const void *value, size_t size, int flags);
int (*inode_listsecurity)(struct inode *inode, char *buffer, size_t buffer_size);

+ int (*page_mkwrite) (struct vm_area_struct *vma, struct page *p);
int (*file_permission) (struct file * file, int mask);
int (*file_alloc_security) (struct file * file);
void (*file_free_security) (struct file * file);
@@ -1738,6 +1747,12 @@ static inline int security_inode_listsec
return security_ops->inode_listsecurity(inode, buffer, buffer_size);
}

+static inline int security_page_mkwrite(struct vm_area_struct *vma,
+ struct page *p)
+{
+ return security_ops->page_mkwrite(vma, p);
+}
+
static inline int security_file_permission (struct file *file, int mask)
{
return security_ops->file_permission (file, mask);
@@ -2405,6 +2420,12 @@ static inline int security_inode_listsec
return 0;
}

+static inline int security_page_mkwrite(struct vm_area_struct *vma,
+ struct page *p)
+{
+ return 0;
+}
+
static inline int security_file_permission (struct file *file, int mask)
{
return 0;
diff --git a/mm/memory.c b/mm/memory.c
index 109e986..c4ed919 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -49,6 +49,7 @@ #include <linux/rmap.h>
#include <linux/module.h>
#include <linux/delayacct.h>
#include <linux/init.h>
+#include <linux/security.h>

#include <asm/pgalloc.h>
#include <asm/uaccess.h>
@@ -1466,6 +1467,12 @@ static int do_wp_page(struct mm_struct *

if (unlikely((vma->vm_flags & (VM_SHARED|VM_WRITE)) ==
(VM_SHARED|VM_WRITE))) {
+ ret = security_page_mkwrite(vma, old_page);
+ if (ret) {
+ page_cache_release(old_page);
+ return VM_FAULT_SIGBUS;
+ }
+
if (vma->vm_ops && vma->vm_ops->page_mkwrite) {
/*
* Notify the address space that the page is about to
@@ -2144,6 +2151,10 @@ retry:
/* if the page will be shareable, see if the backing
* address space wants to know that the page is about
* to become writable */
+ if (security_page_mkwrite(vma, new_page)) {
+ page_cache_release(new_page);
+ return VM_FAULT_SIGBUS;
+ }
if (vma->vm_ops->page_mkwrite &&
vma->vm_ops->page_mkwrite(vma, new_page) < 0
) {
diff --git a/security/dummy.c b/security/dummy.c
index bbbfda7..20e838e 100644
--- a/security/dummy.c
+++ b/security/dummy.c
@@ -397,6 +397,12 @@ static const char *dummy_inode_xattr_get
return NULL;
}

+static inline int dummy_page_mkwrite(struct vm_area_struct *vma,
+ struct page *p)
+{
+ return 0;
+}
+
static int dummy_file_permission (struct file *file, int mask)
{
return 0;
@@ -968,6 +974,7 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, inode_getsecurity);
set_to_dummy_if_null(ops, inode_setsecurity);
set_to_dummy_if_null(ops, inode_listsecurity);
+ set_to_dummy_if_null(ops, page_mkwrite);
set_to_dummy_if_null(ops, file_permission);
set_to_dummy_if_null(ops, file_alloc_security);
set_to_dummy_if_null(ops, file_free_security);
--
1.4.1.1

2006-08-24 16:43:46

by Alan

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Ar Iau, 2006-08-24 am 10:23 -0500, ysgrifennodd Serge E. Hallyn:
> Or will the page associated with the tty already have the data, and this
> really just needs to be fixed in the tty itself?

It is a matter of the timing and the device. You need to do revocation
at the device level because your security state change must occur after
the devices have all been dealt with. This is why I said you need the
core of revoke() to do this.

Patches like the one below are really trying to wallpaper over the
cracks in an implementation that doesn't work. The moment you replace
that part of the implementation with a proper revocation method that
waits for resources to be safe then it all works.

The security model is fine, the implementation is hitting the same
revocation feature wall as others.

> permission from a vma_area_struct. This can be used, for example,
> by security modules wishing to revoke write permissions to a process
> whose clearance has changed.

What about drivers that use get_user_pages() - they have a locked kernel
mapping to the object but may not yet have accessed the data.

Plus the idea of a security indirect call every time we make a page
writable does not make me happy when considering performance. Not one
iota.

Alan

2006-08-24 17:34:16

by David Safford

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Thu, 2006-08-24 at 18:05 +0100, Alan Cox wrote:
> It is a matter of the timing and the device. You need to do revocation
> at the device level because your security state change must occur after
> the devices have all been dealt with. This is why I said you need the
> core of revoke() to do this.

In a typical system, most applications are started at the correct level,
and we don't have to demote/promote them. In those cases where demotion
or promotion are needed, only a small number actually already have
access that needs to be revoked. Of those, most involve shmem, which
I believe we are revoking safely, as we don't have the same problems
with drivers and incomplete I/O. In the remaining cases, where we really
can't revoke safely, we could simply not allow the requested access, and
not demote/promote the process.

I think this would give us a useful balance of allowing "safe" demotion
or promotions, while not requiring general revocation. Does this sound
like a reasonable approach?

dave

2006-08-24 19:16:24

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Quoting David Safford ([email protected]):
> On Thu, 2006-08-24 at 18:05 +0100, Alan Cox wrote:
> > It is a matter of the timing and the device. You need to do revocation
> > at the device level because your security state change must occur after
> > the devices have all been dealt with. This is why I said you need the
> > core of revoke() to do this.
>
> In a typical system, most applications are started at the correct level,
> and we don't have to demote/promote them. In those cases where demotion
> or promotion are needed, only a small number actually already have
> access that needs to be revoked. Of those, most involve shmem, which
> I believe we are revoking safely, as we don't have the same problems
> with drivers and incomplete I/O. In the remaining cases, where we really
> can't revoke safely, we could simply not allow the requested access, and
> not demote/promote the process.
>
> I think this would give us a useful balance of allowing "safe" demotion
> or promotions, while not requiring general revocation. Does this sound
> like a reasonable approach?

It sounds like you're saying "This should not be a problem unless the
system is being abused/exploited so let's not worry about it."

Assuming that wasn't your intent :), could you please rephrase?

thanks,
-serge


2006-08-24 20:21:26

by David Safford

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

On Thu, 2006-08-24 at 14:16 -0500, Serge E Hallyn wrote:
> Quoting David Safford ([email protected]):
> > On Thu, 2006-08-24 at 18:05 +0100, Alan Cox wrote:
> > > It is a matter of the timing and the device. You need to do revocation
> > > at the device level because your security state change must occur after
> > > the devices have all been dealt with. This is why I said you need the
> > > core of revoke() to do this.
> >
> > In a typical system, most applications are started at the correct level,
> > and we don't have to demote/promote them. In those cases where demotion
> > or promotion are needed, only a small number actually already have
> > access that needs to be revoked. Of those, most involve shmem, which
> > I believe we are revoking safely, as we don't have the same problems
> > with drivers and incomplete I/O. In the remaining cases, where we really
> > can't revoke safely, we could simply not allow the requested access, and
> > not demote/promote the process.
> >
> > I think this would give us a useful balance of allowing "safe" demotion
> > or promotions, while not requiring general revocation. Does this sound
> > like a reasonable approach?
>
> It sounds like you're saying "This should not be a problem unless the
> system is being abused/exploited so let's not worry about it."
>
> Assuming that wasn't your intent :), could you please rephrase?

Sorry about that - my explanation was unclear.

What I was trying to say was that I agreed that revocation was not
safe, and that we should block any access request that would cause
demotion/promotion if it would also cause revocation. This would
close the revocation hole for malicious code/data.

We could still safely demote/promote processes in the other cases
which would not trigger revocation, and the security model would
be not only safer from the removal of revocation, but would still be
useful, as in practice we found that many/most demotions/promotions do
not have anything to revoke.

dave






2006-08-24 20:41:39

by Mimi Zohar

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch


Alan Cox <[email protected]> wrote on 08/24/2006 10:15:17 AM:

> Ar Iau, 2006-08-24 am 08:32 -0500, ysgrifennodd Serge E. Hallyn:
> > > You also have to deal with existing mmap() mappings and
> > > outstanding I/O.
> >
> > That she does.
>
> I don't believe so from the patches.
>
> > > SysV shared memory
> >
> > standard mmap controls should handle this, right?
>
> No its rather independant of mmap

Under the covers it seems to use shmem. sys_shmget() calls newseg(),
which sets up the shared memory.

> > > mmap
> >
> > She handles these.
>
> I must have missed where it handles that.

revoke_mmap_wperm() walks current->mm->mmap and removes
the file write permission using do_mprotect().

We have test shmem and mmap programs in the ltp framework that
show this actually works.

Mimi

2006-08-24 21:52:31

by Alan

[permalink] [raw]
Subject: Re: [PATCH 3/7] SLIM main patch

Ar Iau, 2006-08-24 am 16:41 -0400, ysgrifennodd Mimi Zohar:
> Alan Cox <[email protected]> wrote on 08/24/2006 10:15:17 AM:
> revoke_mmap_wperm() walks current->mm->mmap and removes
> the file write permission using do_mprotect().

That is fine for threads of "current" but the pages may be shared
between processes, and there are other fun cases where you can "park"
data in objects and let someone open them later to recover the data (eg
the console)

> We have test shmem and mmap programs in the ltp framework that
> show this actually works.

Cool.