public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC 1/5] [TALPA] Hooking points and kernel interception
@ 2008-08-04 21:00 Eric Paris
  2008-08-04 21:00 ` [RFC 2/5] [TALPA] securityfs configuration interfaces Eric Paris
  2008-08-05  0:28 ` [RFC 1/5] [TALPA] Hooking points and kernel interception Christoph Hellwig
  0 siblings, 2 replies; 6+ messages in thread
From: Eric Paris @ 2008-08-04 21:00 UTC (permalink / raw)
  To: malware-list, linux-kernel; +Cc: Eric Paris

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 27520 bytes --]

Core of the functionality is to insert hooking points at appropriate
places and pass events for vetting.

Vetting works via three chain of filters. First an interception is
passed to a evaluation chain whose purpose is to decide whether the
access should be allowed or denied. Depending on the outcome either
allow or deny filter chain is run next.

Signed-off-by: Eric Paris <eparis@redhat.com>
---

 Documentation/talpa/design.txt          |  266 +++++++++++++++++++++++++++++++
 fs/open.c                               |   10 ++
 include/linux/talpa.h                   |   88 ++++++++++
 security/Kconfig                        |    1 +
 security/Makefile                       |    2 +
 security/talpa/Kconfig                  |    9 +
 security/talpa/Makefile                 |    7 +
 security/talpa/talpa.h                  |   64 ++++++++
 security/talpa/talpa_allow_calls.h      |    5 +
 security/talpa/talpa_deny_calls.h       |    5 +
 security/talpa/talpa_evaluation_calls.h |    6 +
 security/talpa/talpa_interceptor.c      |  116 ++++++++++++++
 12 files changed, 579 insertions(+), 0 deletions(-)

diff --git a/Documentation/talpa/design.txt b/Documentation/talpa/design.txt
new file mode 100644
index 0000000..5df07bf
--- /dev/null
+++ b/Documentation/talpa/design.txt
@@ -0,0 +1,266 @@
+
+Background
+++++++++++
+There is a consensus in the security industry that protecting against malicious
+files (viruses, root kits, spyware, ad-ware, ...) by the way of so-called
+on-access scanning is usable and reasonable approach. Currently the Linux kernel
+does not offer a completely suitable interface to implement such security solutions.
+Present solutions involve overwriting function pointers in the LSM, in filesystem
+operations, in the sycall table, and other fragile hacks.  The purpose of this
+project is to create a fast, clean interface for userspace programs to look for
+malware when files are accessed.  This malware may be ultimately intended for
+this or some other Linux machine or may be malware intended to attack a host
+running a different operating system and is merely in transit across the Linux
+server.  Since there are almost an infinite number of ways in which information
+can enter and exit a server it is not seen as reasonable to move these checks to
+all the applications at the boundary (MTA, NFS, CIFS, SSH, rsync, et al.) to look
+for such malware on at the border.
+
+Speed is of particular interest for those who have it compiled into the kernel
+but have no userspace client.  There must be no measurable performance hit to
+just compiling this into the kernel.
+
+Security vendors, Linux distributors and other interested parties have come together
+on the malware-list mailing list to discuss this problem and see if they can work
+together to propose a solution. During these talks couple of requirement sets were
+posted with the aim of fleshing out common needs as a prerequisite of creating an
+interface prototype.
+
+Collated requirements
++++++++++++++++++++++
+  1. Intercept file opens (exec also) for vetting (block until decision is made) and allow some userspace black magic to make decisions.
+  2. Intercept file closes for scanning post access
+  3. Cache scan results so the same file is not scanned on each and every access
+  4. Ability to flush the cache and cause all files to be re-scanned when accessed
+  5. Define which filesystems are cacheable and which are not
+  6. Scan files directly not relying on path.  Avoid races and problems with namespaces, chroot, containers, etc.
+  7. Report other relevant file, process and user information associated with each interception
+  8. Report file pathnames to userspace (relative to process root, current working directory)
+  9. Mark a processes as exempt from on access scanning
+ 10. Exclude sub-trees from scanning based on filesystem (exclude procfs, sysfs, devfs)
+ 11. Exclude sub-trees from scanning based on filesystem path
+ 12. Include only certain sub-trees from scanning based on filesystem path
+ 13. Register more than one userspace client in which case behavior is restrictive
+
+
+Discussion of requirements
+++++++++++++++++++++++++++
+The initial patch set with NOT meet all of these 'requirements.'  Some will be
+implemented at a later time and some will never be implemented.  Specifics are
+detailed below.  There is no intention to (abu)use the LSM for this purpose.
+The LSM provides complete internal kernel mandatory access controls.  It is not
+intended for userspace scanning and detection.  Users should not be forced to
+choose between an in kernel mandatory access control policy and this additional
+userspace file access.  LSM stacking is NOT as option as has been demonstrated
+repeatedly.
+
+1., 2. Basic interception
+-------------------------
+Core requirement is to intercept access to files and prevent it if malicious
+content is detected.  This is done on open, not on read.  It may be possible
+to do read time checking with minimal performance impact although not currently
+implemented.  This means that the following race is possible
+
+   Process1              Process2
+    - open file RD
+                          - open file WR
+                          - write virus data (1)
+    - read virus data
+
+*note that any open after (1) will get properly vetted.  At this time the
+likelyhood of this being a problem vs the performance impact of scanning on
+read and the increased complexity of the code means this is left out.  This
+should not be a problem for local executables as writes to files opened to be run
+typically return ETXTBSY.
+
+To accomplish that two hooks were inserted, on file open in __dentry_open and in
+filp_close on file close. In both cases the file object in question is passed as a
+parameter for further processing. In case of an open the operation can actually be
+blocked, while closes are always immediately successful and will not cause additional
+blocking.  Results of a close are returned to the kernel asynchronously and may be
+used to cache answers to speed up a future open.
+
+Interception processing is done by way of three chains of filters.  Access requests
+are first send to the "evaluation" chain.  Depending on the results of the evaluation
+the decision is then send to either the allow chain or the deny chain.
+
+There are three basic responses each filter can make - to be indifferent or either
+allow or deny access to the file.  The filter may also allow or deny access to a
+file while not caching that result.
+
+One of the most important filters in the evaluation chain implements an interface
+through which an userspace process can register and receive vetting requests.
+Userspace process opens a misc character device to express its interest and then
+receives binary structures from that device describing basic interception information.
+After file contents have been scanned a vetting response is sent by writing a different
+binary structure back to the device and the intercepted process continues its execution.
+These are not done over network sockets and no endian conversions are done.  The client
+and the kernel must have the same endian configuration.
+
+3., 4. Caching
+---------------
+To avoid scanning unchanged files on every access which would be very bad for
+performance some sort of caching is needed.  Although possible to implement a
+cache in userspace having two context switches required for every open is clearly
+not fast.  We implemented it per inode object as a serial number compared with
+a single global monotonically increasing system serial number.
+
+The cache filter is inserted into the evaluation chain before the userspace
+client filter and if the inode serial number is equal to the system one it allows
+access to the file.
+
+If the file is seen for the first time, has been modified, or for any other reason
+has a serial number less than the system one the cache filter will be 'indifferent'
+and processing of the given vetting request will continue down the evaluation chain.
+When some filter (only Userspace in the first patch set) allows access to a file its
+inode serial number is set to the system global which effectively makes it cached.
+Also, when a write access is gained for a file the serial number will automatically
+be reset as well as when any process actually writes to that file.
+
+Cache flushing is possible by simply increasing the global system serial number.
+
+Both positive and negative vetting results are cached by the means of positive and
+negative serial numbers.
+
+This method of caching has minimal impact on system resources while providing maximal
+effectiveness and simple implementation.
+
+5. Fine-grained caching
+-----------------------
+It is necessary to select which filesystems can be safely cached and which must
+not be. For example it is not a good idea to allow caching of network filesystems
+because their content can be changed invisibly. Disk based and some virtual
+filesystems can be cached safely on the other hand.
+
+This first proposal only partially implements this requirement. Only block device
+backed filesystems will be cached while there is no way to enable caching for
+things like tmpfs. Improving this is left out of the initial prototype.  Although
+there may be additional work to implement caching for certain FS types there is
+no plan to greatly increase the scope of the cache granularity.  There is no
+plan to cache based on the operation or things of that nature.  Caching of
+this nature can be implemented in userspace if the vendor so chooses.  We
+include only a minimal safe cache for performance reasons.
+
+6. Direct access to file content
+--------------------------------
+When an userspace daemon receives a vetting request, it also receives a new RO
+file descriptor which provides direct access to the inode in question. This is
+to enable access to the file regardless of it accessibility from the scanner
+environment (consider process namespaces, chroot's, NFS).  The userspace client
+is responsible for closing this file when it is finished scanning.
+
+7. Other reporting
+------------------
+Along with the fd being installed in the scanning process the process gets a
+binary structure of data including: 
+
++       uint32_t version;
++       uint32_t type;
++       int32_t fd;
++       uint32_t operation;
++       uint32_t flags;
++       uint32_t mode;
++       uint32_t uid;
++       uint32_t gid;
++       uint32_t tgid;
++       uint32_t pid;
+
+8. Path name reporting
+----------------------
+When a malicious content is detected in a file it is important to be able to
+report its location so the user or system administrator can take appropriate actions.
+
+This is implemented in a amazingly simple way which will hopefully avoid the
+controversy of some other solutions. Path name is only needed for reporting purposes
+and it is obtained by reading the symlink of the given file descriptor in /proc.  Its as
+simple as userspace calling:
+
+snprintf(link, sizeof(link), "/proc/self/fd/%d", details.fd);
+ret = readlink(link, buf, sizeof(buf)-1);
+
+9. Process exclusion
+--------------------
+Sometimes it is necessary to exclude certain processes from being intercepted. For
+example it might be a userspace root kit scanner which would not be able to find
+root kits if access to them was blocked by the on-access scanner.
+
+To facilitate that we have created a special file a process can open and register
+itself as excluded. A flag is then put into its kernel structure (task_struct)
+which makes it excluded from scanning.
+
+This implementation is very simple and provides greatest performance. In the proposed
+implementation access to the exclusion device is controlled though permissions on
+the device node which are not sufficient.  An LSM call will need to be made for this
+type or access in a later patch.
+
+10. Filesystem exclusions
+-------------------------
+One pretty important optimization is not to scan things like /proc, /sys or similar.
+Basically all filesystems where user can not store arbitrary, potentially malicious,
+content could and should be excluded from scanning.
+
+This interface prototype implements it as a run-time configurable list of filesystem
+names. Again it is a filter in the evaluation chain which can allow access before
+the request gets routed to the userspace client.
+
+This will not be implemented in the first patch set but should be soon to follow.
+It is done by simply comparing strings between those supplied and the s_type->name
+field in an associated superblock.
+
+11. Path exclusions
+-------------------
+The need for exclusions can be demonstrated with an example of a MySQL server. It's
+data files are frequently modified which means they would need to be constantly
+rescanned which is very bad for performance. Also, it is most often not even
+possible to reasonably scan them. Therefore the best solution is not to scan
+its database store which can simply be implemented by excluding the store subdirectory.
+
+It is a relatively simple implementation which allows run-time configuration of
+a list of sub directories or files to exclude. Exclusion paths are relative to
+each process root. So for example if we want to exclude /var/lib/mysql/ and we
+have a mysql running in a chroot where from the outside that directory actually
+lives in /chroot/mysql/var/lib/mysql, /var/lib/mysql should actually be added
+to the exclusion list.
+
+This is also not included in the initial patch set but will be coming shortly after.
+
+12. Path Inclusions
+-------------------
+
+Path-based inclusions are not implemented due to concerns with hard-linked files
+both inside and outside the included directories.  It is too easy to fall into
+a sense of false security with path inclusions since the pathname is almost
+meaningless.  If a vendor feels this is particularly important for them they
+will have to implement it in userspace by use of a judicious list of exclusion
+filters.
+
+
+13. Multiple client registration with restrictive behavior
+-----------------------------------------------------------
+This is currently not implemented. Multiple clients can register but they will be
+used for (crappy) load balancing only.  Not all will be called for a single interception.
+Only one of the registered clients will process a single interception. Desire here
+is to enable multiple clients servicing interceptions in parallel for performance
+and reliability reasons.
+
+Requirement for serial and restrictive behavior would be slightly more complicated
+to implement because we would want to keep the current behavior as well. Or in other
+words we would need to have groups of multiple clients, where each interception
+would go through one client from each group with the desired restrictive behavior.
+
+This may be left for a future implementation for simplicity reasons but I find it
+unlikely.  If a vendor needs to send requests to multiple scanners they should be
+able to implement that serialization in userspace.  I see no need for an in kernel
+event dispatcher.  Note that the audit system had this same need and has done iti
+as a userspace event dispatcher.  We have also seen in the LSM that restrictive
+access stacking is not as easy as it sounds and has been abandoned.
+
+Closing remarks
+---------------
+Although some may argue some of the filters are not necessary or may better be
+implemented in userspace, we think it is better to have them in kernel primarily
+for performance reasons. Secondly, it is all simple code not introducing much
+baggage or risk into the kernel itself.  The most complex filter and the only
+one with locking ramifications is the userspace client vetting which calls into
+dentry_open() on both open and close operations.  There is no locking around
+caching or process exclusions or other work.
diff --git a/fs/open.c b/fs/open.c
index 07da935..1133005 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -29,6 +29,7 @@
 #include <linux/rcupdate.h>
 #include <linux/audit.h>
 #include <linux/falloc.h>
+#include <linux/talpa.h>
 
 int vfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
@@ -842,6 +843,13 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
 		}
 	}
 
+	if (!IS_ERR(f)) {
+		error = talpa_vet_file_open(f, flags);
+		if (error) {
+			fput(f);
+			f = ERR_PTR(error);
+		}
+	}
 	return f;
 
 cleanup_all:
@@ -1082,6 +1090,8 @@ int filp_close(struct file *filp, fl_owner_t id)
 		return 0;
 	}
 
+	talpa_vet_file_close(filp);
+
 	if (filp->f_op && filp->f_op->flush)
 		retval = filp->f_op->flush(filp, id);
 
diff --git a/include/linux/talpa.h b/include/linux/talpa.h
new file mode 100644
index 0000000..4ae05ba
--- /dev/null
+++ b/include/linux/talpa.h
@@ -0,0 +1,88 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef __LINUX_TALPA_H__
+#define __LINUX_TALPA_H__
+
+#ifndef __KERNEL__
+#include <stdint.h>
+#else
+#include <linux/types.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * enum talpa_operation - type of intercepted operation
+ * @TALPA_OPEN:open of a filesystem object
+ * @TALPA_CLOSE:closing of a filesystem object
+ */
+enum talpa_operation {
+	TALPA_OPEN = 0,
+	TALPA_CLOSE = 1,
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+
+#ifdef CONFIG_TALPA
+
+/* Internal interface. */
+
+/**
+ * talpa_vet_file_open - called to vet an open operation
+ * @file:file which is being opened
+ * @flags:flags passed when opening
+ *
+ * This is to be called from the appropriate place in the VFS layer
+ * to catch all filesystem operations which provide access to
+ * file objects.
+ */
+extern int talpa_vet_file_open(struct file *file, int flags);
+
+/**
+ * talpa_vet_file_close - called to vet files on close
+ * @file:file which is being closed
+ *
+ * This is to be called from the appropriate place in the VFS layer
+ * to inspect file content just before they will be closed.
+ */
+extern void talpa_vet_file_close(struct file *file);
+
+#else /* !CONFIG_TALPA*/
+
+static inline int talpa_vet_file_open(struct file *file, int flags)
+{
+	return 0;
+}
+
+static inline void talpa_vet_file_close(struct file *file)
+{
+}
+
+#endif /* !CONFIG_TALPA */
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_TALPA_H__ */
diff --git a/security/Kconfig b/security/Kconfig
index 5592939..cb32796 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -117,6 +117,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR
 
 source security/selinux/Kconfig
 source security/smack/Kconfig
+source security/talpa/Kconfig
 
 endmenu
 
diff --git a/security/Makefile b/security/Makefile
index f654260..2d5d3a8 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -5,6 +5,7 @@
 obj-$(CONFIG_KEYS)			+= keys/
 subdir-$(CONFIG_SECURITY_SELINUX)	+= selinux
 subdir-$(CONFIG_SECURITY_SMACK)		+= smack
+subdir-$(CONFIG_TALPA)			+= talpa
 
 # always enable default capabilities
 obj-y		+= commoncap.o
@@ -16,3 +17,4 @@ obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/built-in.o
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/built-in.o
 obj-$(CONFIG_SECURITY_ROOTPLUG)		+= root_plug.o
 obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
+obj-$(CONFIG_TALPA)			+= talpa/built-in.o
diff --git a/security/talpa/Kconfig b/security/talpa/Kconfig
new file mode 100644
index 0000000..0b8449e
--- /dev/null
+++ b/security/talpa/Kconfig
@@ -0,0 +1,9 @@
+config TALPA
+	bool "File content vetting interface"
+	default n
+	help
+	  Talpa is a high-performance filesystem access interception
+          package providing facilities for user-mode daemons/programs
+          to 'vet' filesystem operations before they are executed.
+
+	  If you are unsure how to answer this question, answer Y.
diff --git a/security/talpa/Makefile b/security/talpa/Makefile
new file mode 100644
index 0000000..676fc90
--- /dev/null
+++ b/security/talpa/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Talpa
+#
+
+obj-$(CONFIG_TALPA) := talpa.o
+
+talpa-y := talpa_interceptor.o
diff --git a/security/talpa/talpa.h b/security/talpa/talpa.h
new file mode 100644
index 0000000..2c4fb6f
--- /dev/null
+++ b/security/talpa/talpa.h
@@ -0,0 +1,64 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef __TALPA_H__
+#define __TALPA_H__
+
+#include <linux/fs.h>
+#include <linux/talpa.h>
+
+/* Talpa filter interface definitions. */
+
+/**
+ * enum talpa_action - filter response
+ * @TALPA_NEXT: do not care or do not know what to do
+ * @TALPA_ALLOW: operation should be allowed
+ * @TALPA_DENY: operation should be denied
+ * @TALPA_TIMEOUT: decision could not be made in a reasonable time
+ * @TALPA_ERROR: decision could not be made due to an error
+ *
+ * Each filters returns it's response from one of these values signalling
+ * it's opinion on the vetting operation in progress.
+ */
+enum talpa_action {
+	TALPA_NEXT = 0,
+	TALPA_ALLOW = 1,
+	TALPA_DENY = 2,
+	TALPA_TIMEOUT = 3,
+	TALPA_ERROR = 4,
+};
+
+/**
+ * struct talpa_file_vetting - object passed to filters for vetting
+ * @operation: type of operation being vetted
+ * @file: file pointer for the vetted object
+ * @flags: as passed to open(2)
+ * @authoritative: set to non-zero when decision has been made based on file content
+ * @code: error code if applicable
+ *
+ * This object is created by the interceptor and passed to all filters
+ * to do the vetting.
+ */
+struct talpa_file_vetting {
+	enum talpa_operation operation;
+	struct file *file;
+	int flags;
+	unsigned int authoritative;
+	int code;
+};
+
+#endif /* __TALPA_H__ */
diff --git a/security/talpa/talpa_allow_calls.h b/security/talpa/talpa_allow_calls.h
new file mode 100644
index 0000000..eb24482
--- /dev/null
+++ b/security/talpa/talpa_allow_calls.h
@@ -0,0 +1,5 @@
+#include "talpa.h"
+
+static inline void talpa_allow_calls(struct talpa_file_vetting *tfv)
+{
+}
diff --git a/security/talpa/talpa_deny_calls.h b/security/talpa/talpa_deny_calls.h
new file mode 100644
index 0000000..011d5c2
--- /dev/null
+++ b/security/talpa/talpa_deny_calls.h
@@ -0,0 +1,5 @@
+#include "talpa.h"
+
+static inline void talpa_deny_calls(struct talpa_file_vetting *tfv)
+{
+}
diff --git a/security/talpa/talpa_evaluation_calls.h b/security/talpa/talpa_evaluation_calls.h
new file mode 100644
index 0000000..367a149
--- /dev/null
+++ b/security/talpa/talpa_evaluation_calls.h
@@ -0,0 +1,6 @@
+#include "talpa.h"
+
+static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv)
+{
+	return TALPA_NEXT;
+}
diff --git a/security/talpa/talpa_interceptor.c b/security/talpa/talpa_interceptor.c
new file mode 100644
index 0000000..bde8a59
--- /dev/null
+++ b/security/talpa/talpa_interceptor.c
@@ -0,0 +1,116 @@
+/*
+ *  Copyright (C) 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/errno.h>
+#include <linux/talpa.h>
+
+#include "talpa_evaluation_calls.h"
+#include "talpa_allow_calls.h"
+#include "talpa_deny_calls.h"
+
+#include "talpa.h"
+
+static struct kmem_cache *tfv_cache; /* Cache for the above structure */
+
+/* Forward declare implementation functions. */
+static int talpa_vet_file(struct file *file, int flags, enum talpa_operation op);
+
+/* Externally visible interface. */
+int talpa_vet_file_open(struct file *file, int flags)
+{
+	return talpa_vet_file(file, flags, TALPA_OPEN);
+}
+
+void talpa_vet_file_close(struct file *file)
+{
+	talpa_vet_file(file, file->f_flags, TALPA_CLOSE);
+}
+
+/* Main work function */
+static int talpa_vet_file(struct file *file, int flags, enum talpa_operation op)
+{
+	struct talpa_file_vetting *tfv = NULL;
+	enum talpa_action action = TALPA_NEXT;
+	int ret = -ENOSYS;
+
+	/* Try to allocate vetting details or block access.
+	   Cache will not be available before initcalls are run so
+	   allow all access until then. */
+	if (unlikely(!tfv_cache))
+		return 0;
+
+	tfv = kmem_cache_zalloc(tfv_cache, GFP_KERNEL);
+	if (!tfv)
+		return -ENOMEM;
+
+	tfv->operation = op;
+	tfv->file = file;
+	tfv->flags = flags;
+
+	action = talpa_evaluation_calls(tfv);
+
+	/* Response can never be authoritative if response was indifferent. */
+	if (action == TALPA_NEXT)
+		tfv->authoritative = 0;
+
+	/* call post-eval function depedning on result */
+	if (action == TALPA_ALLOW || action == TALPA_NEXT)
+		talpa_allow_calls(tfv);
+	else
+		talpa_deny_calls(tfv);
+
+	/* Choose return code depending on the outcome. */
+	switch (action) {
+	case TALPA_ALLOW:
+	case TALPA_NEXT:
+		ret = 0;
+		break;
+	case TALPA_DENY:
+		ret = -EACCES;
+		break;
+	case TALPA_TIMEOUT:
+		ret = -ETIME;
+		break;
+	case TALPA_ERROR:
+		if (tfv->code)
+			ret = tfv->code;
+		else
+			ret = -EACCES;
+		break;
+	}
+
+	/* Free stuff we have (or might have) allocated. */
+	kmem_cache_free(tfv_cache, tfv);
+
+	return ret;
+}
+
+static __init int talpa_interceptor_init(void)
+{
+	/* No point in running with a security subsystem which does
+	   not work so we will panic if cannot allocate the cache here. */
+	tfv_cache = kmem_cache_create("talpa_file_vetting", sizeof(struct talpa_file_vetting),
+					SLAB_PANIC, 0, NULL);
+	return 0;
+}
+__initcall(talpa_interceptor_init);

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 2/5] [TALPA] securityfs configuration interfaces
  2008-08-04 21:00 [RFC 1/5] [TALPA] Hooking points and kernel interception Eric Paris
@ 2008-08-04 21:00 ` Eric Paris
  2008-08-04 21:00   ` [RFC 3/5] [TALPA] Access result caching Eric Paris
  2008-08-05  0:28 ` [RFC 1/5] [TALPA] Hooking points and kernel interception Christoph Hellwig
  1 sibling, 1 reply; 6+ messages in thread
From: Eric Paris @ 2008-08-04 21:00 UTC (permalink / raw)
  To: malware-list, linux-kernel; +Cc: Eric Paris

Each filter can choose to export a set of configuration options
which is implemented through securityfs on the low-level.

Signed-off-by: Eric Paris <eparis@redhat.com>
---
 security/talpa/Makefile              |    4 +-
 security/talpa/talpa.h               |   41 +++++++++
 security/talpa/talpa_common.c        |   54 ++++++++++++
 security/talpa/talpa_configuration.c |  155 ++++++++++++++++++++++++++++++++++
 4 files changed, 253 insertions(+), 1 deletions(-)
 create mode 100644 security/talpa/talpa_common.c
 create mode 100644 security/talpa/talpa_configuration.c

diff --git a/security/talpa/Makefile b/security/talpa/Makefile
index 676fc90..41045d7 100644
--- a/security/talpa/Makefile
+++ b/security/talpa/Makefile
@@ -4,4 +4,6 @@
 
 obj-$(CONFIG_TALPA) := talpa.o
 
-talpa-y := talpa_interceptor.o
+talpa-y :=	talpa_interceptor.o \
+		talpa_common.o \
+		talpa_configuration.o
diff --git a/security/talpa/talpa.h b/security/talpa/talpa.h
index 2c4fb6f..871b6d4 100644
--- a/security/talpa/talpa.h
+++ b/security/talpa/talpa.h
@@ -1,5 +1,6 @@
 /*
  *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
  *
  *  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
@@ -61,4 +62,44 @@ struct talpa_file_vetting {
 	int code;
 };
 
+/**
+ * struct talpa_configuration - configuration description for filters
+ * @name: name of the configuration
+ * @mode: access mode
+ * @data: private data to pass to get and set callbacks
+ * @get: callback to read out configuration value
+ * @set: callback to write in configuration value
+ *
+ * Filter wanting to have configurable items passes in an array of these
+ * structures at registration time. Last item should have name set to
+ * NULL.
+ * Get and set callbacks will be called with a pointer to a single
+ * configuration item.
+ * Set callback is guaranteed to provide a null-terminated string
+ * with no newline characters and len set to string length.
+ */
+struct talpa_configuration {
+	char *name;
+	int mode;
+	void *data;
+	ssize_t (*get)(struct talpa_configuration *cfg, char *buf, size_t len);
+	ssize_t (*set)(struct talpa_configuration *cfg, char *buf, size_t len);
+};
+
+/**
+ * talpa_register_configuration - register an array of configuration items
+ * @group: group name
+ * @name: filter name
+ * @cfg: array of configuration items
+ *
+ * This is an internal function which will be called for filters which
+ * specify configuration on registration time.
+ */
+extern struct dentry *talpa_register_configuration(char *name, struct talpa_configuration *cfg);
+
+/* Generic configuration get and set methods which can be used in simple cases. */
+extern ssize_t talpa_generic_get_ulong(struct talpa_configuration *cfg, char *buf, size_t len);
+extern ssize_t talpa_generic_set_ulong(struct talpa_configuration *cfg, char *buf, size_t len);
+extern ssize_t talpa_generic_get_long(struct talpa_configuration *cfg, char *buf, size_t len);
+
 #endif /* __TALPA_H__ */
diff --git a/security/talpa/talpa_common.c b/security/talpa/talpa_common.c
new file mode 100644
index 0000000..2dd3eb1
--- /dev/null
+++ b/security/talpa/talpa_common.c
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+
+#include "talpa.h"
+
+ssize_t talpa_generic_get_ulong(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	int ret = snprintf(buf, len, "%lu\n", *((unsigned long *)cfg->data));
+	if (ret > 0)
+		ret++;
+
+	return ret;
+}
+
+ssize_t talpa_generic_set_ulong(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	ssize_t ret = 0;
+
+	if (strict_strtoul(buf, 10, (unsigned long *)cfg->data))
+		return -EINVAL;
+
+	return ret;
+
+}
+
+ssize_t talpa_generic_get_long(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	int ret = snprintf(buf, len, "%ld\n", *((long *)cfg->data));
+	if (ret > 0)
+		ret++;
+
+	return ret;
+}
diff --git a/security/talpa/talpa_configuration.c b/security/talpa/talpa_configuration.c
new file mode 100644
index 0000000..18378b5
--- /dev/null
+++ b/security/talpa/talpa_configuration.c
@@ -0,0 +1,155 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/security.h>
+#include <linux/talpa.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+
+#include "talpa.h"
+
+/* Talpa configuration root. */
+static struct dentry *talpa_fs_root;
+
+/* Filesytem read function. */
+static ssize_t talpa_fs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct talpa_configuration *cfg;
+	char *data;
+	ssize_t ret;
+
+
+	cfg = (struct talpa_configuration *)file->f_dentry->d_inode->i_private;
+	if (!cfg)
+		return -EBADF;
+
+	if (!cfg->get)
+		return -ENOSYS;
+
+	/* Return EOF for second read (whole value must be read in one go) */
+	if (!count || file->f_pos)
+		return 0;
+
+	data = kzalloc(count, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = cfg->get(cfg, data, count);
+	if (ret > 0 && copy_to_user(buf, data, ret)) {
+		kfree(data);
+		return -EFAULT;
+	}
+
+	kfree(data);
+	*ppos = ret;
+	return ret;
+}
+
+/* Filesytem write function. */
+static ssize_t talpa_fs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	struct talpa_configuration *cfg;
+	char *data, *ptr, *end;
+	ssize_t ret;
+	size_t len = 0;
+
+	cfg = (struct talpa_configuration *)file->f_dentry->d_inode->i_private;
+	if (!cfg)
+		return -EBADF;
+
+	if (!cfg->set)
+		return -ENOSYS;
+
+	if (!count)
+		return 0;
+
+	if (file->f_pos)
+		return -EINVAL;
+
+	/* Make sure we have space in buffer is string is
+	   not null-terminated. */
+	data = kzalloc(count + 1, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	if (copy_from_user(data, buf, count)) {
+		kfree(data);
+		return -EFAULT;
+	}
+
+	/* Null terminate string on first newline and find out it's
+	   resulting length. */
+	ptr = data;
+	end = data + count;
+	while (ptr < end) {
+		if (*ptr == 0 || *ptr == '\n') {
+			*ptr = 0;
+			break;
+		}
+		len++;
+		ptr++;
+	}
+
+	ret = cfg->set(cfg, data, len);
+	if (ret >= 0) {
+		*ppos = ret;
+		ret = count;
+	}
+	kfree(data);
+	return ret;
+}
+
+static struct file_operations talpa_fs_ops = {
+	.open = nonseekable_open,
+	.read = talpa_fs_read,
+	.write = talpa_fs_write,
+};
+
+/* Externally visible registration function. */
+struct dentry *talpa_register_configuration(char *name, struct talpa_configuration *cfg)
+{
+	struct dentry *subdir;
+	struct dentry *param;
+
+	/* Get root sub-directory for this group. */
+	subdir = securityfs_create_dir(name, talpa_fs_root);
+	if (!subdir)
+		return ERR_PTR(-ENOMEM);
+
+	/* Create files for configuration items. */
+	for (; cfg->name != NULL; cfg++)
+		param = securityfs_create_file(cfg->name, cfg->mode, subdir, cfg, &talpa_fs_ops);
+
+	return subdir;
+}
+
+static __init int talpa_configuration_init(void)
+{
+	talpa_fs_root = securityfs_create_dir("talpa", NULL);
+	if (!talpa_fs_root)
+		return -ENOMEM;
+
+	return 0;
+}
+
+__initcall(talpa_configuration_init);
-- 
1.5.2.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 3/5] [TALPA] Access result caching
  2008-08-04 21:00 ` [RFC 2/5] [TALPA] securityfs configuration interfaces Eric Paris
@ 2008-08-04 21:00   ` Eric Paris
  2008-08-04 21:00     ` [RFC 4/5] [TALPA] Exclude threads from decision making Eric Paris
  0 siblings, 1 reply; 6+ messages in thread
From: Eric Paris @ 2008-08-04 21:00 UTC (permalink / raw)
  To: malware-list, linux-kernel; +Cc: Eric Paris

Cache both positive and negative access results.  All inode access starts
allowed in the cache for performance reasons.  Descriptions of the cache operation
can be found in Documentation/talpa/cache and in security/talpa/talpa_cache.c

Signed-off-by: Eric Paris <eparis@redhat.com>
---
 Documentation/talpa/cache               |   17 +++
 fs/inode.c                              |    6 +
 fs/namei.c                              |    2 +
 include/linux/fs.h                      |    5 +
 include/linux/talpa.h                   |   17 +++
 security/talpa/Kconfig                  |   13 ++
 security/talpa/Makefile                 |    2 +
 security/talpa/talpa.h                  |   10 ++
 security/talpa/talpa_allow_calls.h      |    7 +
 security/talpa/talpa_cache.c            |  207 +++++++++++++++++++++++++++++++
 security/talpa/talpa_cache.h            |   22 ++++
 security/talpa/talpa_common.c           |    2 +
 security/talpa/talpa_deny_calls.h       |    6 +
 security/talpa/talpa_evaluation_calls.h |   26 ++++-
 14 files changed, 341 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/talpa/cache
 create mode 100644 security/talpa/talpa_cache.c
 create mode 100644 security/talpa/talpa_cache.h

diff --git a/Documentation/talpa/cache b/Documentation/talpa/cache
new file mode 100644
index 0000000..8010b53
--- /dev/null
+++ b/Documentation/talpa/cache
@@ -0,0 +1,17 @@
+The cache mechanism of talpa is incredibly simple.
+
+Talpa maintains a global long called talpa_cache_seqno which is initially zero and monotomically increases either when there is a talpa configuration change or when userspace signals it should be increased.  Caching of inodes is done by adding a field to each inode, i_talpa_cache_seqno.  If talpa determines authoritatively that access to an inode should be allowed or denied the value of the global sequence number will be assigned to the inode.  When allowed the inode is assigned the same value as the global and when denied the inode is assigned the negative of the global value.
+
+When a future access is attempted on an inode the value in inode is compared to the global sequence number.  If they match the access is permitted.  If the inode value is the negative of the global the access is denied.  If the value of the inode is less than the global we can neither allow nor deny access and instead let other filters make the decision.
+
+Invalidating the cache is atomic and is as simple as incrementing the global talpa_cache_seqno.
+
+**********************
+
+/security/talpa/cache/*
+
+enabled: RW:	0 for caching disabled or 1 for caching enabled
+hits: RO:	number of cache hits since the system started
+misses: RO:	number of cache misses since the system started
+increment_seqno: WRONLY:  echo 1 into this file will increment the global sequence number and thus invalidate the cache
+seqno: RO:	the value of the global sequence number
diff --git a/fs/inode.c b/fs/inode.c
index b6726f6..2de5a35 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -17,6 +17,7 @@
 #include <linux/hash.h>
 #include <linux/swap.h>
 #include <linux/security.h>
+#include <linux/talpa.h>
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
@@ -182,6 +183,9 @@ static struct inode *alloc_inode(struct super_block *sb)
 		}
 		inode->i_private = NULL;
 		inode->i_mapping = mapping;
+#ifdef CONFIG_TALPA_CACHE
+		inode->i_talpa_cache_seqno = 0;
+#endif
 	}
 	return inode;
 }
@@ -1268,6 +1272,8 @@ void file_update_time(struct file *file)
 		sync_it = 1;
 	}
 
+	talpa_invalidate_inode(inode);
+
 	if (sync_it)
 		mark_inode_dirty_sync(inode);
 	mnt_drop_write(file->f_path.mnt);
diff --git a/fs/namei.c b/fs/namei.c
index 4ea63ed..0eef4ae 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -31,6 +31,7 @@
 #include <linux/file.h>
 #include <linux/fcntl.h>
 #include <linux/device_cgroup.h>
+#include <linux/talpa.h>
 #include <asm/uaccess.h>
 
 #define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
@@ -335,6 +336,7 @@ int get_write_access(struct inode * inode)
 		return -ETXTBSY;
 	}
 	atomic_inc(&inode->i_writecount);
+	talpa_invalidate_inode(inode);
 	spin_unlock(&inode->i_lock);
 
 	return 0;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 580b513..30bda8f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -680,6 +680,11 @@ struct inode {
 #ifdef CONFIG_SECURITY
 	void			*i_security;
 #endif
+
+#ifdef CONFIG_TALPA_CACHE
+	long			i_talpa_cache_seqno;
+#endif
+
 	void			*i_private; /* fs or device private pointer */
 };
 
diff --git a/include/linux/talpa.h b/include/linux/talpa.h
index 4ae05ba..52750b7 100644
--- a/include/linux/talpa.h
+++ b/include/linux/talpa.h
@@ -47,6 +47,23 @@ enum talpa_operation {
 
 #include <linux/fs.h>
 
+#if defined CONFIG_TALPA && defined CONFIG_TALPA_CACHE
+/**
+ * talpa_invalidate_inode - marks inode status as unknown
+ * @inode:inode to mark
+ *
+ * When write access for an inode is taken cache status must be
+ * invalidated which is what this helper function does.
+ */
+static inline void talpa_invalidate_inode(struct inode *inode)
+{
+	inode->i_talpa_cache_seqno = 0;
+}
+#else
+static inline void talpa_invalidate_inode(struct inode *inode)
+{ }
+#endif
+
 #ifdef CONFIG_TALPA
 
 /* Internal interface. */
diff --git a/security/talpa/Kconfig b/security/talpa/Kconfig
index 0b8449e..7910cc1 100644
--- a/security/talpa/Kconfig
+++ b/security/talpa/Kconfig
@@ -7,3 +7,16 @@ config TALPA
           to 'vet' filesystem operations before they are executed.
 
 	  If you are unsure how to answer this question, answer Y.
+
+
+config TALPA_CACHE
+	bool "Caching of vetting decisions"
+	depends on TALPA
+	default y
+	help
+	  Caching of vetting decisions is crucial for minimising effect
+	  of vetting operations on system performance.
+
+	  By default all block device backed filesystems are cached.
+
+	  If you are unsure how to answer this question, answer Y.
diff --git a/security/talpa/Makefile b/security/talpa/Makefile
index 41045d7..8da21b9 100644
--- a/security/talpa/Makefile
+++ b/security/talpa/Makefile
@@ -7,3 +7,5 @@ obj-$(CONFIG_TALPA) := talpa.o
 talpa-y :=	talpa_interceptor.o \
 		talpa_common.o \
 		talpa_configuration.o
+
+talpa-$(CONFIG_TALPA_CACHE) += talpa_cache.o
diff --git a/security/talpa/talpa.h b/security/talpa/talpa.h
index 871b6d4..8a93512 100644
--- a/security/talpa/talpa.h
+++ b/security/talpa/talpa.h
@@ -60,6 +60,7 @@ struct talpa_file_vetting {
 	int flags;
 	unsigned int authoritative;
 	int code;
+	long cache_seqno;
 };
 
 /**
@@ -97,6 +98,15 @@ struct talpa_configuration {
  */
 extern struct dentry *talpa_register_configuration(char *name, struct talpa_configuration *cfg);
 
+#ifdef CONFIG_TALPA_CACHE
+extern ssize_t talpa_increment_seqno(struct talpa_configuration *cfg, char *buf, size_t len);
+#else /* CONFIG_TALPA_CACHE */
+static inline ssize_t talpa_increment_seqno(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	return 0;
+}
+#endif /* CONFIG_TALPA_CACHE */
+
 /* Generic configuration get and set methods which can be used in simple cases. */
 extern ssize_t talpa_generic_get_ulong(struct talpa_configuration *cfg, char *buf, size_t len);
 extern ssize_t talpa_generic_set_ulong(struct talpa_configuration *cfg, char *buf, size_t len);
diff --git a/security/talpa/talpa_allow_calls.h b/security/talpa/talpa_allow_calls.h
index eb24482..1f4b309 100644
--- a/security/talpa/talpa_allow_calls.h
+++ b/security/talpa/talpa_allow_calls.h
@@ -1,5 +1,12 @@
 #include "talpa.h"
+#include "talpa_cache.h"
 
 static inline void talpa_allow_calls(struct talpa_file_vetting *tfv)
 {
+	enum talpa_action ret;
+
+#ifdef CONFIG_TALPA_CACHE
+	ret = talpa_cache_allow(tfv);
+#endif /* CONFIG_TALPA_CACHE */
+
 }
diff --git a/security/talpa/talpa_cache.c b/security/talpa/talpa_cache.c
new file mode 100644
index 0000000..239c494
--- /dev/null
+++ b/security/talpa/talpa_cache.c
@@ -0,0 +1,207 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/talpa.h>
+#include <linux/kdev_t.h>
+
+#include "talpa.h"
+
+
+/*
+ * Cache filter caches both positive and negative outcomes of vetting
+ * if they were made based on file content (authoritative).
+ * What is actually cached are inodes which are marked with a positive
+ * or negative seqno number. Token number is a serial number which
+ * can be incremented from outside with the purpose of invalidating
+ * the cache when external entity decides that the set of rules has
+ * changed sufficiently to require that. There is a limit on maximum
+ * number of seqno increases (LONG_MAX) after which it is impossible
+ * to invalidate the cache in this way. However, due to the size of
+ * LONG_MAX it is hard to imagine how this limit would be a problem.
+ * Inode cache status is cleared as soon as write access for that
+ * inode is obtained.
+ * Inodes which are opened for writing elsewhere are not cached at
+ * the time authoritative positive response is received.
+ */
+
+static unsigned long talpa_cache_enbl = 1;
+/*
+ * lock all read/write of auth_seqno.  inside inc_seqno() is could go
+ * negative and then be assigned to an inode incorrectly inside the allow/deny
+ * functions or could cause a BUG in the examine function.
+ */
+DEFINE_MUTEX(talpa_cache_seqno_lock);
+long talpa_cache_seqno;
+static unsigned long talpa_cache_hits;
+static unsigned long talpa_cache_misses;
+
+
+enum talpa_action talpa_cache_examine(struct talpa_file_vetting *tfv)
+{
+	int ret = TALPA_NEXT;
+
+	/*
+	 * order here probably doesn't matter but just to be safe grab
+	 * the serial first.  The race I'm scared of (but probably
+	 * impossible is:
+	 *   Task A          Task B          Task C
+	 *   -Collect seqno
+	 *                   -increment seqno
+	 *                                   -update ino with new seqno
+	 *   -collect serial from ino
+	 *   -BUG()
+	 * Collecting serial first just means we might scan a couple
+	 * extra times, but at least its safe.
+	 */
+	long serial = tfv->file->f_dentry->d_inode->i_talpa_cache_seqno;
+	long seqno;
+
+	mutex_lock(&talpa_cache_seqno_lock);
+	seqno  = talpa_cache_seqno;
+	mutex_unlock(&talpa_cache_seqno_lock);
+
+	BUG_ON(serial > seqno);
+	BUG_ON(serial < -seqno);
+	/* If serial == seqno we already approved this inode.
+	   Opposite rules are valid for negative cachings. */
+	if (serial == seqno) {
+		ret = TALPA_ALLOW;
+		talpa_cache_hits++;
+	} else if (serial == -seqno) {
+		ret = TALPA_DENY;
+		talpa_cache_hits++;
+	} else {
+		talpa_cache_misses++;
+	}
+
+	return ret;
+}
+
+static inline int writable_elsewhere(struct inode *inode, unsigned long flags)
+{
+	if (atomic_read(&inode->i_writecount) <= 0)
+		return 0;
+
+	if (atomic_read(&inode->i_writecount) == 1 && flags&(O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_TRUNC))
+		return 0;
+
+	return 1;
+}
+
+enum talpa_action talpa_cache_allow(struct talpa_file_vetting *tfv)
+{
+	struct inode *inode = tfv->file->f_dentry->d_inode;
+
+	/* Only block device backed filesystem are cached. */
+	if (inode->i_sb->s_type->fs_flags & FS_REQUIRES_DEV) {
+		spin_lock(&inode->i_lock);
+		/* Cache inode on authoritative allow if it is
+		   not writable elsewhere, we could cache here,
+		   but we really don't know if we raced between this
+		   scan and the last write operation.... */
+		if (tfv->authoritative && !writable_elsewhere(inode, tfv->flags))
+			inode->i_talpa_cache_seqno = tfv->cache_seqno;
+		spin_unlock(&inode->i_lock);
+	}
+
+	return TALPA_NEXT;
+}
+
+enum talpa_action talpa_cache_deny(struct talpa_file_vetting *tfv)
+{
+	struct inode *inode = tfv->file->f_dentry->d_inode;
+
+	/* Cache inode on authoritative deny if it is
+	   not writable elsewhere. */
+	spin_lock(&inode->i_lock);
+	if (tfv->authoritative && !writable_elsewhere(inode, tfv->flags))
+		inode->i_talpa_cache_seqno = -tfv->cache_seqno;
+	spin_unlock(&inode->i_lock);
+
+	return TALPA_NEXT;
+}
+
+ssize_t talpa_increment_seqno(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	int ret = len;
+
+	mutex_lock(&talpa_cache_seqno_lock);
+	talpa_cache_seqno++;
+	if (talpa_cache_seqno < 0) {
+		talpa_cache_seqno = LONG_MAX;
+		ret = -ERANGE;
+	}
+	mutex_unlock(&talpa_cache_seqno_lock);
+
+	return ret;
+}
+
+static struct talpa_configuration talpa_cache_cfg[] = {
+	{
+		.name = "enabled",
+		.mode = S_IRUSR|S_IWUSR|S_IRGRP,
+		.data = &talpa_cache_enbl,
+		.get = talpa_generic_get_ulong,
+		.set = talpa_generic_set_ulong,
+	},
+	{
+		.name = "seqno",
+		.mode = S_IRUSR,
+		.data = &talpa_cache_seqno,
+		.get = talpa_generic_get_long,
+	},
+	{
+		.name = "increment_seqno",
+		.mode = S_IWUSR,
+		.set = talpa_increment_seqno,
+	},
+	{
+		.name = "hits",
+		.mode = S_IRUSR|S_IRGRP,
+		.data = &talpa_cache_hits,
+		.get = talpa_generic_get_ulong,
+	},
+	{
+		.name = "misses",
+		.mode = S_IRUSR|S_IRGRP,
+		.data = &talpa_cache_misses,
+		.get = talpa_generic_get_ulong,
+	},
+	{
+	},
+};
+
+static __init int talpa_cache_init(void)
+{
+	struct dentry *dentry = 0;
+
+	mutex_init(&talpa_cache_seqno_lock);
+
+	dentry = talpa_register_configuration("cache", talpa_cache_cfg);
+	if (IS_ERR(dentry))
+		return PTR_ERR(dentry);
+
+	return 0;
+}
+
+__initcall(talpa_cache_init);
diff --git a/security/talpa/talpa_cache.h b/security/talpa/talpa_cache.h
new file mode 100644
index 0000000..aa23dd8
--- /dev/null
+++ b/security/talpa/talpa_cache.h
@@ -0,0 +1,22 @@
+#include "talpa.h"
+
+#ifdef CONFIG_TALPA_CACHE
+extern enum talpa_action talpa_cache_deny(struct talpa_file_vetting *tfv);
+extern enum talpa_action talpa_cache_allow(struct talpa_file_vetting *tfv);
+extern enum talpa_action talpa_cache_examine(struct talpa_file_vetting *tfv);
+#else /* CONFIG_TALPA_CACHE */
+static inline enum talpa_action talpa_cache_deny(struct talpa_file_vetting *tfv)
+{
+	return TALPA_NEXT;
+}
+
+static inline enum talpa_action talpa_cache_allow(struct talpa_file_vetting *tfv)
+{
+	return TALPA_NEXT;
+}
+
+static inline enum talpa_action talpa_cache_examine(struct talpa_file_vetting *tfv)
+{
+	return TALPA_NEXT;
+}
+#endif /* CONFIG_TALPA_CACHE */
diff --git a/security/talpa/talpa_common.c b/security/talpa/talpa_common.c
index 2dd3eb1..7ee4254 100644
--- a/security/talpa/talpa_common.c
+++ b/security/talpa/talpa_common.c
@@ -40,6 +40,8 @@ ssize_t talpa_generic_set_ulong(struct talpa_configuration *cfg, char *buf, size
 	if (strict_strtoul(buf, 10, (unsigned long *)cfg->data))
 		return -EINVAL;
 
+	ret = talpa_increment_seqno(cfg, buf, len);
+
 	return ret;
 
 }
diff --git a/security/talpa/talpa_deny_calls.h b/security/talpa/talpa_deny_calls.h
index 011d5c2..e3b404e 100644
--- a/security/talpa/talpa_deny_calls.h
+++ b/security/talpa/talpa_deny_calls.h
@@ -1,5 +1,11 @@
 #include "talpa.h"
+#include "talpa_cache.h"
 
 static inline void talpa_deny_calls(struct talpa_file_vetting *tfv)
 {
+	enum talpa_action ret;
+
+#ifdef CONFIG_TALPA_CACHE
+	ret = talpa_cache_deny(tfv);
+#endif /* CONFIG_TALPA_CACHE */
 }
diff --git a/security/talpa/talpa_evaluation_calls.h b/security/talpa/talpa_evaluation_calls.h
index 367a149..24d0955 100644
--- a/security/talpa/talpa_evaluation_calls.h
+++ b/security/talpa/talpa_evaluation_calls.h
@@ -1,6 +1,30 @@
+#include <linux/mutex.h>
+
 #include "talpa.h"
+#include "talpa_cache.h"
 
 static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv)
 {
-	return TALPA_NEXT;
+	enum talpa_action ret = TALPA_NEXT;
+
+#ifdef CONFIG_TALPA_CACHE
+	/*
+	 * collect the seqno number before decision is made.  Thus later during
+	 * allow/deny handling we won't possibly set a seqno number newer than
+	 * the object in question was analyzed with.  We might set one too low,
+	 * but that will just be a cache miss.
+	 */
+	extern struct mutex talpa_cache_seqno_lock;
+	extern long talpa_cache_seqno;
+
+	mutex_lock(&talpa_cache_seqno_lock);
+	tfv->cache_seqno = talpa_cache_seqno;
+	mutex_unlock(&talpa_cache_seqno_lock);
+
+	ret = talpa_cache_examine(tfv);
+	if (ret != TALPA_NEXT)
+		return ret;
+#endif /* CONFIG_TALPA_CACHE */
+
+	return ret;
 }
-- 
1.5.2.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 4/5] [TALPA] Exclude threads from decision making
  2008-08-04 21:00   ` [RFC 3/5] [TALPA] Access result caching Eric Paris
@ 2008-08-04 21:00     ` Eric Paris
  2008-08-04 21:00       ` [RFC 5/5] [TALPA] Userspace vetting of open and close access Eric Paris
  0 siblings, 1 reply; 6+ messages in thread
From: Eric Paris @ 2008-08-04 21:00 UTC (permalink / raw)
  To: malware-list, linux-kernel; +Cc: Eric Paris

Sometimes it is necessary for certain processes to bypass the interception
by Talpa.  For example that may be a purely userspace on-demand scanner which wouldn't
work if unable to access malicious files.

This is implemented as a process flag in the task_struct.

Signed-off-by: Eric Paris <eparis@redhat.com>
---
 Documentation/talpa/tecat.c           |   50 +++++++++++++++++++++++++
 Documentation/talpa/thread_exclude    |    6 +++
 include/linux/sched.h                 |    1 +
 security/talpa/Kconfig                |   18 +++++++++
 security/talpa/Makefile               |    2 +
 security/talpa/talpa_interceptor.c    |    5 ++
 security/talpa/talpa_thread_exclude.c |   66 +++++++++++++++++++++++++++++++++
 7 files changed, 148 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/talpa/tecat.c
 create mode 100644 Documentation/talpa/thread_exclude
 create mode 100644 security/talpa/talpa_thread_exclude.c

diff --git a/Documentation/talpa/tecat.c b/Documentation/talpa/tecat.c
new file mode 100644
index 0000000..8c66d73
--- /dev/null
+++ b/Documentation/talpa/tecat.c
@@ -0,0 +1,50 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+	int talpa;
+	int fd;
+	ssize_t size;
+	char buffer[4096];
+	int args = argc - 1;
+
+	/* Register as an excluded client. */
+	talpa = open("/security/talpa/exclude/talpa-exclude", O_WRONLY);
+	if (!talpa) {
+		fprintf(stderr, "Cannot connect to Talpa - errno %d!\n", errno);
+		return 1;
+	}
+
+	/* Signal Talpa that we want to be excluded. */
+	if (write(talpa, "1", 1) != 1) {
+		fprintf(stderr, "Failed to exclude - errno %d!\n", errno);
+		return 1;
+	}
+
+	/* Cat files given as arguments. */
+	while (args > 0) {
+		fd = open(argv[args], O_RDONLY);
+		if (fd > 0) {
+			while ((size = read(fd, buffer, sizeof(buffer))) > 0)
+				write(STDOUT_FILENO, buffer, size);
+			close(fd);
+		} else {
+			fprintf(stderr, "%s: %s!\n", argv[args], strerror(errno));
+		}
+		args--;
+	}
+
+	/* Revert back to being intercepted. */
+	if (write(talpa, "0", 1) != 1) {
+		fprintf(stderr, "Failed to revert - errno %d!\n", errno);
+		return 1;
+	}
+
+	close(talpa);
+
+	return 0;
+}
diff --git a/Documentation/talpa/thread_exclude b/Documentation/talpa/thread_exclude
new file mode 100644
index 0000000..b2e9f10
--- /dev/null
+++ b/Documentation/talpa/thread_exclude
@@ -0,0 +1,6 @@
+Provide an interface for threads to exclude themselves from all talpa decisions and always be allowed access.  This is implemented using a task_struct flag.  Userspace clients which are expected to access content normally denied can use this interface to gain access.  Userspace vetting clients will have this flag automatically set so they do not recursively call themselves asking for permissions to open files to check them for malware.
+
+***********
+/security/talpa/exclude/*
+
+exclude: WRONLY:	echo 1 into this file will cause the current task to be excluded from talpa access decisions.  0 is default
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 5270d44..0274f85 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1502,6 +1502,7 @@ static inline void put_task_struct(struct task_struct *t)
 #define PF_SPREAD_PAGE	0x01000000	/* Spread page cache over cpuset */
 #define PF_SPREAD_SLAB	0x02000000	/* Spread some slab caches over cpuset */
 #define PF_THREAD_BOUND	0x04000000	/* Thread bound to specific cpu */
+#define PF_TALPA_EXCL	0x08000000	/* Processes which talpa should exclude */
 #define PF_MEMPOLICY	0x10000000	/* Non-default NUMA mempolicy */
 #define PF_MUTEX_TESTER	0x20000000	/* Thread belongs to the rt mutex tester */
 #define PF_FREEZER_SKIP	0x40000000	/* Freezer should not count it as freezeable */
diff --git a/security/talpa/Kconfig b/security/talpa/Kconfig
index 7910cc1..c5528e5 100644
--- a/security/talpa/Kconfig
+++ b/security/talpa/Kconfig
@@ -20,3 +20,21 @@ config TALPA_CACHE
 	  By default all block device backed filesystems are cached.
 
 	  If you are unsure how to answer this question, answer Y.
+
+
+config TALPA_THREAD_EXCLUSION
+	bool "Thread exclusions"
+	depends on TALPA
+	default y
+	help
+	  This adds the ability for certain threads to register
+          themselves as excluded from the vetting process. It is useful
+          for allowing certain classes of userspace programs (for
+          example anti-malware or anti-rootkit scanner) to bypass
+          inteception which would otherwise interfere with their
+	  operation.
+
+	  Access to this facility is controlled via permissions
+	  on a corresponding device node.
+
+	  If you are unsure how to answer this question, answer Y.
diff --git a/security/talpa/Makefile b/security/talpa/Makefile
index 8da21b9..16cb1d0 100644
--- a/security/talpa/Makefile
+++ b/security/talpa/Makefile
@@ -9,3 +9,5 @@ talpa-y :=	talpa_interceptor.o \
 		talpa_configuration.o
 
 talpa-$(CONFIG_TALPA_CACHE) += talpa_cache.o
+talpa-$(CONFIG_TALPA_THREAD_EXCLUSION) += talpa_thread_exclude.o
+
diff --git a/security/talpa/talpa_interceptor.c b/security/talpa/talpa_interceptor.c
index bde8a59..9aefbf0 100644
--- a/security/talpa/talpa_interceptor.c
+++ b/security/talpa/talpa_interceptor.c
@@ -22,6 +22,7 @@
 #include <linux/fs.h>
 #include <linux/file.h>
 #include <linux/errno.h>
+#include <linux/sched.h>
 #include <linux/talpa.h>
 
 #include "talpa_evaluation_calls.h"
@@ -53,6 +54,10 @@ static int talpa_vet_file(struct file *file, int flags, enum talpa_operation op)
 	enum talpa_action action = TALPA_NEXT;
 	int ret = -ENOSYS;
 
+	/* Tasks marked as excluded are immediately ignored. */
+	if (current->flags & PF_TALPA_EXCL)
+		return 0;
+
 	/* Try to allocate vetting details or block access.
 	   Cache will not be available before initcalls are run so
 	   allow all access until then. */
diff --git a/security/talpa/talpa_thread_exclude.c b/security/talpa/talpa_thread_exclude.c
new file mode 100644
index 0000000..77e3c39
--- /dev/null
+++ b/security/talpa/talpa_thread_exclude.c
@@ -0,0 +1,66 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include "talpa.h"
+/*
+ * Very simple misc device driver. User can open it and exclude
+ * themselves from interception by writting ASCII 1 to it, or
+ * go back by writting ASCII 0.
+ */
+static ssize_t talpa_thread_excl_write(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	if (len == 1) {
+		if (*buf == '1') {
+			current->flags |= PF_TALPA_EXCL;
+			return 1;
+		} else if (*buf == '0') {
+			current->flags &= ~PF_TALPA_EXCL;
+			return 1;
+		}
+	}
+
+	return -EPROTO;
+}
+
+static struct talpa_configuration talpa_excl_cfg[] = {
+	{
+		.name = "exclude",
+		.mode = S_IRUSR|S_IWUSR,
+		.data = NULL,
+		.set = talpa_thread_excl_write,
+	},
+	{
+	},
+};
+
+static __init int talpa_thread_exclude_init(void)
+{
+	struct dentry *dentry = talpa_register_configuration("talpa-exclude", talpa_excl_cfg);
+	if (IS_ERR(dentry))
+		return PTR_ERR(dentry);
+	return 0;
+}
+
+__initcall(talpa_thread_exclude_init);
-- 
1.5.2.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 5/5] [TALPA] Userspace vetting of open and close access.
  2008-08-04 21:00     ` [RFC 4/5] [TALPA] Exclude threads from decision making Eric Paris
@ 2008-08-04 21:00       ` Eric Paris
  0 siblings, 0 replies; 6+ messages in thread
From: Eric Paris @ 2008-08-04 21:00 UTC (permalink / raw)
  To: malware-list, linux-kernel; +Cc: Eric Paris

Userspace vetting is an evaluation filter which passes vetting request
to an userspace daemon.

Example vetting client which allows access to everything except one very
specific file can be found in Documentation/talpa/allow_most.c.

Many vetting clients can register simultaneously but only one gets
a particular request. In other words userspace vetting clients do not
operate as a chain but as a set of consumers.

To facilitate access to the actual file a file descriptor is duplicated
into the vetting client process. In this way there is no reliance on
paths and different permission issues are bypassed.

Signed-off-by: Eric Paris <eparis@redhat.com>
---
 Documentation/talpa/allow_most.c        |  138 ++++++++
 Documentation/talpa/client              |   85 +++++
 Documentation/talpa/test_deny.c         |  356 ++++++++++++++++++++
 include/linux/talpa.h                   |   83 +++++
 security/talpa/Kconfig                  |   11 +
 security/talpa/Makefile                 |    2 +-
 security/talpa/talpa_client.c           |  543 +++++++++++++++++++++++++++++++
 security/talpa/talpa_evaluation_calls.h |    8 +
 8 files changed, 1225 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/talpa/allow_most.c
 create mode 100644 Documentation/talpa/client
 create mode 100644 Documentation/talpa/test_deny.c
 create mode 100644 security/talpa/talpa_client.c

diff --git a/Documentation/talpa/allow_most.c b/Documentation/talpa/allow_most.c
new file mode 100644
index 0000000..e563ed4
--- /dev/null
+++ b/Documentation/talpa/allow_most.c
@@ -0,0 +1,138 @@
+/* This is a userspace talpa client.  It looks for a file /root/denyme and
+ * will deny access to that file if it starts with the string "bad" */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "talpa.h"
+
+#define DENY_FILE "/root/denyme"
+
+/* read a file and return true if it contains the characters "bad" as the first 3 chars */
+int file_contains_bad(int fd)
+{
+	char buf[10];
+	int ret;
+
+	ret = read(fd, &buf[0], 10);
+
+	if (ret < 0) {
+		perror("reading test fd");
+		return -1;
+	}
+
+	buf[ret] = '\0';
+
+	if (ret < 3)
+		return 0;
+
+	if (!strncmp(&buf[0], "bad", 3))
+		return 1;
+
+	return 0;
+}
+
+int main()
+{
+	int talpa;
+	struct talpa_packet_client response;
+	size_t cnt = 0;
+
+	/* Register as a vetting client. */
+	talpa = open("/security/talpa/client/talpa-vetting", O_RDWR);
+	if (talpa < 0) {
+		fprintf(stderr, "Cannot connect to Talpa - errno %d!\n", errno);
+		return 1;
+	}
+
+	/* We will always use the same reply packet. */
+	response.header.version = TALPA_MAX_PACKET_VERSION;
+
+	for (;;) {
+		char buf[PATH_MAX];
+		char link[64];
+		char *path;
+		struct talpa_packet_kernel details;
+		ssize_t ret;
+
+
+		response.header.type = TALPA_PKT_OK;
+
+		/* Get vetting details. */
+		ret = read(talpa, &details, sizeof(details));
+		if (ret != sizeof(details)) {
+			fprintf(stderr, "RERR: %zd (expected %zd) - errno %d\n", ret, sizeof(details), errno);
+			return 2;
+		}
+
+		cnt++;
+		/* We may have got a file descriptor as well. */
+		if (details.fd >= 0) {
+			/* If so we can find out the path of the file. */
+			snprintf(link, sizeof(link), "/proc/self/fd/%d", details.fd);
+			ret = readlink(link, buf, sizeof(buf)-1);
+			if (ret > 0) {
+				path = buf;
+				path[ret] = '\0';
+			} else {
+				path = "<error>";
+			}
+		} else
+			path = "<unknown>";
+
+		if (!strcmp(path, DENY_FILE)) {
+			ret = file_contains_bad(details.fd);
+			switch (ret) {
+			case -1:
+				response.header.type = TALPA_PKT_DENY;
+				response.cache = 0;
+				break;
+			case 0:
+				response.header.type = TALPA_PKT_OK;
+				response.cache = 1;
+				break;
+			case 1:
+				response.header.type = TALPA_PKT_DENY;
+				response.cache = 1;
+				break;
+			default:
+				fprintf(stderr, "file_contains_bad returned something other than -1, 0, 1\n");
+				exit(1);
+			}
+			ret = 0;
+		}
+
+		/* Print out vetting details. */
+		fprintf(stdout,
+			"#%zd: op:%u flags:0%o mode:0%o uid:%d gid:%d tgid:%d pid:%d fd:%d path:%s",
+			cnt, details.operation, details.flags, details.mode,
+			details.uid, details.gid, details.tgid, details.pid, details.fd, path);
+
+		if (response.header.type == TALPA_PKT_DENY)
+			fprintf(stdout, " REJECTED!");
+
+		fprintf(stdout, "\n");
+
+		/* We must reply with our decision */
+		ret = write(talpa, &response, sizeof(response));
+		if (ret != sizeof(response)) {
+			fprintf(stderr, "WERR: %zd (expected %zd) - errno %d\n",
+					ret, sizeof(response), errno);
+			return 3;
+		}
+
+		/* Close the file descriptor if received. */
+		if (details.fd >= 0)
+			close(details.fd);
+	}
+
+	return 0;
+}
diff --git a/Documentation/talpa/client b/Documentation/talpa/client
new file mode 100644
index 0000000..a39aa4f
--- /dev/null
+++ b/Documentation/talpa/client
@@ -0,0 +1,85 @@
+*****************************************************************
+
+The basic explanation of operation and API between the kernel and userspace
+----------------------------------------------
+
+When a process calls open or close it will end up in talpa_vet_file().
+talpa_vet_file() will create a struct talpa_file_vetting (TFV) which contains
+the file in question and information about the process trying to access that
+file.  This TFV will be sent to the evaluation chain of filters.  In the
+initial implementation this means it will first run the cache filter and then,
+if not cached will run the client filter.  The results of a filter can be:
+
+enum talpa_action {
+	TALPA_NEXT = 0,
+	TALPA_ALLOW = 1,
+	TALPA_DENY = 2,
+	TALPA_TIMEOUT = 3,
+	TALPA_ERROR = 4,
+};
+
+The cache filter is very simple.  It compares the sequence number in the inode
+to the global sequence number.  If they are equal the cache will return
+TALPA_ALLOW or TALPA_DENY.  If they are not equal the cache filter will return
+the indifferent TALPA_NEXT.  Assuming the cache returned TALPA_NEXT the TFV
+will be passed to the client filter, talpa_client_examine().
+
+The client filter will create a structure:
+
+struct talpa_packet_kernel {
+	struct talpa_packet_header header;
+	int32_t fd;
+	uint32_t operation;
+	uint32_t flags;
+	uint32_t mode;
+	uint32_t uid;
+	uint32_t gid;
+	uint32_t tgid;
+	uint32_t pid;
+} __attribute__ ((packed));
+
+struct talpa_packet_header {
+	uint32_t version;
+	uint32_t type;
+} __attribute__ ((packed));
+
+Most of the fields in this structure are obvious.  operation is basically open
+or close (but could be expanded to read/write).  Type is mostly useless when
+send from the kernel to userspace.
+
+When a userspace client reads from the special device the kernel will create a
+new fd in its fd table which points to the file being opened or closed.  That
+fd will get written into fd of the above structure.  The kernel will then
+write this structure to the userspace client over the character device.
+
+Userspace may at that time do any magic it wants to its brand new (RO) fd.  At
+some point the userspace vetting client will need to close that fd.  Userspace
+will also need to respond over the character device with a
+
+struct talpa_packet_client {
+	struct talpa_packet_header header;
+	union {
+		int32_t code;
+		int32_t cache;
+	}
+} __attribute__ ((packed));
+
+code is the errno only used if the type is TALPA_ERROR.  cache is non-zero if
+this answer should be cached.  This is useful if userspace feels the need to
+implement their own complex fine grained cache and wishes to bypass the
+in-kernel cache or if they feel the need to allow opens for some programs but
+not others.
+
+Once the kernel gets a response from userspace that response will be used back
+in talpa_vet_file() to decide if we are going to follow the allow or deny
+chains.  The allow/deny chain in the initial implementation will only be used
+by the cache although in future patches they may be used for other purposes.
+
+*****************
+
+/security/talpa/client/*
+
+enabled: RW:	is userspace vetting enabled (0 default)
+queued: RO:	number of access decisions waiting to be sent to a userspace client
+talpa_vetting: RW: Special file which only userspace vetting clients should open
+timeout_ms: RW:	number of ms any access decision should wait for userspace before being timeing out
diff --git a/Documentation/talpa/test_deny.c b/Documentation/talpa/test_deny.c
new file mode 100644
index 0000000..1466026
--- /dev/null
+++ b/Documentation/talpa/test_deny.c
@@ -0,0 +1,356 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/* lens include the NULL */
+#define GOOD_STRING "good"
+#define GOOD_STRING_LEN 5
+#define BAD_STRING "bad"
+#define BAD_STRING_LEN 4
+
+#define TEST_FILE "/root/denyme"
+
+char buf[128];
+
+int open_test_file(int flags)
+{
+	int fd;
+
+	fd = open(TEST_FILE, flags);
+	if ((fd < 0) && (errno != EACCES)) {
+		int old_err = errno;
+
+		snprintf(&buf[0], sizeof(buf), "open with flags %x", flags);
+		errno = old_err;
+		perror(buf);
+		exit(1);
+	}
+	return fd;
+}
+
+int test_good_write(void)
+{
+	int fd;
+	int ret;
+
+	fprintf(stdout, "Good file write: ");
+
+	fd = open_test_file(O_WRONLY | O_CREAT | O_TRUNC);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to write good message\n");
+		return 1;
+	}
+
+	ret = write(fd, GOOD_STRING, GOOD_STRING_LEN);
+	if (ret < 0) {
+		fprintf(stdout, "FAIL\n");
+		perror("Failed writing good string to denyme");
+		close(fd);
+		return 1;
+	}
+
+	if (ret != GOOD_STRING_LEN) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "writing good string to denyme returned a len: %d", ret);
+		close(fd);
+		return 1;
+	}
+
+	close(fd);
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_good_read(void)
+{
+	int fd;
+	int ret;
+
+	fprintf(stdout, "Good file read: ");
+
+	fd = open_test_file(O_RDONLY);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to read good message\n");
+		return 1;
+	}
+
+	ret = read(fd, &buf[0], sizeof(buf));
+	if (ret < 0) {
+		fprintf(stdout, "FAIL\n");
+		perror("Failed to read good string from denyme");
+		close(fd);
+		return 1;
+	}
+
+	if (ret != GOOD_STRING_LEN) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "reading good string to denyme returned a len: %d", ret);
+		close(fd);
+		return 1;
+	}
+
+	close(fd);
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_good(void)
+{
+	int ret = 0;
+
+	ret |= test_good_write();
+	/* this sleep gives the asyncronous close scan enough time to cache */
+	usleep(50);
+	ret |= test_good_read();
+	ret |= test_good_read();
+
+	return ret;
+}
+
+int test_good_write_mmap(void)
+{
+	int fd;
+	int ret;
+	char *file;
+
+	fprintf(stdout, "Good file write mmap: ");
+
+	fd = open_test_file(O_RDWR | O_CREAT | O_TRUNC);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to write with mmap good message\n");
+		return 1;
+	}
+
+	ret = ftruncate(fd, GOOD_STRING_LEN);
+	if (ret) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to truncate file to write with good message\n");
+		return 1;
+	}
+
+	file = mmap(NULL, GOOD_STRING_LEN , PROT_WRITE, MAP_SHARED, fd, 0);
+	if (file == MAP_FAILED) {
+		fprintf(stdout, "FAIL\n");
+		perror("mmaping file for good write");
+		return 1;
+	}
+
+	strcpy(file, GOOD_STRING);
+
+	munmap(file, GOOD_STRING_LEN);
+	close(fd);
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_good_read_mmap(void)
+{
+	int fd;
+	char *file;
+
+	fprintf(stdout, "Good file read mmap: ");
+
+	fd = open_test_file(O_RDONLY);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to read good message for mmaping\n");
+		return 1;
+	}
+
+	file = mmap(NULL, GOOD_STRING_LEN , PROT_READ, MAP_PRIVATE, fd, 0);
+	if (file == MAP_FAILED) {
+		fprintf(stdout, "FAIL\n");
+		perror("mmaping file for good read");
+		return 1;
+	}
+
+	if (strcmp(file, GOOD_STRING)) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "The string we read from the good file wasn't the good string!\n");
+		return 1;
+	}
+
+	munmap(file, GOOD_STRING_LEN);
+
+	close(fd);
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_good_mmap(void)
+{
+	int ret = 0;
+
+	ret |= test_good_write_mmap();
+	/* this sleep gives the asyncronous close scan enough time to cache */
+	usleep(50);
+	ret |= test_good_read_mmap();
+	ret |= test_good_read_mmap();
+
+	return ret;
+}
+
+int test_bad_write(void)
+{
+	int fd;
+	int ret;
+
+	fprintf(stdout, "Bad file write: ");
+
+	fd = open_test_file(O_WRONLY | O_CREAT | O_TRUNC);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to write bad message\n");
+		return 1;
+	}
+
+	ret = write(fd, BAD_STRING, BAD_STRING_LEN);
+	if (ret < 0) {
+		fprintf(stdout, "FAIL\n");
+		perror("Failed writing bad string to denyme");
+		close(fd);
+		return 1;
+	}
+
+	if (ret != BAD_STRING_LEN) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "writing bad string to denyme returned a len: %d", ret);
+		close(fd);
+		return 1;
+	}
+
+	ret = close(fd);
+	if (ret) {
+		fprintf(stdout, "FAIL\n");
+		perror("failed to close fd to file with bad string");
+		return 1;
+	}
+
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_bad_read(void)
+{
+	int fd;
+
+	fprintf(stdout, "Bad file read: ");
+
+	fd = open_test_file(O_RDONLY);
+	if (fd >= 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "we were able to open the file containing the bad string\n");
+		close(fd);
+		return 1;
+	}
+
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_bad(void)
+{
+	int ret = 0;
+
+	ret |= test_bad_write();
+	/* this sleep gives the asyncronous close scan enough time to cache */
+	usleep(50);
+	ret |= test_bad_read();
+	ret |= test_bad_read();
+
+	return ret;
+}
+int test_bad_write_mmap(void)
+{
+	int fd;
+	int ret;
+	char *file;
+
+	fprintf(stdout, "Bad file write mmap: ");
+
+	fd = open_test_file(O_RDWR | O_CREAT | O_TRUNC);
+	if (fd < 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to open file to write bad message for mmap\n");
+		return 1;
+	}
+
+	ret = ftruncate(fd, BAD_STRING_LEN);
+	if (ret) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "Failed to truncate file to write with bad message\n");
+		return 1;
+	}
+
+	file = mmap(NULL, BAD_STRING_LEN , PROT_WRITE, MAP_SHARED, fd, 0);
+	if (file == MAP_FAILED) {
+		fprintf(stdout, "FAIL\n");
+		perror("mmaping file for bad write");
+		return 1;
+	}
+
+	strcpy(file, BAD_STRING);
+
+	munmap(file, BAD_STRING_LEN);
+
+	ret = close(fd);
+	if (ret) {
+		fprintf(stdout, "FAIL\n");
+		perror("failed to close fd to file with bad string");
+		return 1;
+	}
+
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_bad_read_mmap(void)
+{
+	int fd;
+
+	fprintf(stdout, "Bad file read mmap: ");
+
+	fd = open_test_file(O_RDONLY);
+	if (fd >= 0) {
+		fprintf(stdout, "FAIL\n");
+		fprintf(stderr, "we were able to open the file containing the bad string\n");
+		close(fd);
+		return 1;
+	}
+
+	fprintf(stdout, "PASS\n");
+	return 0;
+}
+
+int test_bad_mmap(void)
+{
+	int ret = 0;
+
+	ret |= test_bad_write_mmap();
+	/* this sleep gives the asyncronous close scan enough time to cache */
+	usleep(50);
+	ret |= test_bad_read_mmap();
+	ret |= test_bad_read_mmap();
+
+	return ret;
+}
+int main(void)
+{
+	int ret = 0;
+
+	ret |= test_good();
+	ret |= test_bad();
+	ret |= test_good_mmap();
+	ret |= test_bad_mmap();
+
+	return ret;
+}
diff --git a/include/linux/talpa.h b/include/linux/talpa.h
index 52750b7..342fa02 100644
--- a/include/linux/talpa.h
+++ b/include/linux/talpa.h
@@ -39,6 +39,89 @@ enum talpa_operation {
 	TALPA_CLOSE = 1,
 };
 
+/* Communication protocol for userspace clients. */
+
+/**
+ * enum talpa_packet_type - packet type
+ *
+ * Packet header type field must be set to one of these.
+ */
+enum talpa_packet_type {
+	TALPA_PKT_OK = 0,
+	TALPA_PKT_DENY = 1,
+	TALPA_PKT_TIMEOUT = 2,
+	TALPA_PKT_ERROR = 3,
+	TALPA_PKT_DETAILS = 4,
+};
+
+/**
+ * struct talpa_packet_header - leads each packet
+ *
+ * Type field will be or must be set to one of the known packet
+ * types from the above enum.
+ */
+struct talpa_packet_header {
+	uint32_t version;
+	uint32_t type;
+} __attribute__ ((packed));
+
+/* Externally visible and usable packets */
+
+/**
+ * struct talpa_packet_error - client reply on vetting error
+ * @header:standard packet header
+ * @code:error code to return
+ *
+ * When vetting has been unsuccessful client should reply with this
+ * packet indicating that access should be denied with a specific
+ * error code.
+ */
+struct talpa_packet_client {
+	struct talpa_packet_header header;
+	union {
+		int32_t code;
+		uint32_t cache;
+	};
+} __attribute__ ((packed));
+
+/**
+ * struct talpa_packet_details - main interception packet
+ * @header:standard packet header
+ * @fd:file descriptor of the intercepted object
+ * @operation: operation enum
+ * @flags: as passed to open(2)
+ * @mode: inode mode
+ * @uid: uid of the process doing the operation
+ * @gid: gid of the process doing the operation
+ * @tgid: tgid of the process doing the operation
+ * @pid: pid of the process doing the operation
+ *
+ * On each interception registered userspace client will receive this
+ * packet describing the operation to be vetted.
+ * File content can be inspected through a file descriptor (unless -1)
+ * which must be closed(2) on completion.
+ * One of the reply packets must be sent to Talpa as a decision, unless
+ * operation was TALPA_CLOSE when no reply is expected.
+ */
+struct talpa_packet_kernel {
+	struct talpa_packet_header header;
+	int32_t fd;
+	uint32_t operation;
+	uint32_t flags;
+	uint32_t mode;
+	uint32_t uid;
+	uint32_t gid;
+	uint32_t tgid;
+	uint32_t pid;
+} __attribute__ ((packed));
+
+/* Useful sizes for allocating packet buffers */
+#define TALPA_PACKET_READ_SIZE (sizeof(struct talpa_packet_kernel))
+#define TALPA_PACKET_WRITE_SIZE (sizeof(struct talpa_packet_client))
+#define TALPA_MAX_PACKET_SIZE TALPA_PACKET_READ_SIZE
+
+#define TALPA_MAX_PACKET_VERSION 1
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/security/talpa/Kconfig b/security/talpa/Kconfig
index c5528e5..f460ec8 100644
--- a/security/talpa/Kconfig
+++ b/security/talpa/Kconfig
@@ -38,3 +38,14 @@ config TALPA_THREAD_EXCLUSION
 	  on a corresponding device node.
 
 	  If you are unsure how to answer this question, answer Y.
+
+
+config TALPA_CLIENT
+	bool "Userspace vetting support"
+	depends on TALPA
+	default y
+	help
+	  This enables userspace clients to register with Talpa and
+	  receive vetting requests for filesystem operations.
+
+	  If you are unsure how to answer this question, answer Y.
diff --git a/security/talpa/Makefile b/security/talpa/Makefile
index 16cb1d0..76a6410 100644
--- a/security/talpa/Makefile
+++ b/security/talpa/Makefile
@@ -10,4 +10,4 @@ talpa-y :=	talpa_interceptor.o \
 
 talpa-$(CONFIG_TALPA_CACHE) += talpa_cache.o
 talpa-$(CONFIG_TALPA_THREAD_EXCLUSION) += talpa_thread_exclude.o
-
+talpa-$(CONFIG_TALPA_CLIENT) += talpa_client.o
diff --git a/security/talpa/talpa_client.c b/security/talpa/talpa_client.c
new file mode 100644
index 0000000..1b18285
--- /dev/null
+++ b/security/talpa/talpa_client.c
@@ -0,0 +1,543 @@
+/*
+ *  Copyright 2008 Sophos Plc
+ *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ *  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, 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; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/talpa.h>
+#include <linux/stat.h>
+#include <linux/poll.h>
+#include <linux/major.h>
+#include <linux/miscdevice.h>
+#include <linux/mount.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/jiffies.h>
+#include <linux/file.h>
+#include <linux/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/security.h>
+#include <asm/atomic.h>
+
+#include "talpa.h"
+#include "talpa_cache.h"
+
+
+/* Each interception is stored in a form of this structure
+   and put in a queue until consumed by the client or expired. */
+struct vetting_details {
+	atomic_t refcnt;		/* Reference count for this structure. */
+	bool complete;			/* Whether this interception was completed by the client. */
+	enum talpa_action action;	/* What was the response. */
+	union {
+		int code;		/* Error code if applicable. */
+		unsigned int cache;	/* Should this result be cached */
+	};
+	wait_queue_head_t wait;		/* Wait queue on which the intercepted process sleeps. */
+	struct file *file;		/* filp from the open/close call */
+	long cache_seqno;		/* seqno of the cache at the time examine started */
+	struct talpa_packet_kernel k_packet;
+
+	struct list_head head;		/* List of vet_dets to be sent to userspace */
+};
+
+/* Each registered vetting client gets one of these. */
+struct vetting_client {
+	struct vetting_details *details;	/* Details currently being processed. */
+
+	size_t response;			/* Number of bytes left to read. */
+	loff_t offset;				/* Offset in packet buffer. */
+	struct talpa_packet_client c_packet;	/* Packet buffer for replies. */
+
+	struct list_head head;			/* List of all client processes */
+};
+
+/* Global data. */
+
+static unsigned long talpa_client_enabled;
+static unsigned long talpa_client_timeout = 5000; /* Time in miliseconds to wait for vetting to complete. */
+
+static atomic_t num_clients = ATOMIC_INIT(0); /* number of registered clients */
+
+static struct kmem_cache *det_cache; /* Cache for struct vetting_details */
+
+static unsigned long intercept_queue_size; /* Number of interceptions in the queue. */
+static LIST_HEAD(intercept_queue); /* List head for vetting details. */
+static DEFINE_MUTEX(intercept_queue_lock); /* Mutext protecting the queue. */
+
+static DECLARE_WAIT_QUEUE_HEAD(intercept_queue_wait); /* Wait queue for clients awaiting interceptions. */
+
+
+/* Release reference counted vetting details. */
+static inline void put_details(struct vetting_details *details)
+{
+	if (atomic_dec_and_test(&details->refcnt)) {
+		fput(details->file);
+		kmem_cache_free(det_cache, details);
+	}
+}
+
+/* Test if these should be handled async (CLOSE, someday WRITE?) */
+static inline int handle_async(struct vetting_details *details)
+{
+	if (details->k_packet.operation == TALPA_CLOSE)
+		return 1;
+	return 0;
+}
+
+/* Main examine function. */
+enum talpa_action talpa_client_examine(struct talpa_file_vetting *tfv)
+{
+	struct vetting_details *details = NULL;
+	int ret;
+	enum talpa_action action = TALPA_NEXT;
+	struct task_struct *tsk = current;
+
+	if (!talpa_client_enabled || !atomic_read(&num_clients))
+		return action;
+
+	details = kmem_cache_zalloc(det_cache, GFP_KERNEL);
+	if (!details)
+		return action;
+
+	/* Fill-in vetting details and vetting packet. */
+	atomic_set(&details->refcnt, 2);
+	details->action = TALPA_NEXT;
+	get_file(tfv->file);
+	details->file = tfv->file;
+	init_waitqueue_head(&details->wait);
+
+	details->k_packet.header.type = TALPA_PKT_DETAILS;
+	details->k_packet.header.version = TALPA_MAX_PACKET_VERSION;
+	details->k_packet.fd = -1;
+	details->k_packet.operation = tfv->operation;
+	details->k_packet.flags = tfv->flags;
+	details->k_packet.mode = tfv->file->f_dentry->d_inode->i_mode;
+	details->k_packet.uid = tsk->uid;
+	details->k_packet.gid = tsk->gid;
+	details->k_packet.tgid = tsk->tgid;
+	details->k_packet.pid = tsk->pid;
+
+	/* Put it on a queue and wake-up clients. */
+	mutex_lock(&intercept_queue_lock);
+	list_add_tail(&details->head, &intercept_queue);
+	intercept_queue_size++;
+	mutex_unlock(&intercept_queue_lock);
+	wake_up(&intercept_queue_wait);
+
+	/* Interceptions of close can immediately proceed because
+	   response is not required.  callbacks into the cache based
+	   on the results will be done in the context of the userspace
+	   client thread */
+	if (handle_async(details)) {
+		details->cache_seqno = tfv->cache_seqno;
+		put_details(details);
+		return action;
+	}
+
+	/* Wait until vetting completes in any way or all the clients disappear*/
+	do {
+		ret = wait_event_interruptible_timeout(details->wait,
+							details->complete,
+							msecs_to_jiffies(talpa_client_timeout));
+		/* Timeout or signal? */
+		if (ret == 0 || ret < 0) {
+			struct list_head *pos;
+			if (ret == 0) {
+				action = TALPA_TIMEOUT;
+				tfv->code = -ETIME;
+			} else {
+				/* do we really want to ERROR if we took a signal? */
+				action = TALPA_ERROR;
+				tfv->code = ret;
+			}
+			/* Unqueue details if still queued. */
+			mutex_lock(&intercept_queue_lock);
+			list_for_each(pos, &intercept_queue) {
+				if (pos == &details->head) {
+					list_del(&details->head);
+					intercept_queue_size--;
+					break;
+				}
+			}
+			mutex_unlock(&intercept_queue_lock);
+			break;
+		}
+	} while (!details->complete && atomic_read(&num_clients));
+
+	if (details->complete) {
+		action = details->action;
+		switch (details->action) {
+		case TALPA_ALLOW:
+		case TALPA_DENY:
+			tfv->authoritative = details->cache;
+			break;
+		case TALPA_ERROR:
+			tfv->code = details->code;
+			break;
+		case TALPA_TIMEOUT:
+			tfv->code = -ETIME;
+		default:
+			break;
+		}
+	}
+
+	put_details(details);
+	return action;
+}
+
+/* Clients register by opening our device. */
+static int client_open(struct inode *inode, struct file *file)
+{
+	struct vetting_client *client;
+
+	client = kmalloc(sizeof(struct vetting_client), GFP_KERNEL);
+	if (!client)
+		return -ENOMEM;
+
+	client->details = NULL;
+	client->response = 0;
+	client->offset = 0;
+	INIT_LIST_HEAD(&client->head);
+
+	current->flags |= PF_TALPA_EXCL;
+
+	/* if we ever overflow this just shoot me */
+	atomic_inc(&num_clients);
+
+	file->private_data = client;
+	return 0;
+}
+
+/* And unregister on close. */
+static int client_close(struct inode *inode, struct file *file)
+{
+	struct vetting_client *client;
+
+	client = (struct vetting_client *)file->private_data;
+	if (!client)
+		return -EBADF;
+
+	atomic_dec(&num_clients);
+
+	/* Cleanup if client has abruptly exited. */
+	if (client->details) {
+		client->details->action = TALPA_ERROR;
+		client->details->code = -EUNATCH;
+		client->details->complete = true;
+		wake_up(&client->details->wait);
+		put_details(client->details);
+	}
+
+	kfree(client);
+
+	return 0;
+}
+
+/* Helper which keeps the lock acquired if queue is not empty. */
+static inline unsigned int check_intercept_queue(void)
+{
+	mutex_lock(&intercept_queue_lock);
+	if (!list_empty(&intercept_queue))
+		return 1;
+	mutex_unlock(&intercept_queue_lock);
+	return 0;
+}
+
+/* Helper to just fill the userspace buf with the k_packet */
+static ssize_t client_read_fill_buf(struct vetting_client *client, size_t len, char *buf)
+{
+	if (len > client->response)
+		len = client->response;
+
+	if (copy_to_user(buf, (char *)&client->details->k_packet + client->offset, len))
+		return -EFAULT;
+
+	client->response -= len;
+	client->offset += len;
+
+	return len;
+}
+
+/* must hold the queue_lock and must know there is something in the list */
+static void client_fill_details(struct vetting_client *client)
+{
+	client->details = list_entry(intercept_queue.next, struct vetting_details, head);
+	list_del(&client->details->head);
+	intercept_queue_size--;
+	mutex_unlock(&intercept_queue_lock);
+}
+
+static ssize_t client_read(struct file *file, char *buf, size_t len, loff_t *ppos)
+{
+	struct vetting_client *client;
+	struct vetting_details *details;
+
+	int client_fd;
+	struct dentry *client_file_dentry;
+	struct vfsmount *client_file_vfsmnt;
+	struct file *client_file;
+
+	ssize_t ret = 0;
+
+	client = (struct vetting_client *)file->private_data;
+	if (unlikely(!client))
+		return -EBADF;
+
+	if (unlikely(!len))
+		return 0;
+
+	/* Misbehaving client - tries to read when it should respond. */
+	if (unlikely(client->details && !client->response))
+		return -EBUSY;
+
+	/* there are bytes left to read, last one was a short read */
+	if (unlikely(client->response))
+		return client_read_fill_buf(client, len, buf);
+
+	/* Clean state - try to obtain vetting details for this client. */
+	mutex_lock(&intercept_queue_lock);
+	if (!list_empty(&intercept_queue)) {
+		/* note: client_fill_details releases the lock */
+		client_fill_details(client);
+	} else {
+		mutex_unlock(&intercept_queue_lock);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		/* Wait for something to get into the queue. */
+		ret = wait_event_interruptible(intercept_queue_wait, check_intercept_queue());
+		if (!ret)
+			client_fill_details(client);
+		/* FIXME, should we just rewait if we took a signal and handled it? */
+	}
+
+	details = client->details;
+
+	/* we won't have details if we took a signal */
+	if (!details)
+		return ret;
+
+	WARN_ON(!details->file);
+
+	client_fd = get_unused_fd();
+	if (client_fd < 0)
+		return client_fd;
+
+	client_file_dentry = dget(details->file->f_dentry);
+	client_file_vfsmnt = mntget(details->file->f_vfsmnt);
+
+	/*
+	 * we need a new file handle for the userspace program so it can read even if it was
+	 * originally opened O_WRONLY.
+	 */
+	client_file = dentry_open(client_file_dentry, client_file_vfsmnt, O_RDONLY);
+	if (IS_ERR(client_file)) {
+		printk(KERN_CRIT "dentry_open failed with reason %ld\n", PTR_ERR(client_file));
+		put_unused_fd(client_fd);
+		client_fd = -1;
+	} else {
+		fd_install(client_fd, client_file);
+	}
+
+	details->k_packet.fd = client_fd;
+	client->response = sizeof(struct talpa_packet_kernel);
+	client->offset = 0;
+	ret = client_read_fill_buf(client, len, buf);
+
+	return ret;
+}
+
+static int process_async_response(struct vetting_details *details)
+{
+	struct talpa_file_vetting tfv;
+
+	tfv.file = details->file;
+	tfv.operation = details->k_packet.operation;
+	tfv.flags = details->k_packet.flags;
+	tfv.cache_seqno = details->cache_seqno;
+
+	switch (details->action) {
+	case TALPA_ALLOW:
+		tfv.authoritative = 1;
+		talpa_cache_allow(&tfv);
+		break;
+	case TALPA_DENY:
+		tfv.authoritative = 1;
+		talpa_cache_deny(&tfv);
+		break;
+	default:
+		talpa_invalidate_inode(details->file->f_dentry->d_inode);
+		break;
+	}
+
+	return 0;
+}
+
+static int process_packet(struct vetting_client *client)
+{
+	if (unlikely(!client->details))
+		return -EPROTO;
+
+	client->details->cache = 0;
+
+	switch (client->c_packet.header.type) {
+	case TALPA_PKT_OK:
+		client->details->action = TALPA_ALLOW;
+		client->details->cache = client->c_packet.cache;
+		break;
+	case TALPA_PKT_DENY:
+		client->details->action = TALPA_DENY;
+		break;
+	case TALPA_PKT_TIMEOUT:
+		client->details->action = TALPA_TIMEOUT;
+		break;
+	case TALPA_PKT_ERROR:
+		client->details->action = TALPA_ERROR;
+		client->details->code = client->c_packet.code;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	client->details->complete = true;
+	wake_up(&client->details->wait);
+
+	if (handle_async(client->details))
+		process_async_response(client->details);
+
+	put_details(client->details);
+	client->details = NULL;
+	return 0;
+}
+
+static ssize_t client_write(struct file *file, const char *buf, size_t len, loff_t *ppos)
+{
+	struct vetting_client *client;
+	int ret;
+
+	client = (struct vetting_client *)file->private_data;
+	if (unlikely(!client))
+		return -EBADF;
+
+	if (unlikely(client->response))
+		return -EPROTO;
+
+	/* reject short and long writes, its only 96 bits for crying out loud */
+	if (unlikely(len != TALPA_PACKET_WRITE_SIZE))
+		return -EPROTO;
+
+	if (unlikely(copy_from_user(&client->c_packet, buf, len)))
+		return -EFAULT;
+
+	ret = process_packet(client);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static unsigned int client_poll(struct file *file, struct poll_table_struct *polltbl)
+{
+	struct vetting_client *client;
+	int ret = 0;
+
+	client = (struct vetting_client *)file->private_data;
+	if (!client)
+		return -EBADF;
+	poll_wait(file, &intercept_queue_wait, polltbl);
+	mutex_lock(&intercept_queue_lock);
+	if (!list_empty(&intercept_queue))
+		ret = POLLIN | POLLRDNORM;
+	mutex_lock(&intercept_queue_lock);
+
+	return ret;
+}
+
+/* Don't enable the client filter if we failed to create the kmem_cache */
+ssize_t set_enabled(struct talpa_configuration *cfg, char *buf, size_t len)
+{
+	if (det_cache)
+		return talpa_generic_set_ulong(cfg, buf, len);
+	return -ENOMEM;
+}
+
+static struct talpa_configuration talpa_client_cfg[] = {
+	{
+		.name = "enabled",
+		.mode = S_IRUSR|S_IWUSR|S_IRGRP,
+		.data = &talpa_client_enabled,
+		.get = talpa_generic_get_ulong,
+		.set = set_enabled,
+	},
+	{
+		.name = "timeout_ms",
+		.mode = S_IRUSR|S_IWUSR|S_IRGRP,
+		.data = &talpa_client_timeout,
+		.get = talpa_generic_get_ulong,
+		.set = talpa_generic_set_ulong,
+	},
+	{
+		.name = "queued",
+		.mode = S_IRUSR|S_IRGRP,
+		.data = &intercept_queue_size,
+		.get = talpa_generic_get_ulong,
+	},
+	{
+	},
+};
+
+static struct file_operations client_fops = {
+	.open =		client_open,
+	.release =	client_close,
+	.read =		client_read,
+	.write =	client_write,
+	.poll =		client_poll
+};
+
+static __init int talpa_client_init(void)
+{
+	struct dentry *dentry;
+
+	mutex_init(&intercept_queue_lock);
+
+	dentry = talpa_register_configuration("client", talpa_client_cfg);
+	if (IS_ERR(dentry)) {
+		pr_err("talpa: Failed to register userspace client filter!\n");
+		return PTR_ERR(dentry);
+	}
+
+	/* Register the 'special' file */
+	dentry = securityfs_create_file("talpa-vetting", S_IRUSR|S_IWUSR, dentry, NULL, &client_fops);
+	if (IS_ERR(dentry)) {
+		pr_err("talpa: Failed to register userspace client filter special file!\n");
+		return PTR_ERR(dentry);
+	}
+
+	det_cache = kmem_cache_create("talpa_vetting_details", sizeof(struct vetting_details), 0, 0, NULL);
+	if (!det_cache) {
+		pr_err("talpa: Failed to create vetting details cache!\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+__initcall(talpa_client_init);
diff --git a/security/talpa/talpa_evaluation_calls.h b/security/talpa/talpa_evaluation_calls.h
index 24d0955..1aaa08c 100644
--- a/security/talpa/talpa_evaluation_calls.h
+++ b/security/talpa/talpa_evaluation_calls.h
@@ -3,6 +3,8 @@
 #include "talpa.h"
 #include "talpa_cache.h"
 
+extern enum talpa_action talpa_client_examine(struct talpa_file_vetting *tfv);
+
 static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv)
 {
 	enum talpa_action ret = TALPA_NEXT;
@@ -26,5 +28,11 @@ static inline int talpa_evaluation_calls(struct talpa_file_vetting *tfv)
 		return ret;
 #endif /* CONFIG_TALPA_CACHE */
 
+#ifdef CONFIG_TALPA_CLIENT
+	ret = talpa_client_examine(tfv);
+	if (ret != TALPA_NEXT)
+		return ret;
+#endif /* CONFIG_TALPA_CLIENT */
+
 	return ret;
 }
-- 
1.5.2.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC 1/5] [TALPA] Hooking points and kernel interception
  2008-08-04 21:00 [RFC 1/5] [TALPA] Hooking points and kernel interception Eric Paris
  2008-08-04 21:00 ` [RFC 2/5] [TALPA] securityfs configuration interfaces Eric Paris
@ 2008-08-05  0:28 ` Christoph Hellwig
  1 sibling, 0 replies; 6+ messages in thread
From: Christoph Hellwig @ 2008-08-05  0:28 UTC (permalink / raw)
  To: Eric Paris; +Cc: linux-kernel

And while we're at it please stop cross-posting to restricted
mailinglist.


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2008-08-05  0:29 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-08-04 21:00 [RFC 1/5] [TALPA] Hooking points and kernel interception Eric Paris
2008-08-04 21:00 ` [RFC 2/5] [TALPA] securityfs configuration interfaces Eric Paris
2008-08-04 21:00   ` [RFC 3/5] [TALPA] Access result caching Eric Paris
2008-08-04 21:00     ` [RFC 4/5] [TALPA] Exclude threads from decision making Eric Paris
2008-08-04 21:00       ` [RFC 5/5] [TALPA] Userspace vetting of open and close access Eric Paris
2008-08-05  0:28 ` [RFC 1/5] [TALPA] Hooking points and kernel interception Christoph Hellwig

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox