linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released
@ 2008-04-04 12:22 Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 01/30] TOMOYO Linux documentation Tetsuo Handa
                   ` (30 more replies)
  0 siblings, 31 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module

What is TOMOYO Linux for?

  It is userland applications' charge to keep processes under control.
  This includes that userland applications restrict what programs are
  permitted to execute and what files are permitted to read/write
  by the application.
  Thus, the kernel does whatever asked by the userland applications.

  But userland applications often make mistakes such as buffer overflow,
  OS command injection, directory traversal etc.
  As a result, they can't always keep processes under control.

  TOMOYO Linux is developed to take partial charge of keeping processes
  under control, by teaching the kernel what programs are
  permitted to execute and what files are permitted to read/write
  by the application and letting the kernel check it once again.

Can TOMOYO Linux coexist with SELinux, SMACK, AppArmor etc. ?

  Yes. TOMOYO Linux 1.x is not using LSM because I want don't want to disable
  different access control implementations.

  TOMOYO Linux is good at dealing with the subject's request chains,
  while LSM is good at dealing with object's life-cycle.

What are new features of this release?

  This release reinforced execution parameter checks.

  Introduced execute handler mechanism.

    The usage of TOMOYO Linux is that understand what programs are executed and
    what files are opened by individual applications and let the kernel
    enforce them. Thus, assuming that all programs needed by individual
    applications are known by the time of enforcing, attempts to execute
    not ever observed application (e.g. /bin/sh) are considered to be attacks.

    Some shellcode (an exploit code used to execute /bin/sh) attempts to
    execute /bin/sh until the execution succeeds. One of such shellcodes is
    Samba's trans2open exploit. It attempts to execute /bin/sh from infinite
    loop. As a result, just rejecting execute request of /bin/sh triggers
    CPU power consumption problem (all CPU powers are eaten by the shellcode's
    infinite loop).

    But by using execute handler, you can run different programs instead of
    just rejecting execute request to redirect the process to somewhere else.

    This redirection mechanism is useful for deploying on demand honey pot.
    You can provide regular service in peacetime, and you can redirect
    the attacker trying to start /bin/sh to honey pot in wartime.

    Also, since the fact that a program which is different from the requested
    one is executed by execute handler is not notified to the caller process,
    you can use execute handler as a transparent validation interface
    like "Web Application Firewall" or "AntiVirus". You can monitor
    parameters passed to execve() and stdio to drop unwanted parameters or
    reject the execution of the program requested by the caller process.

  Introduced "struct linux_bprm" checks.

    Proper codes tends to setting up argv[] and envp[] when executing
    a new program. But shellcodes tend to not setting up argv[] and envp[]
    when executed using buffer overflows.
    Thus, it became possible to require specific argv[] and envp[] values
    when executing a program.

  Introduced environment variable names checks.

    Some environment variables (e.g. LD_PRELOAD) are used by attackers to make
    the application behave differently.
    Thus, it became possible to restrict acceptable environment variable's
    names passed to execve().

  And various usability enhancement like more detailed access logs and
  policy management by non root user.

Is TOMOYO Linux architecture independent?

  Yes. I think TOMOYO Linux doesn't contain architecture dependent code.
  TOMOYO Linux touches only surface of the kernel code.

Are older kernels supported?

  Yes. From 2.4.30 to 2.4.36 and from 2.6.11 to 2.6.25-rc7 are supported.
  Also, various distributions' latest kernels are supported.

Where's the web page?

  http://elinux.org/TomoyoLinux
  http://tomoyo.sourceforge.jp/wiki-e/?WhatIs
  http://sourceforge.jp/projects/tomoyo/document/fosdem2008.pdf

  TOMOYO Linux is compact and suits well to PC servers and embedded systems.
  There is a TOMOYO Linux presentation at CELF Embedded Linux Conference 2008
  held in California from April 15th to the 17th.

Thank you.


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

* [TOMOYO #7 01/30] TOMOYO Linux documentation.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO Tetsuo Handa
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-documentation.patch --]
[-- Type: text/plain, Size: 13940 bytes --]

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 Documentation/TOMOYO.txt |  292 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 292 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/Documentation/TOMOYO.txt
@@ -0,0 +1,292 @@
+Subject: TOMOYO Linux Security Goal
+
+This document is intended to specify the security goal that TOMOYO Linux is
+trying to achieve, so that users can evaluate whether TOMOYO Linux will meet
+their needs, and kernel developers can evaluate whether TOMOYO Linux deserved
+to be in-tree.
+
+1. About TOMOYO Linux
+
+Project Homepage: http://tomoyo.sourceforge.jp/index.html.en
+Project Wiki: http://elinux.org/TomoyoLinux
+
+TOMOYO Linux is a DIY tool for understanding and protecting your system.
+TOMOYO Linux policy definitions are absolutely readable to Linux users, and
+TOMOYO Linux supports unique policy learning mechanism which automatically
+gathers information and arranges the results as policy definitions.
+These things made it possible for users to write policy from scratch.
+Troubleshooting can be done by users.
+
+We put some TOMOYO Linux policy examples on our web site.
+http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/etch/domain_policy.conf?v=policy-sample
+
+Here's our version of Linux security comparison table.
+http://tomoyo.sourceforge.jp/wiki-e/?WhatIs#comparison
+
+2. TOMOYO Linux Security Goal
+
+The TOMOYO Linux's security goal is to provide "MAC that covers practical
+requirements for most users and keeps usable for most administrators".
+TOMOYO Linux is not a tool for security professional but for average users
+and administrators. We keep TOMOYO Linux understandable and customizable
+so that end users (i.e. administrators) can configure policy for their systems.
+
+TOMOYO Linux can authorize part of kernel resources which SELinux can
+and part of other resources which SELinux can't.
+
+Currently, TOMOYO Linux can authorize
+
+  * execve() of programs.
+  * open() of files for reading/writing.
+  * creat()/link()/rename()/unlink()/symlink()/mkfifo()/mksock()/mkblock()/
+    mkchar()/truncate()/mkdir()/rmdir() of files and directries
+    that are visible to userland process's namespace.
+  * namespace manipulation. (i.e. mount()/umount()/pivot_root())
+  * TCP/IP networking operations based on IPv4/v6 addresses and port numbers.
+  * booleans for some operations. (Part of POSIX capability and TOMOYO Linux's
+    original capability.)
+  * signal transmissions.
+  * argv[0] passed to execve().
+  * environment variables' names passed to execve().
+
+TOMOYO Linux is purely restrictive. No requests which are denied by existent
+access control mechanisms (e.g. DAC) will be reversed by TOMOYO Linux.
+
+TOMOYO Linux is not intended to provide information flow control.
+Analyzing and restricting how information propagates are not the region of
+interest for TOMOYO Linux.
+
+3. Our Approach
+
+To meet the above goal, TOMOYO Linux attempts to make the system where
+everything is prearranged in an easy-to-understand way.
+
+  * Make the all subject's all access requests that will occur at least once
+    during the lifetime of the kernel known in advance.
+
+  * Let the administrator understand what access requests will each subject
+    raise in his/her system and write policy which only allows expected and
+    desirable access requests for his/her needs.
+
+Unlike AppArmor, TOMOYO Linux is intended to protect the whole system from
+attackers exploiting vulnerabilities in applications that the system hosts.
+The threat is that an attacker can cause a vulnerable application to do
+something unexpected and undesirable. TOMOYO Linux addresses this threat by
+recording all applications' behaviors in the test environment and forcing
+all applications behave within recorded behaviors in the production environment.
+
+TOMOYO Linux has a unique concept of "process invocation history"
+(in short, PIH). The PIH is a developmental lineage of a process.
+When a process executes a program, the process creates a copy with fork() and
+replace the copy with execve().
+TOMOYO Linux appends the pathname of the executed program to the PIH of
+the replaced process, and associates process's PIH (stored in task_struct)
+with a domain.
+As a result, the context switching (a.k.a. domain transition) is unidirectional.
+This rule allows administrator distinguish and manage fine-grained context.
+Domain transition forms tree structure like a directory tree of filesystems.
+Each domain has different set of permissions that are essential for that domain.
+
+TOMOYO Linux's region of interest is how to minimize means granted to each PIH,
+although not all permissions TOMOYO Linux can authorize.
+
+The "learning mode" is primary source of policy. TOMOYO Linux depends on change
+of behavior to detect intruders. All unprecedented behaviors should be detected
+and rejected. TOMOYO Linux's power comes from
+"know all and understand what requests can happen within your system".
+
+4. Things you can do with TOMOYO Linux.
+
+Create policy from scratch.
+
+  You want to use ready-made policy files supplied by somebody else
+  because testing all paths needed for your usage sounds boring?
+
+  OK, then you can choose other implementations that provide
+  ready-made policy files, but you should check whether these files
+  contain enough permissions for your usage or not. It is inevitable thing
+  to test all paths needed for your usage if you want to use white listing
+  access control.
+
+  Also, ready-made policy files tend to contain redundant permissions
+  for your usage which often leads to serious problem.
+
+  TOMOYO Linux is a DIY tool for understanding and protecting your Linux box.
+  TOMOYO Linux's "learning mode" will automatically generate
+  policy files with necessary and sufficient permissions for you.
+
+Understand all possible requests.
+
+  TOMOYO Linux reports what is happening within your Linux box.
+  You can have the security of knowing that no unexpected requests arise,
+  if you have tested all paths needed for your usage.
+
+  Please remember, we are not saying that
+  "You can have the security of knowing that no unexpected results happen".
+  Although TOMOYO Linux attempts to filter the request as hard as possible,
+  TOMOYO Linux can't guarantee the result.
+
+Analyze system's behavior.
+
+  TOMOYO Linux resembles to /usr/bin/strace .
+  TOMOYO Linux reports what programs are executed from each program and
+  what files/networks are accessed by each program.
+
+  You can use TOMOYO Linux for analyzing application's behavior
+  if you want to know "which configuration file does this daemon read?",
+  "what port numbers does this daemon require?" and so on.
+
+  This helps debugging program's behaviors and writing manuals.
+  TOMOYO Linux is also applicable for educational use.
+
+Provide per application firewall.
+
+  It is userland applications' businesses to perform pathname based access
+  control, but they sometimes make mistakes which are known as OS command
+  injection vulnerability or buffer overflow vulnerability.
+  TOMOYO Linux assists this access control in kernel space to reduce
+  the damage by restricting pathnames which each application can request.
+
+  TOMOYO Linux can perform TCP_Wrapper-like simple stateless
+  TCP/IP packet filtering based on IPv4/v6 address and ports.
+
+  TOMOYO Linux can restrict the list of environment variable's name
+  passed to execve() so that some dangerous environment variable
+  (e.g. LD_PRELOAD) won't be passed freely.
+
+  TOMOYO Linux also supports conditional permissions.
+  You can use uid/gid etc. of a process to restrict the combination of
+  user and accessible resources.
+
+Provide support for multi-call binary applications without patches.
+
+  A multi-call binary (e.g. /sbin/busybox) changes behaviors according to
+  the invocation name (in other words, argv[0] passed to execve()).
+  Users specify the invocation name using symbolic links or hard links.
+
+  TOMOYO Linux allows administrators to give execute permission and define PIH
+  using the pathname of a symbolic link, if the combination of
+  the pathname of a symbolic link and the pathname of the entity pointed
+  by the symbolic link is registered in the policy file.
+  You can use symbolic links as if they are hard links.
+
+  TOMOYO Linux supports restricting the combination of
+  path-to-executable-or-symbolic-link and basename-of-argv[0]
+  so that users can't pass different argv[0]
+  from path-to-executable-or-symbolic-link freely.
+
+  Please be aware that some multi-call binary programs change their behaviors
+  according to the command line parameters (i.e. argv[1] to argv[argc - 1]).
+  Regarding such programs, TOMOYO Linux can't restrict behaviors by restricting
+  the argv[0]. But TOMOYO Linux doesn't attempt to restrict all argv[] elements
+  passed to execve(), because doing so will make the system too inconvenient
+  and frustrating to use.
+
+Give different set of permissions to the same application.
+
+  There are some non-executable applications (e.g. Java's class files).
+  Such applications use the same program for executing (e.g. Java Runtime
+  Environment). Since TOMOYO Linux has PIH, you can give different set of
+  permissions for each application by separating PIH for each application.
+
+  Although TOMOYO Linux can switch context for every execve() requests,
+  it is preferable to be able to switch context without invoking execve().
+  So far, we are not providing APIs to switch context like AppArmor's
+  change_hat()/change_profile(). We'd like to introduce APIs to switch context
+  when distributors get ready to support TOMOYO Linux (in other words, after
+  TOMOYO Linux is merged into mainline).
+
+Provide DMZ for remote logins.
+
+  Recently, password brute-force attacks against SSH service are increasing.
+  But TOMOYO Linux's PIH can provide a room for deploying DMZ.
+
+  Why password or public-key authentication is possible for only once?
+  Why not give normal users a chance to beat back attackers who logged in
+  through brute-force attacks?
+
+  You can deploy extra login authentications that the only normal user knows
+  how to pass.
+
+Provide simple RBAC-like administrative task division.
+
+  TOMOYO Linux's PIH forms a tree structure.
+  This means that you can split one tree into several subtrees
+  and associate each subtree with each administrative task.
+  You can give each subtree necessary and sufficient permissions
+  for each administrative task.
+
+  You can deploy custom authentication at the entry point of each subtree
+  so that one administrator cannot proceed to other administrator's subtrees.
+
+Provide on-demand honey pot.
+
+  Since TOMOYO Linux's approach is
+  "know all essential requests in advance and create policy that permits only them",
+  you can treat anomalous requests as attacks (if you want to do so).
+
+  Common MAC implementations merely reject requests that violate policy.
+  But TOMOYO can trigger special handler for execve() requests that are not
+  permitted by policy.
+
+  Most attackers' purpose is to execute /bin/sh to start something malicious
+  rather than consume CPU resources to slow down the target system.
+
+  Attackers execute an exploit code using buffer overflow vulnerability
+  to steal control of a process.
+  TOMOYO can get back control if an exploit code requests execve()
+  that is not permitted by policy.
+  TOMOYO executes a different program (i.e. special handler prepared for
+  violation of execve() policy) instead of a program requested by attackers
+  to redirect attackers to somewhere else (e.g. honey pot).
+  This makes it possible to act your Linux box as an on-demand honey pot
+  while keeping regular services for your usage.
+
+  Of course, you may silently terminate a process who requests execve()
+  that is not permitted by policy using this redirection mechanism.
+  You may merely reject execve() requests like common MAC implementations.
+
+And more...
+
+  Your imagination invents new usage.
+
+
+Authors:
+  Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
+  Toshiharu Harada <haradats@nttdata.co.jp>
+  Kentaro Takeda <takedakn@nttdata.co.jp>
+
+
+Appendix 1 - Usability features of TOMOYO Linux
+
+TOMOYO Linux switches the context of a process whenever a process executes
+a program, but there are two exceptions.
+Administrator may relocate domain of a process if PIH is not meaningful for
+that process (e.g. daemon programs). This exception allows administrator
+restart daemon programs.
+Administrator may suppress domain transition if domain transition is not
+meaningful for that process (e.g. /bin/touch called from /etc/init.d/ scripts).
+This exception reduces memory usage for policy.
+
+TOMOYO Linux can apply access control over all userspace applications,
+but administrator can also apply access control over only specific userspace
+applications if he/she wishes so.
+
+TOMOYO Linux supports "delayed enforcing mode" that allows administrator
+interactively judge whether a request which is not defined in the policy
+should be permitted or rejected.
+This mode helps administrator adjust the policy after software updates.
+
+
+Appendix 2 - Presentation slides
+
+- PacSec2007: TOMOYO Linux: "A Practical Method to Understand and Protect
+                             Your Own Linux Box"
+  http://sourceforge.jp/projects/tomoyo/document/PacSec2007-en-demo.pdf
+
+- OLS2007: "TOMOYO Linux BoF"
+  http://sourceforge.jp/projects/tomoyo/document/ols2007-tomoyo-20070629.pdf
+
+- ELC2007: "TOMOYO Linux - A Lightweight and Manageable Security System
+            for PC and Embedded Linux"
+  http://sourceforge.jp/projects/tomoyo/document/elc2007-presentation-20070418-for_linux.pdf

-- 

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

* [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 01/30] TOMOYO Linux documentation Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 15:29   ` Daniel Walker
  2008-04-04 12:22 ` [TOMOYO #7 03/30] Constants for /proc/ccs/ interface Tetsuo Handa
                   ` (28 subsequent siblings)
  30 siblings, 1 reply; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-common-header.patch --]
[-- Type: text/plain, Size: 29773 bytes --]

This file defines prototypes and constants that are needed for internal use.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/ccs_common.h |  744 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 744 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/ccs_common.h
@@ -0,0 +1,744 @@
+/*
+ * include/linux/ccs_common.h
+ *
+ * Common functions for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#ifndef _LINUX_CCS_COMMON_H
+#define _LINUX_CCS_COMMON_H
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <stdarg.h>
+#include <linux/delay.h>
+#include <linux/hardirq.h>
+
+struct dentry;
+struct vfsmount;
+struct in6_addr;
+extern asmlinkage long sys_getppid(void);
+
+#define false 0
+#define true 1
+
+/*
+ * Singly linked list.
+ *
+ * This list holds ACL entries used for access control.
+ * Since TOMOYO Linux performs string pattern matching which takes long time,
+ * I don't want to take any locks which disable preemption.
+ * Threfore, I use singly linked list that cannot delete an element
+ * but can make the code read-lock free.
+ * This is OK because ACL entries in this list are seldom deleted.
+ * You don't append garbage ACL entries without reasons, do you?
+ */
+struct list1_head {
+	struct list1_head *next;
+};
+
+#define LIST1_HEAD_INIT(name) { &(name) }
+#define LIST1_HEAD(name) struct list1_head name = LIST1_HEAD_INIT(name)
+
+static inline void INIT_LIST1_HEAD(struct list1_head *list)
+{
+	list->next = list;
+}
+
+/**
+ * list1_entry - get the struct for this entry
+ * @ptr:        the &struct list1_head pointer.
+ * @type:       the type of the struct this is embedded in.
+ * @member:     the name of the list1_struct within the struct.
+ */
+#define list1_entry(ptr, type, member) container_of(ptr, type, member)
+
+/**
+ * list1_for_each        -       iterate over a list
+ * @pos:        the &struct list1_head to use as a loop cursor.
+ * @head:       the head for your list.
+ */
+#define list1_for_each(pos, head)					\
+	for (pos = (head)->next; prefetch(pos->next), pos != (head);	\
+	     pos = pos->next)
+
+/**
+ * list1_for_each_entry  -       iterate over list of given type
+ * @pos:        the type * to use as a loop cursor.
+ * @head:       the head for your list.
+ * @member:     the name of the list1_struct within the struct.
+ */
+#define list1_for_each_entry(pos, head, member)				\
+	for (pos = list1_entry((head)->next, typeof(*pos), member);	\
+	     prefetch(pos->member.next), &pos->member != (head);        \
+	     pos = list1_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list1_for_each_cookie - iterate over a list with cookie.
+ * @pos:        the &struct list1_head to use as a loop cursor.
+ * @cookie:     the &struct list1_head to use as a cookie.
+ * @head:       the head for your list.
+ *
+ * Same with list_for_each except that this primitive uses cookie
+ * so that we can continue iteration.
+ */
+#define list1_for_each_cookie(pos, cookie, head)			\
+	for (({ if (!cookie)						\
+				     cookie = head; }), pos = (cookie)->next; \
+	     prefetch(pos->next), pos != (head) || ((cookie) = NULL);	\
+	     (cookie) = pos, pos = pos->next)
+
+/**
+ * list_add_tail_mb - add a new entry with memory barrier.
+ * @new: new entry to be added.
+ * @head: list head to add it before.
+ *
+ * Same with list_add_tail_rcu() except that this primitive uses mb()
+ * so that we can traverse forwards using list_for_each() and
+ * list_for_each_cookie().
+ */
+static inline void list1_add_tail_mb(struct list1_head *new,
+				     struct list1_head *head)
+{
+	struct list1_head *pos = head;
+	new->next = head;
+	mb(); /* Avoid out-of-order execution. */
+	while (pos->next != head)
+		pos = pos->next;
+	pos->next = new;
+}
+
+/* Temporary buffer for holding pathnames. */
+struct ccs_page_buffer {
+	char buffer[4096];
+};
+
+/* Subset of "struct stat". */
+struct mini_stat {
+	uid_t uid;
+	gid_t gid;
+	ino_t ino;
+};
+
+/* Structure for attribute checks in addition to pathname checks. */
+struct obj_info {
+	bool validate_done;
+	bool path1_valid;
+	bool path1_parent_valid;
+	bool path2_parent_valid;
+	struct dentry *path1_dentry;
+	struct vfsmount *path1_vfsmnt;
+	struct dentry *path2_dentry;
+	struct vfsmount *path2_vfsmnt;
+	struct mini_stat path1_stat;
+	/* I don't handle path2_stat for rename operation. */
+	struct mini_stat path1_parent_stat;
+	struct mini_stat path2_parent_stat;
+	struct linux_binprm *bprm;
+	struct ccs_page_buffer *tmp;
+};
+
+/* Structure for holding a token. */
+struct path_info {
+	const char *name;
+	u32 hash;          /* = full_name_hash(name, strlen(name)) */
+	u16 total_len;     /* = strlen(name)                       */
+	u16 const_len;     /* = const_part_length(name)            */
+	bool is_dir;       /* = strendswith(name, "/")             */
+	bool is_patterned; /* = path_contains_pattern(name)        */
+	u16 depth;         /* = path_depth(name)                   */
+};
+
+/*
+ * This is the max length of a token.
+ *
+ * A token consists of only ASCII printable characters.
+ * Non printable characters in a token is represented in \ooo style
+ * octal string. Thus, \ itself is represented as \\.
+ */
+#define CCS_MAX_PATHNAME_LEN 4000
+
+/* Structure for "path_group" directive. */
+struct path_group_member {
+	struct list1_head list;
+	const struct path_info *member_name;
+	bool is_deleted;
+};
+
+/* Structure for "path_group" directive. */
+struct path_group_entry {
+	struct list1_head list;
+	const struct path_info *group_name;
+	struct list1_head path_group_member_list;
+};
+
+/* Structure for "address_group" directive. */
+struct address_group_member {
+	struct list1_head list;
+	union {
+		u32 ipv4;                    /* Host byte order    */
+		const struct in6_addr *ipv6; /* Network byte order */
+	} min, max;
+	bool is_deleted;
+	bool is_ipv6;
+};
+
+/* Structure for "address_group" directive. */
+struct address_group_entry {
+	struct list1_head list;
+	const struct path_info *group_name;
+	struct list1_head address_group_member_list;
+};
+
+/* Structure for holding requested pathname. */
+struct path_info_with_data {
+	/* Keep "head" first, for this pointer is passed to ccs_free(). */
+	struct path_info head;
+	char bariier1[16]; /* Safeguard for overrun. */
+	char body[CCS_MAX_PATHNAME_LEN];
+	char barrier2[16]; /* Safeguard for overrun. */
+};
+
+/* Common header for holding ACL entries. */
+struct acl_info {
+	/*
+	 * Keep "access_me_via_ccs_get_condition_part" first, for
+	 * memory for this filed is not allocated if
+	 * (type & ACL_WITH_CONDITION) == 0.
+	 */
+	const struct condition_list *access_me_via_ccs_get_condition_part;
+	struct list1_head list;
+	/*
+	 * Type of this ACL entry.
+	 *
+	 * MSB is is_deleted flag.
+	 * Next bit is with_condition flag.
+	 */
+	u8 type;
+} __attribute__((__packed__));
+
+/* This ACL entry is deleted.           */
+#define ACL_DELETED        0x80
+/* This ACL entry has conditional part. */
+#define ACL_WITH_CONDITION 0x40
+
+/* Structure for domain information. */
+struct domain_info {
+	struct list1_head list;
+	struct list1_head acl_info_list;
+	/* Name of this domain. Never NULL.          */
+	const struct path_info *domainname;
+	u8 profile;        /* Profile number to use. */
+	u8 is_deleted;     /* Delete flag.           */
+	bool quota_warned; /* Quota warnning flag.   */
+	/* DOMAIN_FLAGS_IGNORE_*. Use ccs_set_domain_flag() to modify. */
+	u8 flags;
+};
+
+/* Profile number is an integer between 0 and 255. */
+#define MAX_PROFILES 256
+
+/* Ignore "allow_read" directive in exception policy. */
+#define DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1
+/* Ignore "allow_env" directive in exception policy.  */
+#define DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_ENV  2
+
+/*
+ * Structure for "execute_handler" and "denied_execute_handler" directive.
+ * These directives can exist only one entry in a domain.
+ *
+ * If "execute_handler" directive exists and the current process is not
+ * an execute handler, all execve() requests are replaced by execve() requests
+ * of a program specified by "execute_handler" directive.
+ * If the current process is an execute handler,
+ * "execute_handler" and "denied_execute_handler" directives are ignored.
+ * The program specified by "execute_handler" validates execve() parameters
+ * and executes the original execve() requests if appropriate.
+ *
+ * "denied_execute_handler" directive is used only when execve() request was
+ * rejected in enforcing mode (i.e. MAC_FOR_FILE=enforcing).
+ * The program specified by "denied_execute_handler" does whatever it wants
+ * to do (e.g. silently terminate, change firewall settings,
+ * redirect the user to honey pot etc.).
+ */
+struct execute_handler_record {
+	struct acl_info head;            /* type = TYPE_*EXECUTE_HANDLER */
+	const struct path_info *handler; /* Pointer to single pathname.  */
+};
+
+/*
+ * Structure for "allow_read/write", "allow_execute", "allow_read",
+ * "allow_write", "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink" and "allow_rewrite" directive.
+ */
+struct single_path_acl_record {
+	struct acl_info head; /* type = TYPE_SINGLE_PATH_ACL */
+	bool u_is_group; /* True if u points to "path_group" directive. */
+	u16 perm;
+	union {
+		/* Pointer to single pathname. */
+		const struct path_info *filename;
+		/* Pointer to pathname group. */
+		const struct path_group_entry *group;
+	} u;
+};
+
+/* Structure for "allow_rename" and "allow_link" directive. */
+struct double_path_acl_record {
+	struct acl_info head; /* type = TYPE_DOUBLE_PATH_ACL */
+	u8 perm;
+	bool u1_is_group; /* True if u1 points to "path_group" directive. */
+	bool u2_is_group; /* True if u2 points to "path_group" directive. */
+	union {
+		/* Pointer to single pathname. */
+		const struct path_info *filename1;
+		/* Pointer to pathname group. */
+		const struct path_group_entry *group1;
+	} u1;
+	union {
+		/* Pointer to single pathname. */
+		const struct path_info *filename2;
+		/* Pointer to pathname group. */
+		const struct path_group_entry *group2;
+	} u2;
+};
+
+/* Structure for "allow_argv0" directive. */
+struct argv0_acl_record {
+	struct acl_info head;             /* type = TYPE_ARGV0_ACL       */
+	const struct path_info *filename; /* Pointer to single pathname. */
+	const struct path_info *argv0;    /* = strrchr(argv[0], '/') + 1 */
+};
+
+/* Structure for "allow_env" directive in domain policy. */
+struct env_acl_record {
+	struct acl_info head;        /* type = TYPE_ENV_ACL  */
+	const struct path_info *env; /* environment variable */
+};
+
+/* Structure for "allow_capability" directive. */
+struct capability_acl_record {
+	struct acl_info head; /* type = TYPE_CAPABILITY_ACL */
+	u8 operation;
+};
+
+/* Structure for "allow_signal" directive. */
+struct signal_acl_record {
+	struct acl_info head; /* type = TYPE_SIGNAL_ACL */
+	u16 sig;
+	/* Pointer to destination pattern. */
+	const struct path_info *domainname;
+};
+
+/* Structure for "allow_network" directive. */
+struct ip_network_acl_record {
+	struct acl_info head; /* type = TYPE_IP_NETWORK_ACL */
+	/*
+	 * operation_type takes one of the following constants.
+	 *   NETWORK_ACL_UDP_BIND for UDP's bind() operation.
+	 *   NETWORK_ACL_UDP_CONNECT for UDP's connect()/send()/recv()
+	 *                               operation.
+	 *   NETWORK_ACL_TCP_BIND for TCP's bind() operation.
+	 *   NETWORK_ACL_TCP_LISTEN for TCP's listen() operation.
+	 *   NETWORK_ACL_TCP_CONNECT for TCP's connect() operation.
+	 *   NETWORK_ACL_TCP_ACCEPT for TCP's accept() operation.
+	 *   NETWORK_ACL_RAW_BIND for IP's bind() operation.
+	 *   NETWORK_ACL_RAW_CONNECT for IP's connect()/send()/recv()
+	 *                               operation.
+	 */
+	u8 operation_type;
+	/*
+	 * record_type takes one of the following constants.
+	 *   IP_RECORD_TYPE_ADDRESS_GROUP
+	 *                if u points to "address_group" directive.
+	 *   IP_RECORD_TYPE_IPv4
+	 *                if u points to an IPv4 address.
+	 *   IP_RECORD_TYPE_IPv6
+	 *                if u points to an IPv6 address.
+	 */
+	u8 record_type;
+	/* Start of port number range. */
+	u16 min_port;
+	/* End of port number range.   */
+	u16 max_port;
+	union {
+		struct {
+			/* Start of IPv4 address range. Host endian. */
+			u32 min;
+			/* End of IPv4 address range. Host endian.   */
+			u32 max;
+		} ipv4;
+		struct {
+			/* Start of IPv6 address range. Big endian.  */
+			const struct in6_addr *min;
+			/* End of IPv6 address range. Big endian.    */
+			const struct in6_addr *max;
+		} ipv6;
+		/* Pointer to address group. */
+		const struct address_group_entry *group;
+	} u;
+};
+
+#define IP_RECORD_TYPE_ADDRESS_GROUP 0
+#define IP_RECORD_TYPE_IPv4          1
+#define IP_RECORD_TYPE_IPv6          2
+
+/* Keywords for ACLs. */
+#define KEYWORD_ADDRESS_GROUP             "address_group "
+#define KEYWORD_AGGREGATOR                "aggregator "
+#define KEYWORD_ALIAS                     "alias "
+#define KEYWORD_ALLOW_ARGV0               "allow_argv0 "
+#define KEYWORD_ALLOW_CAPABILITY          "allow_capability "
+#define KEYWORD_ALLOW_CHROOT              "allow_chroot "
+#define KEYWORD_ALLOW_ENV                 "allow_env "
+#define KEYWORD_ALLOW_MOUNT               "allow_mount "
+#define KEYWORD_ALLOW_NETWORK             "allow_network "
+#define KEYWORD_ALLOW_PIVOT_ROOT          "allow_pivot_root "
+#define KEYWORD_ALLOW_READ                "allow_read "
+#define KEYWORD_ALLOW_SIGNAL              "allow_signal "
+#define KEYWORD_DELETE                    "delete "
+#define KEYWORD_DENY_AUTOBIND             "deny_autobind "
+#define KEYWORD_DENY_REWRITE              "deny_rewrite "
+#define KEYWORD_DENY_UNMOUNT              "deny_unmount "
+#define KEYWORD_FILE_PATTERN              "file_pattern "
+#define KEYWORD_INITIALIZE_DOMAIN         "initialize_domain "
+#define KEYWORD_KEEP_DOMAIN               "keep_domain "
+#define KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
+#define KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
+#define KEYWORD_PATH_GROUP                "path_group "
+#define KEYWORD_SELECT                    "select "
+#define KEYWORD_UNDELETE                  "undelete "
+#define KEYWORD_USE_PROFILE               "use_profile "
+#define KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
+#define KEYWORD_IGNORE_GLOBAL_ALLOW_ENV   "ignore_global_allow_env"
+#define KEYWORD_EXECUTE_HANDLER           "execute_handler"
+#define KEYWORD_DENIED_EXECUTE_HANDLER    "denied_execute_handler"
+#define KEYWORD_MAC_FOR_CAPABILITY        "MAC_FOR_CAPABILITY::"
+/* A domain definition starts with <kernel>. */
+#define ROOT_NAME                         "<kernel>"
+#define ROOT_NAME_LEN                     (sizeof(ROOT_NAME) - 1)
+
+/* Index numbers for Access Controls. */
+#define CCS_TOMOYO_MAC_FOR_FILE                  0  /* domain_policy.conf */
+#define CCS_TOMOYO_MAC_FOR_ARGV0                 1  /* domain_policy.conf */
+#define CCS_TOMOYO_MAC_FOR_ENV                   2  /* domain_policy.conf */
+#define CCS_TOMOYO_MAC_FOR_NETWORK               3  /* domain_policy.conf */
+#define CCS_TOMOYO_MAC_FOR_SIGNAL                4  /* domain_policy.conf */
+#define CCS_SAKURA_DENY_CONCEAL_MOUNT            5
+#define CCS_SAKURA_RESTRICT_CHROOT               6  /* system_policy.conf */
+#define CCS_SAKURA_RESTRICT_MOUNT                7  /* system_policy.conf */
+#define CCS_SAKURA_RESTRICT_UNMOUNT              8  /* system_policy.conf */
+#define CCS_SAKURA_RESTRICT_PIVOT_ROOT           9  /* system_policy.conf */
+#define CCS_SAKURA_RESTRICT_AUTOBIND            10  /* system_policy.conf */
+#define CCS_TOMOYO_MAX_ACCEPT_ENTRY             11
+#define CCS_TOMOYO_MAX_GRANT_LOG                12
+#define CCS_TOMOYO_MAX_REJECT_LOG               13
+#define CCS_TOMOYO_VERBOSE                      14
+#define CCS_ALLOW_ENFORCE_GRACE                 15
+#define CCS_SLEEP_PERIOD                        16  /* profile.conf       */
+#define CCS_MAX_CONTROL_INDEX                   17
+
+/* Index numbers for updates counter. */
+#define CCS_UPDATES_COUNTER_SYSTEM_POLICY    0
+#define CCS_UPDATES_COUNTER_DOMAIN_POLICY    1
+#define CCS_UPDATES_COUNTER_EXCEPTION_POLICY 2
+#define CCS_UPDATES_COUNTER_PROFILE          3
+#define CCS_UPDATES_COUNTER_QUERY            4
+#define CCS_UPDATES_COUNTER_MANAGER          5
+#define CCS_UPDATES_COUNTER_GRANT_LOG        6
+#define CCS_UPDATES_COUNTER_REJECT_LOG       7
+#define MAX_CCS_UPDATES_COUNTER              8
+
+/* Structure for reading/writing policy via /proc interfaces. */
+struct ccs_io_buffer {
+	int (*read) (struct ccs_io_buffer *);
+	int (*write) (struct ccs_io_buffer *);
+	int (*poll) (struct file *file, poll_table *wait);
+	/* Exclusive lock for read_buf.         */
+	struct mutex read_sem;
+	/* Exclusive lock for write_buf.        */
+	struct mutex write_sem;
+	/* The position currently reading from. */
+	struct list1_head *read_var1;
+	/* Extra variables for reading.         */
+	struct list1_head *read_var2;
+	/* The position currently writing to.   */
+	struct domain_info *write_var1;
+	/* The step for reading.                */
+	int read_step;
+	/* Buffer for reading.                  */
+	char *read_buf;
+	/* EOF flag for reading.                */
+	bool read_eof;
+	/* Extra variable for reading.          */
+	u8 read_bit;
+	/* Bytes available for reading.         */
+	int read_avail;
+	/* Size of read buffer.                 */
+	int readbuf_size;
+	/* Buffer for writing.                  */
+	char *write_buf;
+	/* Bytes available for writing.         */
+	int write_avail;
+	/* Size of write buffer.                */
+	int writebuf_size;
+};
+
+/* Prototype definition. */
+struct condition_list;
+
+/* Check conditional part of an ACL entry. */
+bool ccs_check_condition(const struct acl_info *acl,
+			 struct obj_info *obj_info);
+/* Check whether the domain has too many ACL entries to hold. */
+bool ccs_check_domain_quota(struct domain_info * const domain);
+/* Transactional sprintf() for policy dump. */
+bool ccs_io_printf(struct ccs_io_buffer *head, const char *fmt, ...)
+	__attribute__ ((format(printf, 2, 3)));
+/* Check whether the domainname is correct. */
+bool ccs_is_correct_domain(const unsigned char *domainname,
+			   const char *function);
+/* Check whether the token is correct. */
+bool ccs_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function);
+/* Check whether the token can be a domainname. */
+bool ccs_is_domain_def(const unsigned char *buffer);
+/* Check whether the given filename matches the given pattern. */
+bool ccs_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern);
+/* Print conditional part of an ACL entry. */
+bool ccs_print_condition(struct ccs_io_buffer *head,
+			const struct condition_list *cond);
+/* Read "address_group" entry in exception policy. */
+bool ccs_read_address_group_policy(struct ccs_io_buffer *head);
+/* Read "aggregator" entry in exception policy. */
+bool ccs_read_aggregator_policy(struct ccs_io_buffer *head);
+/* Read "alias" entry in exception policy. */
+bool ccs_read_alias_policy(struct ccs_io_buffer *head);
+/* Read "allow_chroot" entry in system policy. */
+bool ccs_read_chroot_policy(struct ccs_io_buffer *head);
+/*
+ * Read "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+bool ccs_read_domain_initializer_policy(struct ccs_io_buffer *head);
+/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
+bool ccs_read_domain_keeper_policy(struct ccs_io_buffer *head);
+/* Read "file_pattern" entry in exception policy. */
+bool ccs_read_file_pattern(struct ccs_io_buffer *head);
+/* Read "allow_read" entry in exception policy. */
+bool ccs_read_globally_readable_policy(struct ccs_io_buffer *head);
+/* Read "allow_env" entry in exception policy. */
+bool ccs_read_globally_usable_env_policy(struct ccs_io_buffer *head);
+/* Read "allow_mount" entry in system policy. */
+bool ccs_read_mount_policy(struct ccs_io_buffer *head);
+/* Read "deny_rewrite" entry in exception policy. */
+bool ccs_read_no_rewrite_policy(struct ccs_io_buffer *head);
+/* Read "deny_unmount" entry in system policy. */
+bool ccs_read_no_umount_policy(struct ccs_io_buffer *head);
+/* Read "path_group" entry in exception policy. */
+bool ccs_read_path_group_policy(struct ccs_io_buffer *head);
+/* Read "allow_pivot_root" entry in system policy. */
+bool ccs_read_pivot_root_policy(struct ccs_io_buffer *head);
+/* Read "deny_autobind" entry in system policy. */
+bool ccs_read_reserved_port_policy(struct ccs_io_buffer *head);
+/* Write domain policy violation warning message to console? */
+bool ccs_verbose_mode(void);
+/* Allocate buffer for domain policy auditing. */
+char *ccs_init_audit_log(int *len, const u8 profile, const u8 mode,
+			 struct linux_binprm *bprm);
+/* Convert capability index to capability name. */
+const char *ccs_cap2keyword(const u8 operation);
+/* Convert double path operation to operation name. */
+const char *ccs_dp2keyword(const u8 operation);
+/* Get the pathname of current process. */
+const char *ccs_get_exe(void);
+/* Get the last component of the given domainname. */
+const char *ccs_get_last_name(const struct domain_info *domain);
+/* Get warning message. */
+const char *ccs_get_msg(const bool is_enforce);
+/* Convert network operation index to operation name. */
+const char *ccs_net2keyword(const u8 operation);
+/* Convert single path operation to operation name. */
+const char *ccs_sp2keyword(const u8 operation);
+/* Create conditional part of an ACL entry. */
+const struct condition_list *
+ccs_find_or_assign_new_condition(char * const condition);
+/* Read conditional part of an ACL entry. */
+const struct condition_list *
+ccs_get_condition_part(const struct acl_info *acl);
+/* Add an ACL entry to domain's ACL list. */
+int ccs_add_domain_acl(struct domain_info *domain, struct acl_info *acl);
+/* Check whether there is space for audit logs. */
+int ccs_can_save_audit_log(const bool is_granted);
+/* Ask supervisor's opinion. */
+int ccs_check_supervisor(const char *fmt, ...)
+	__attribute__ ((format(printf, 1, 2)));
+/* Close /proc/ccs/ interface. */
+int ccs_close_control(struct file *file);
+/* Delete an ACL entry from domain's ACL list. */
+int ccs_del_domain_acl(struct acl_info *acl);
+/* Delete a domain. */
+int ccs_delete_domain(char *data);
+/* Open operation for /proc/ccs/ interface. */
+int ccs_open_control(const u8 type, struct file *file);
+/* Poll operation for /proc/ccs/ interface. */
+int ccs_poll_control(struct file *file, poll_table *wait);
+/* Check whether there is a grant log. */
+int ccs_poll_grant_log(struct file *file, poll_table *wait);
+/* Check whether there is a reject log. */
+int ccs_poll_reject_log(struct file *file, poll_table *wait);
+/* Read operation for /proc/ccs/ interface. */
+int ccs_read_control(struct file *file, char __user *buffer,
+		     const int buffer_len);
+/* Read a grant log. */
+int ccs_read_grant_log(struct ccs_io_buffer *head);
+/* Read a reject log. */
+int ccs_read_reject_log(struct ccs_io_buffer *head);
+/* Add "address_group" entry in exception policy. */
+int ccs_write_address_group_policy(char *data, const bool is_delete);
+/* Create "aggregator" entry in exception policy. */
+int ccs_write_aggregator_policy(char *data, const bool is_delete);
+/* Create "alias" entry in exception policy. */
+int ccs_write_alias_policy(char *data, const bool is_delete);
+/* Create "allow_argv0" entry in domain policy. */
+int ccs_write_argv0_policy(char *data, struct domain_info *domain,
+			   const struct condition_list *condition,
+			   const bool is_delete);
+/* Write an audit log. */
+int ccs_write_audit_log(char *log, const bool is_granted);
+/* Create "allow_capability" entry in domain policy. */
+int ccs_write_capability_policy(char *data, struct domain_info *domain,
+				const struct condition_list *condition,
+				const bool is_delete);
+/* Create "allow_chroot" entry in system policy. */
+int ccs_write_chroot_policy(char *data, const bool is_delete);
+/*
+ * Create "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+int ccs_write_domain_initializer_policy(char *data, const bool is_not,
+					const bool is_delete);
+/* Create "keep_domain" and "no_keep_domain" entry in exception policy. */
+int ccs_write_domain_keeper_policy(char *data, const bool is_not,
+				   const bool is_delete);
+/* Create "allow_env" entry in domain policy. */
+int ccs_write_env_policy(char *data, struct domain_info *domain,
+			 const struct condition_list *condition,
+			 const bool is_delete);
+/*
+ * Create "allow_read/write", "allow_execute", "allow_read", "allow_write",
+ * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename",
+ * "allow_link", "execute_handler" and "denied_execute_handler"
+ * entry in domain policy.
+ */
+int ccs_write_file_policy(char *data, struct domain_info *domain,
+			  const struct condition_list *condition,
+			  const bool is_delete);
+/* Create "allow_read" entry in exception policy. */
+int ccs_write_globally_readable_policy(char *data, const bool is_delete);
+/* Create "allow_env" entry in exception policy. */
+int ccs_write_globally_usable_env_policy(char *data, const bool is_delete);
+/* Create "allow_mount" entry in system policy. */
+int ccs_write_mount_policy(char *data, const bool is_delete);
+/* Create "allow_network" entry in domain policy. */
+int ccs_write_network_policy(char *data, struct domain_info *domain,
+			     const struct condition_list *condition,
+			     const bool is_delete);
+/* Create "deny_rewrite" entry in exception policy. */
+int ccs_write_no_rewrite_policy(char *data, const bool is_delete);
+/* Create "deny_unmount" entry in system policy. */
+int ccs_write_no_umount_policy(char *data, const bool is_delete);
+/* Create "path_group" entry in exception policy. */
+int ccs_write_path_group_policy(char *data, const bool is_delete);
+/* Create "file_pattern" entry in exception policy. */
+int ccs_write_pattern_policy(char *data, const bool is_delete);
+/* Create "allow_pivot_root" entry in system policy. */
+int ccs_write_pivot_root_policy(char *data, const bool is_delete);
+/* Create "deny_autobind" entry in system policy. */
+int ccs_write_reserved_port_policy(char *data, const bool is_delete);
+/* Create "allow_signal" entry in domain policy. */
+int ccs_write_signal_policy(char *data, struct domain_info *domain,
+			    const struct condition_list *condition,
+			    const bool is_delete);
+/* Write operation for /proc/ccs/ interface. */
+int ccs_write_control(struct file *file, const char __user *buffer,
+		      const int buffer_len);
+/* Find a domain by the given name. */
+struct domain_info *ccs_find_domain(const char *domainname);
+/* Find or create a domain by the given name. */
+struct domain_info *ccs_find_or_assign_new_domain(const char *domainname,
+						  const u8 profile);
+/* Undelete a domain. */
+struct domain_info *ccs_undelete_domain(const char *domainname);
+/* Write a grant log. */
+u8 ccs_check_capability_flags(const u8 index);
+/* Check mode for specified functionality. */
+unsigned int ccs_check_flags(const u8 index);
+/* Same with ccs_check_flags() except that it doesn't check might_sleep(). */
+unsigned int ccs_check_flags_no_sleep_check(const u8 index);
+/* Allocate memory for structures. */
+void *ccs_alloc_acl_element(const u8 acl_type,
+			    const struct condition_list *condition);
+/* Fill in "struct path_info" members. */
+void ccs_fill_path_info(struct path_info *ptr);
+/* Run policy loader when /sbin/init starts. */
+void ccs_load_policy(const char *filename);
+/* Print an IPv6 address. */
+void ccs_print_ipv6(char *buffer, const int buffer_len,
+		    const struct in6_addr *ip);
+/* Change "struct domain_info"->flags. */
+void ccs_set_domain_flag(struct domain_info *domain, const bool is_delete,
+			 const u8 flags);
+/* Update the process's state. */
+void ccs_update_condition(const struct acl_info *acl);
+/* Update the policy change counter. */
+void ccs_update_counter(const unsigned char index);
+
+/* strcmp() for "struct path_info" structure. */
+static inline bool ccs_pathcmp(const struct path_info *a,
+			       const struct path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/* Get type of an ACL entry. */
+static inline u8 ccs_acl_type1(struct acl_info *ptr)
+{
+	return (ptr->type & ~(ACL_DELETED | ACL_WITH_CONDITION));
+}
+
+/* Get type of an ACL entry. */
+static inline u8 ccs_acl_type2(struct acl_info *ptr)
+{
+	return (ptr->type & ~ACL_WITH_CONDITION);
+}
+
+/* A linked list of domains. */
+extern struct list1_head domain_list;
+/* Has /sbin/init started? */
+extern bool sbin_init_started;
+/* Log level for printk(). */
+extern const char *ccs_log_level;
+/* The kernel's domain. */
+extern struct domain_info KERNEL_DOMAIN;
+/* Exclusive lock for updating domain policy. */
+extern struct mutex domain_acl_lock;
+
+#endif

-- 

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

* [TOMOYO #7 03/30] Constants for /proc/ccs/ interface.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 01/30] TOMOYO Linux documentation Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 04/30] Prototypes of realpath Tetsuo Handa
                   ` (27 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-proc-header.patch --]
[-- Type: text/plain, Size: 1234 bytes --]

This file defines constants for /proc/ccs/ interface.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/ccs_proc.h |   31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/ccs_proc.h
@@ -0,0 +1,31 @@
+/*
+ * include/linux/ccs_proc.h
+ *
+ * /proc/ccs/ interface for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#ifndef _LINUX_CCS_PROC_H
+#define _LINUX_CCS_PROC_H
+
+/* Indexes for /proc/ccs/ interfaces. */
+#define CCS_DOMAINPOLICY          0
+#define CCS_EXCEPTIONPOLICY       1
+#define CCS_SYSTEMPOLICY          2
+#define CCS_DOMAIN_STATUS         3
+#define CCS_PROCESS_STATUS        4
+#define CCS_MEMINFO               5
+#define CCS_GRANTLOG              6
+#define CCS_REJECTLOG             7
+#define CCS_SELFDOMAIN            8
+#define CCS_VERSION               9
+#define CCS_PROFILE              10
+#define CCS_QUERY                11
+#define CCS_MANAGER              12
+#define CCS_UPDATESCOUNTER       13
+
+#endif

-- 

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

* [TOMOYO #7 04/30] Prototypes of realpath.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (2 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 03/30] Constants for /proc/ccs/ interface Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 05/30] External functions prototypes for SAKURA Tetsuo Handa
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-realpath-header.patch --]
[-- Type: text/plain, Size: 2349 bytes --]

This file defines realpath related prototypes.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/realpath.h |   62 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/realpath.h
@@ -0,0 +1,62 @@
+/*
+ * include/linux/realpath.h
+ *
+ * Get the canonicalized absolute pathnames. The basis for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#ifndef _LINUX_REALPATH_H
+#define _LINUX_REALPATH_H
+
+struct dentry;
+struct vfsmount;
+struct condition_list;
+struct path_info;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int ccs_realpath_from_dentry2(struct dentry *dentry, struct vfsmount *mnt,
+			      char *newname, int newname_len);
+
+/*
+ * Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ * These functions use ccs_alloc(), so caller must ccs_free()
+ * if these functions didn't return NULL.
+ */
+char *ccs_realpath(const char *pathname);
+/* Same with ccs_realpath() except that it doesn't follow the final symlink. */
+char *ccs_realpath_nofollow(const char *pathname);
+/* Same with ccs_realpath() except that the pathname is already solved. */
+char *ccs_realpath_from_dentry(struct dentry *dentry, struct vfsmount *mnt);
+
+/*
+ * Allocate memory for ACL entry.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *ccs_alloc_element(const unsigned int size);
+
+/* Get used RAM size for ccs_alloc_elements(). */
+unsigned int ccs_get_memory_used_for_elements(void);
+
+/*
+ * Keep the given name on the RAM.
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *ccs_save_name(const char *name);
+
+/* Get used RAM size for ccs_save_name(). */
+unsigned int ccs_get_memory_used_for_save_name(void);
+
+/* Allocate memory for temporary use (e.g. permission checks). */
+void *ccs_alloc(const size_t size);
+
+/* Get used RAM size for ccs_alloc(). */
+unsigned int ccs_get_memory_used_for_dynamic(void);
+
+/* Free memory allocated by ccs_alloc(). */
+void ccs_free(const void *p);
+
+#endif

-- 

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

* [TOMOYO #7 05/30] External functions prototypes for SAKURA.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (3 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 04/30] Prototypes of realpath Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 06/30] External functions prototypes for TOMOYO Tetsuo Handa
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-sakura-header.patch --]
[-- Type: text/plain, Size: 2748 bytes --]

This file defines prototypes and constants that are needed by hooks.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/sakura.h |   82 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/sakura.h
@@ -0,0 +1,82 @@
+/*
+ * include/linux/sakura.h
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+/*
+ * A brief description about SAKURA:
+ *
+ *  SAKURA stands for "Security Advancement Know-how Upon Read-only Approach".
+ *  As the name shows, SAKURA was originally a methodology to make root fs
+ *  read-only to avoid tampering the system files.
+ *  But now, SAKURA is not only a methodology but also a kernel patch
+ *  that improves the system security with less effort.
+ *
+ *  SAKURA can restrict operations that affect systemwide.
+ *  SAKURA manages the filesystem's namespace related operations so that
+ *  files remains where the administrator expects.
+ */
+
+#ifndef _LINUX_SAKURA_H
+#define _LINUX_SAKURA_H
+
+#if defined(CONFIG_SAKURA)
+
+/* Check whether the given pathname is allowed to chroot to. */
+int ccs_check_chroot_permission(struct nameidata *nd);
+
+/* Check whether the mount operation with the given parameters is allowed. */
+int ccs_check_mount_permission(char *dev_name, char *dir_name, char *type,
+			       const unsigned long *flags);
+
+/* Check whether the current process is allowed to pivot_root. */
+int ccs_check_pivot_root_permission(struct nameidata *old_nd,
+				    struct nameidata *new_nd);
+
+/* Check whether the given mount operation hides an mounted partition. */
+int ccs_may_mount(struct nameidata *nd);
+
+/* Check whether the given mountpoint is allowed to umount. */
+int ccs_may_umount(struct vfsmount *mnt);
+
+/* Check whether the given port is allowed to autobind. */
+int ccs_may_autobind(const u16 port);
+
+#else
+
+static inline int ccs_check_chroot_permission(struct nameidata *nd)
+{
+	return 0;
+}
+static inline int ccs_check_mount_permission(char *dev_name, char *dir_name,
+					     char *type,
+					     const unsigned long *flags)
+{
+	return 0;
+}
+static inline int ccs_check_pivot_root_permission(struct nameidata *old_nd,
+						  struct nameidata *new_nd)
+{
+	return 0;
+}
+static inline int ccs_may_mount(struct nameidata *nd)
+{
+	return 0;
+}
+static inline int ccs_may_umount(struct vfsmount *mnt)
+{
+	return 0;
+}
+static inline int ccs_may_autobind(const u16 port)
+{
+	return 0;
+}
+
+#endif
+
+#endif

-- 

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

* [TOMOYO #7 06/30] External functions prototypes for TOMOYO.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (4 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 05/30] External functions prototypes for SAKURA Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 07/30] Some wrapper functions for socket operation Tetsuo Handa
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-tomoyo-header.patch --]
[-- Type: text/plain, Size: 11756 bytes --]

This file defines prototypes and constants that are needed by hooks.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/tomoyo.h |  308 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 308 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/tomoyo.h
@@ -0,0 +1,308 @@
+/*
+ * include/linux/tomoyo.h
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+/*
+ * A brief description about TOMOYO:
+ *
+ *  TOMOYO stands for "Task Oriented Management Obviates Your Onus".
+ *  TOMOYO is intended to provide the Domain-Based MAC utilizing task_struct.
+ *
+ *  The biggest feature of TOMOYO is that TOMOYO has "learning mode".
+ *  The learning mode can automatically generate policy definition,
+ *  and dramatically reduces the policy definition labors.
+ *
+ *  TOMOYO is applicable to figuring out the system's behavior, for
+ *  TOMOYO uses the canonicalized absolute pathnames and
+ *  TreeView style domain transitions.
+ */
+
+#ifndef _LINUX_TOMOYO_H
+#define _LINUX_TOMOYO_H
+
+struct path_info;
+struct dentry;
+struct vfsmount;
+struct inode;
+struct linux_binprm;
+struct pt_regs;
+struct ccs_page_buffer;
+
+#if defined(CONFIG_TOMOYO)
+
+int ccs_check_file_perm(const char *filename, const u8 perm,
+			const char *operation);
+int ccs_check_exec_perm(const struct path_info *filename,
+			struct linux_binprm *bprm,
+			struct ccs_page_buffer *buf);
+int ccs_check_open_permission(struct dentry *dentry, struct vfsmount *mnt,
+			      const int flag);
+int ccs_check_1path_perm(const u8 operation,
+				     struct dentry *dentry,
+				     struct vfsmount *mnt);
+int ccs_check_2path_perm(const u8 operation,
+				     struct dentry *dentry1,
+				     struct vfsmount *mnt1,
+				     struct dentry *dentry2,
+				     struct vfsmount *mnt2);
+int ccs_check_rewrite_permission(struct file *filp);
+
+/* Check whether the basename of program and argv0 is allowed to differ. */
+int ccs_check_argv0_perm(const struct path_info *filename, const char *argv0);
+
+/* Check whether the given environment is allowed to be received. */
+int ccs_check_env_perm(const char *env, const u8 profile, const u8 mode);
+
+/* Check whether the given IP address and port number are allowed to use. */
+int ccs_check_network_listen_acl(const bool is_ipv6, const u8 *address,
+				 const u16 port);
+int ccs_check_network_connect_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port);
+int ccs_check_network_bind_acl(const bool is_ipv6, const int sock_type,
+			       const u8 *address, const u16 port);
+int ccs_check_network_accept_acl(const bool is_ipv6, const u8 *address,
+				 const u16 port);
+int ccs_check_network_sendmsg_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port);
+int ccs_check_network_recvmsg_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port);
+
+/* Check whether the given signal is allowed to use. */
+int ccs_check_signal_acl(const int sig, const int pid);
+
+/* Check whether the given capability is allowed to use. */
+bool ccs_capable(const u8 operation);
+
+#else
+
+static inline int ccs_check_file_perm(const char *filename, const u8 perm,
+				      const char *operation)
+{
+	return 0;
+}
+static inline int ccs_check_exec_perm(const struct path_info *filename,
+				      struct linux_binprm *bprm,
+				      struct ccs_page_buffer *buf)
+{
+	return 0;
+}
+static inline int ccs_check_open_permission(struct dentry *dentry,
+					    struct vfsmount *mnt,
+					    const int flag)
+{
+	return 0;
+}
+static inline int ccs_check_1path_perm(const u8 operation,
+						   struct dentry *dentry,
+						   struct vfsmount *mnt)
+{
+	return 0;
+}
+static inline int ccs_check_2path_perm(const u8 operation,
+						   struct dentry *dentry1,
+						   struct vfsmount *mnt1,
+						   struct dentry *dentry2,
+						   struct vfsmount *mnt2)
+{
+	return 0;
+}
+static inline int ccs_check_rewrite_permission(struct file *filp)
+{
+	return 0;
+}
+static inline int ccs_check_argv0_perm(const struct path_info *filename,
+				       const char *argv0)
+{
+	return 0;
+}
+static inline int ccs_check_env_perm(const char *env, const u8 profile,
+				     const u8 mode)
+{
+	return 0;
+}
+static inline int ccs_check_network_listen_acl(const bool is_ipv6,
+					       const u8 *address,
+					       const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_network_connect_acl(const bool is_ipv6,
+						const int sock_type,
+						const u8 *address,
+						const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_network_bind_acl(const bool is_ipv6,
+					     const int sock_type,
+					     const u8 *address, const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_network_accept_acl(const bool is_ipv6,
+					       const u8 *address,
+					       const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_network_sendmsg_acl(const bool is_ipv6,
+						const int sock_type,
+						const u8 *address,
+						const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_network_recvmsg_acl(const bool is_ipv6,
+						const int sock_type,
+						const u8 *address,
+						const u16 port)
+{
+	return 0;
+}
+static inline int ccs_check_signal_acl(const int sig, const int pid)
+{
+	return 0;
+}
+static inline bool ccs_capable(const u8 operation)
+{
+	return true;
+}
+
+#endif
+
+int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode);
+
+int search_binary_handler_with_transition(struct linux_binprm *bprm,
+					  struct pt_regs *regs);
+#define TOMOYO_CHECK_READ_FOR_OPEN_EXEC 1
+#define CCS_DONT_SLEEP_ON_ENFORCE_ERROR 2
+#define TOMOYO_TASK_IS_EXECUTE_HANDLER  4
+
+/* Index numbers for Access Controls. */
+
+#define TYPE_SINGLE_PATH_ACL                 0
+#define TYPE_DOUBLE_PATH_ACL                 1
+#define TYPE_ARGV0_ACL                       2
+#define TYPE_ENV_ACL                         3
+#define TYPE_CAPABILITY_ACL                  4
+#define TYPE_IP_NETWORK_ACL                  5
+#define TYPE_SIGNAL_ACL                      6
+#define TYPE_EXECUTE_HANDLER                 7
+#define TYPE_DENIED_EXECUTE_HANDLER          8
+
+/* Index numbers for File Controls. */
+
+/*
+ * TYPE_READ_WRITE_ACL is special. TYPE_READ_WRITE_ACL is automatically set
+ * if both TYPE_READ_ACL and TYPE_WRITE_ACL are set. Both TYPE_READ_ACL and
+ * TYPE_WRITE_ACL are automatically set if TYPE_READ_WRITE_ACL is set.
+ * TYPE_READ_WRITE_ACL is automatically cleared if either TYPE_READ_ACL or
+ * TYPE_WRITE_ACL is cleared. Both TYPE_READ_ACL and TYPE_WRITE_ACL are
+ * automatically cleared if TYPE_READ_WRITE_ACL is cleared.
+ */
+
+#define TYPE_READ_WRITE_ACL        0
+#define TYPE_EXECUTE_ACL           1
+#define TYPE_READ_ACL              2
+#define TYPE_WRITE_ACL             3
+#define TYPE_CREATE_ACL            4
+#define TYPE_UNLINK_ACL            5
+#define TYPE_MKDIR_ACL             6
+#define TYPE_RMDIR_ACL             7
+#define TYPE_MKFIFO_ACL            8
+#define TYPE_MKSOCK_ACL            9
+#define TYPE_MKBLOCK_ACL          10
+#define TYPE_MKCHAR_ACL           11
+#define TYPE_TRUNCATE_ACL         12
+#define TYPE_SYMLINK_ACL          13
+#define TYPE_REWRITE_ACL          14
+#define MAX_SINGLE_PATH_OPERATION 15
+
+#define TYPE_LINK_ACL             0
+#define TYPE_RENAME_ACL           1
+#define MAX_DOUBLE_PATH_OPERATION 2
+
+/* Index numbers for Capability Controls. */
+
+/* socket(PF_INET or PF_INET6, SOCK_STREAM, *)                 */
+#define TOMOYO_INET_STREAM_SOCKET_CREATE         0
+/* listen() for PF_INET or PF_INET6, SOCK_STREAM               */
+#define TOMOYO_INET_STREAM_SOCKET_LISTEN         1
+/* connect() for PF_INET or PF_INET6, SOCK_STREAM              */
+#define TOMOYO_INET_STREAM_SOCKET_CONNECT        2
+/* socket(PF_INET or PF_INET6, SOCK_DGRAM, *)                  */
+#define TOMOYO_USE_INET_DGRAM_SOCKET             3
+/* socket(PF_INET or PF_INET6, SOCK_RAW, *)                    */
+#define TOMOYO_USE_INET_RAW_SOCKET               4
+/* socket(PF_ROUTE, *, *)                                      */
+#define TOMOYO_USE_ROUTE_SOCKET                  5
+/* socket(PF_PACKET, *, *)                                     */
+#define TOMOYO_USE_PACKET_SOCKET                 6
+/* sys_mount()                                                 */
+#define TOMOYO_SYS_MOUNT                         7
+/* sys_umount()                                                */
+#define TOMOYO_SYS_UMOUNT                        8
+/* sys_reboot()                                                */
+#define TOMOYO_SYS_REBOOT                        9
+/* sys_chroot()                                                */
+#define TOMOYO_SYS_CHROOT                       10
+/* sys_kill(), sys_tkill(), sys_tgkill()                       */
+#define TOMOYO_SYS_KILL                         11
+/* sys_vhangup()                                               */
+#define TOMOYO_SYS_VHANGUP                      12
+/* do_settimeofday(), sys_adjtimex()                           */
+#define TOMOYO_SYS_SETTIME                      13
+/* sys_nice(), sys_setpriority()                               */
+#define TOMOYO_SYS_NICE                         14
+/* sys_sethostname(), sys_setdomainname()                      */
+#define TOMOYO_SYS_SETHOSTNAME                  15
+/* sys_create_module(), sys_init_module(), sys_delete_module() */
+#define TOMOYO_USE_KERNEL_MODULE                16
+/* sys_mknod(S_IFIFO)                                          */
+#define TOMOYO_CREATE_FIFO                      17
+/* sys_mknod(S_IFBLK)                                          */
+#define TOMOYO_CREATE_BLOCK_DEV                 18
+/* sys_mknod(S_IFCHR)                                          */
+#define TOMOYO_CREATE_CHAR_DEV                  19
+/* sys_mknod(S_IFSOCK)                                         */
+#define TOMOYO_CREATE_UNIX_SOCKET               20
+/* sys_link()                                                  */
+#define TOMOYO_SYS_LINK                         21
+/* sys_symlink()                                               */
+#define TOMOYO_SYS_SYMLINK                      22
+/* sys_rename()                                                */
+#define TOMOYO_SYS_RENAME                       23
+/* sys_unlink()                                                */
+#define TOMOYO_SYS_UNLINK                       24
+/* sys_chmod(), sys_fchmod()                                   */
+#define TOMOYO_SYS_CHMOD                        25
+/* sys_chown(), sys_fchown(), sys_lchown()                     */
+#define TOMOYO_SYS_CHOWN                        26
+/* sys_ioctl(), compat_sys_ioctl()                             */
+#define TOMOYO_SYS_IOCTL                        27
+/* sys_kexec_load()                                            */
+#define TOMOYO_SYS_KEXEC_LOAD                   28
+/* sys_pivot_root()                                            */
+#define TOMOYO_SYS_PIVOT_ROOT                   29
+/* sys_ptrace()                                                */
+#define TOMOYO_SYS_PTRACE                       30
+#define TOMOYO_MAX_CAPABILITY_INDEX             31
+
+/* Index numbers for Network Controls. */
+
+#define NETWORK_ACL_UDP_BIND    0
+#define NETWORK_ACL_UDP_CONNECT 1
+#define NETWORK_ACL_TCP_BIND    2
+#define NETWORK_ACL_TCP_LISTEN  3
+#define NETWORK_ACL_TCP_CONNECT 4
+#define NETWORK_ACL_TCP_ACCEPT  5
+#define NETWORK_ACL_RAW_BIND    6
+#define NETWORK_ACL_RAW_CONNECT 7
+
+#endif

-- 

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

* [TOMOYO #7 07/30] Some wrapper functions for socket operation.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (5 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 06/30] External functions prototypes for TOMOYO Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 08/30] Some of permission checks from VFS helper functions Tetsuo Handa
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada, linux-netdev

[-- Attachment #1: tomoyo-tomoyo-socket-header.patch --]
[-- Type: text/plain, Size: 11378 bytes --]

These functions checks whether TOMOYO can do permission checks or not.

TOMOYO won't do permission checks if the process is a kernel thread
or the process is not permitted to sleep.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
Cc: linux-netdev <netdev@vger.kernel.org>
---
 include/linux/tomoyo_socket.h |  419 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 419 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/tomoyo_socket.h
@@ -0,0 +1,419 @@
+/*
+ * include/linux/tomoyo_socket.h
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#ifndef _LINUX_TOMOYO_SOCKET_H
+#define _LINUX_TOMOYO_SOCKET_H
+
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/udp.h>
+#include <linux/uaccess.h>
+
+#if defined(CONFIG_TOMOYO)
+
+#define false 0
+#define true 1
+
+#define MAX_SOCK_ADDR 128 /* net/socket.c */
+
+/* Check permission for creating a socket. */
+static inline int ccs_socket_create_permission(int family, int type,
+					       int protocol)
+{
+	int error = 0;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	if (family == PF_PACKET && !ccs_capable(TOMOYO_USE_PACKET_SOCKET))
+		return -EPERM;
+	if (family == PF_ROUTE && !ccs_capable(TOMOYO_USE_ROUTE_SOCKET))
+		return -EPERM;
+	if (family != PF_INET && family != PF_INET6)
+		return 0;
+	switch (type) {
+	case SOCK_STREAM:
+		if (!ccs_capable(TOMOYO_INET_STREAM_SOCKET_CREATE))
+			error = -EPERM;
+		break;
+	case SOCK_DGRAM:
+		if (!ccs_capable(TOMOYO_USE_INET_DGRAM_SOCKET))
+			error = -EPERM;
+		break;
+	case SOCK_RAW:
+		if (!ccs_capable(TOMOYO_USE_INET_RAW_SOCKET))
+			error = -EPERM;
+		break;
+	}
+	return error;
+}
+
+/* Check permission for listening a TCP socket. */
+static inline int ccs_socket_listen_permission(struct socket *sock)
+{
+	int error = 0;
+	char addr[MAX_SOCK_ADDR];
+	int addr_len;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	if (sock->type != SOCK_STREAM)
+		return 0;
+	switch (sock->sk->sk_family) {
+	case PF_INET:
+	case PF_INET6:
+		break;
+	default:
+		return 0;
+	}
+	if (!ccs_capable(TOMOYO_INET_STREAM_SOCKET_LISTEN))
+		return -EPERM;
+	if (sock->ops->getname(sock, (struct sockaddr *) addr, &addr_len, 0))
+		return -EPERM;
+	switch (((struct sockaddr *) addr)->sa_family) {
+		struct sockaddr_in6 *addr6;
+		struct sockaddr_in *addr4;
+	case AF_INET6:
+		addr6 = (struct sockaddr_in6 *) addr;
+		error = ccs_check_network_listen_acl(true,
+						     addr6->sin6_addr.s6_addr,
+						     addr6->sin6_port);
+		break;
+	case AF_INET:
+		addr4 = (struct sockaddr_in *) addr;
+		error = ccs_check_network_listen_acl(false,
+						     (u8 *) &addr4->sin_addr,
+						     addr4->sin_port);
+		break;
+	}
+	return error;
+}
+
+/* Check permission for setting the remote IP address/port pair of a socket. */
+static inline int ccs_socket_connect_permission(struct socket *sock,
+						struct sockaddr *addr,
+						int addr_len)
+{
+	int error = 0;
+	const unsigned int type = sock->type;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	switch (type) {
+	case SOCK_STREAM:
+	case SOCK_DGRAM:
+	case SOCK_RAW:
+		break;
+	default:
+		return 0;
+	}
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *addr6;
+		struct sockaddr_in *addr4;
+		u16 port;
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+		addr6 = (struct sockaddr_in6 *) addr;
+		if (type != SOCK_RAW)
+			port = addr6->sin6_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_connect_acl(true, type,
+						      addr6->sin6_addr.s6_addr,
+						      port);
+		break;
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+		addr4 = (struct sockaddr_in *) addr;
+		if (type != SOCK_RAW)
+			port = addr4->sin_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_connect_acl(false, type,
+						      (u8 *) &addr4->sin_addr,
+						      port);
+		break;
+	}
+	if (type != SOCK_STREAM)
+		return error;
+	switch (sock->sk->sk_family) {
+	case PF_INET:
+	case PF_INET6:
+		if (!ccs_capable(TOMOYO_INET_STREAM_SOCKET_CONNECT))
+			error = -EPERM;
+		break;
+	}
+	return error;
+}
+
+/* Check permission for setting the local IP address/port pair of a socket. */
+static inline int ccs_socket_bind_permission(struct socket *sock,
+					     struct sockaddr *addr,
+					     int addr_len)
+{
+	int error = 0;
+	const unsigned int type = sock->type;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	switch (type) {
+	case SOCK_STREAM:
+	case SOCK_DGRAM:
+	case SOCK_RAW:
+		break;
+	default:
+		return 0;
+	}
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *addr6;
+		struct sockaddr_in *addr4;
+		u16 port;
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+		addr6 = (struct sockaddr_in6 *) addr;
+		if (type != SOCK_RAW)
+			port = addr6->sin6_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_bind_acl(true, type,
+						   addr6->sin6_addr.s6_addr,
+						   port);
+		break;
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+		addr4 = (struct sockaddr_in *) addr;
+		if (type != SOCK_RAW)
+			port = addr4->sin_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_bind_acl(false, type,
+						   (u8 *) &addr4->sin_addr,
+						   port);
+		break;
+	}
+	return error;
+}
+
+/*
+ * Check permission for accepting a TCP socket.
+ *
+ * Currently, the LSM hook for this purpose is not provided.
+ */
+static inline int ccs_socket_accept_permission(struct socket *sock,
+					       struct sockaddr *addr)
+{
+	int error = 0;
+	int addr_len;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	switch (sock->sk->sk_family) {
+	case PF_INET:
+	case PF_INET6:
+		break;
+	default:
+		return 0;
+	}
+	error = sock->ops->getname(sock, addr, &addr_len, 2);
+	if (error)
+		return error;
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *addr6;
+		struct sockaddr_in *addr4;
+	case AF_INET6:
+		addr6 = (struct sockaddr_in6 *) addr;
+		error = ccs_check_network_accept_acl(true,
+						     addr6->sin6_addr.s6_addr,
+						     addr6->sin6_port);
+		break;
+	case AF_INET:
+		addr4 = (struct sockaddr_in *) addr;
+		error = ccs_check_network_accept_acl(false,
+						     (u8 *) &addr4->sin_addr,
+						     addr4->sin_port);
+		break;
+	}
+	return error;
+}
+
+/* Check permission for sending a datagram via a UDP or RAW socket. */
+static inline int ccs_socket_sendmsg_permission(struct socket *sock,
+						struct sockaddr *addr,
+						int addr_len)
+{
+	int error = 0;
+	const int type = sock->type;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	if (!addr || (type != SOCK_DGRAM && type != SOCK_RAW))
+		return 0;
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *addr6;
+		struct sockaddr_in *addr4;
+		u16 port;
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+		addr6 = (struct sockaddr_in6 *) addr;
+		if (type == SOCK_DGRAM)
+			port = addr6->sin6_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_sendmsg_acl(true, type,
+						      addr6->sin6_addr.s6_addr,
+						      port);
+		break;
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+		addr4 = (struct sockaddr_in *) addr;
+		if (type == SOCK_DGRAM)
+			port = addr4->sin_port;
+		else
+			port = htons(sock->sk->sk_protocol);
+		error = ccs_check_network_sendmsg_acl(false, type,
+						      (u8 *) &addr4->sin_addr,
+						      port);
+		break;
+	}
+	return error;
+}
+
+/*
+ * Check permission for receiving a datagram via a UDP or RAW socket.
+ *
+ * Currently, the LSM hook for this purpose is not provided.
+ */
+static inline int ccs_socket_recv_datagram_permission(struct sock *sk,
+						      struct sk_buff *skb,
+						      const unsigned int flags)
+{
+	int error = 0;
+	const unsigned int type = sk->sk_type;
+	/* Nothing to do if I didn't receive a datagram. */
+	if (!skb)
+		return 0;
+	/* Nothing to do if I can't sleep. */
+	if (in_atomic())
+		return 0;
+	/* Nothing to do if I am a kernel service. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+	if (type != SOCK_DGRAM && type != SOCK_RAW)
+		return 0;
+
+	switch (sk->sk_family) {
+		struct in6_addr sin6;
+		struct in_addr sin4;
+		u16 port;
+	case PF_INET6:
+		if (type == SOCK_DGRAM) { /* UDP IPv6 */
+			if (skb->protocol == htons(ETH_P_IP)) {
+				ipv6_addr_set(&sin6, 0, 0, htonl(0xffff),
+					      ip_hdr(skb)->saddr);
+			} else {
+				ipv6_addr_copy(&sin6, &ipv6_hdr(skb)->saddr);
+			}
+			port = udp_hdr(skb)->source;
+		} else { /* RAW IPv6 */
+			ipv6_addr_copy(&sin6, &ipv6_hdr(skb)->saddr);
+			port = htons(sk->sk_protocol);
+		}
+		error = ccs_check_network_recvmsg_acl(true, type,
+						      (u8 *) &sin6, port);
+		break;
+	case PF_INET:
+		if (type == SOCK_DGRAM) { /* UDP IPv4 */
+			sin4.s_addr = ip_hdr(skb)->saddr;
+			port = udp_hdr(skb)->source;
+		} else { /* RAW IPv4 */
+			sin4.s_addr = ip_hdr(skb)->saddr;
+			port = htons(sk->sk_protocol);
+		}
+		error = ccs_check_network_recvmsg_acl(false, type,
+						      (u8 *) &sin4, port);
+		break;
+	}
+	if (!error)
+		return 0;
+	lock_sock(sk);
+	/*
+	 * Remove from queue if MSG_PEEK is used so that
+	 * the head message from unwanted source in receive queue will not
+	 * prevent the caller from picking up next message from wanted source
+	 * when the caller is using MSG_PEEK flag for picking up.
+	 */
+	if (flags & MSG_PEEK) {
+		unsigned long cpu_flags;
+		/***** CRITICAL SECTION START *****/
+		spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
+		if (skb == skb_peek(&sk->sk_receive_queue)) {
+			__skb_unlink(skb, &sk->sk_receive_queue);
+			atomic_dec(&skb->users);
+		}
+		spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
+		/***** CRITICAL SECTION END *****/
+	}
+	/* Drop reference count. */
+	skb_free_datagram(sk, skb);
+	release_sock(sk);
+	/* Hope less harmful than -EPERM. */
+	return -EAGAIN;
+}
+
+#else
+
+static inline int ccs_socket_create_permission(int family, int type,
+					       int protocol)
+{
+	return 0;
+}
+static inline int ccs_socket_listen_permission(struct socket *sock)
+{
+	return 0;
+}
+static inline int ccs_socket_connect_permission(struct socket *sock,
+						struct sockaddr *addr,
+						int addr_len)
+{
+	return 0;
+}
+static inline int ccs_socket_bind_permission(struct socket *sock,
+					     struct sockaddr *addr,
+					     int addr_len)
+{
+	return 0;
+}
+static inline int ccs_socket_accept_permission(struct socket *sock,
+					       struct sockaddr *addr)
+{
+	return 0;
+}
+static inline int ccs_socket_sendmsg_permission(struct socket *sock,
+						struct sockaddr *addr,
+						int addr_len)
+{
+	return 0;
+}
+static inline int ccs_socket_recv_datagram_permission(struct sock *sk,
+						      struct sk_buff *skb,
+						      const unsigned int flags)
+{
+	return 0;
+}
+
+#endif
+
+#endif

-- 

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

* [TOMOYO #7 08/30] Some of permission checks from VFS helper functions.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (6 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 07/30] Some wrapper functions for socket operation Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 09/30] Access control part of tamper-proof device filesystem Tetsuo Handa
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada, linux-fsdevel

[-- Attachment #1: tomoyo-tomoyo-vfs-header.patch --]
[-- Type: text/plain, Size: 4993 bytes --]

TOMOYO Linux performs pathname based access control,
but "struct vfsmount" is not passed to VFS helper functions
which is needed for deriving requested pathname.

Since I want to do conventional DAC's permission checks
before TOMOYO Linux's permission checks, I copied some of
permission checks from VFS helper functions.

The side effect of this approach is that DAC permission checks
are done twice. But the approach to pass "struct vfsmount" to
VFS helper functions seems unacceptable.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>
---
 include/linux/tomoyo_vfs.h |  141 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/tomoyo_vfs.h
@@ -0,0 +1,141 @@
+/*
+ * include/linux/tomoyo_vfs.h
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#ifndef _LINUX_TOMOYO_VFS_H
+#define _LINUX_TOMOYO_VFS_H
+
+/*
+ * This file contains copy of some of VFS helper functions.
+ *
+ * Since TOMOYO Linux requires "struct vfsmount" parameter to calculate
+ * an absolute pathname of the requested "struct dentry" parameter
+ * but the VFS helper functions don't receive "struct vfsmount" parameter,
+ * TOMOYO Linux checks permission outside VFS helper functions.
+ * To keep the DAC's permission checks are performed before the
+ * TOMOYO Linux's permission checks are performed, I'm manually inserting
+ * these functions that performs the DAC's permission checks into fs/namei.c.
+ *
+ * The approach to obtain "struct vfsmount" parameter from
+ * the "struct task_struct" doesn't work because it triggers deadlock.
+ */
+
+/*
+ * Permission checks before security_inode_mknod() is called.
+ *
+ * This function is exported because
+ * vfs_mknod() is called from net/unix/af_unix.c.
+ */
+int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode)
+{
+	int error = may_create(dir, dentry, NULL);
+	if (error)
+		return error;
+	if ((S_ISCHR(mode) || S_ISBLK(mode)) && !capable(CAP_MKNOD))
+		return -EPERM;
+	if (!dir->i_op || !dir->i_op->mknod)
+		return -EPERM;
+	return 0;
+}
+EXPORT_SYMBOL(pre_vfs_mknod);
+
+/* Permission checks before security_inode_mkdir() is called. */
+static inline int pre_vfs_mkdir(struct inode *dir, struct dentry *dentry)
+{
+	int error = may_create(dir, dentry, NULL);
+	if (error)
+		return error;
+	if (!dir->i_op || !dir->i_op->mkdir)
+		return -EPERM;
+	return 0;
+}
+
+/* Some of permission checks before security_inode_rmdir() is called. */
+static inline int pre_vfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	int error = may_delete(dir, dentry, 1);
+	if (error)
+		return error;
+	if (!dir->i_op || !dir->i_op->rmdir)
+		return -EPERM;
+	return 0;
+}
+
+/* Some of permission checks before security_inode_unlink() is called. */
+static inline int pre_vfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	int error = may_delete(dir, dentry, 0);
+	if (error)
+		return error;
+	if (!dir->i_op || !dir->i_op->unlink)
+		return -EPERM;
+	return 0;
+}
+
+/* Permission checks before security_inode_link() is called. */
+static inline int pre_vfs_link(struct dentry *old_dentry, struct inode *dir,
+			       struct dentry *new_dentry)
+{
+	struct inode *inode = old_dentry->d_inode;
+	int error;
+	if (!inode)
+		return -ENOENT;
+	error = may_create(dir, new_dentry, NULL);
+	if (error)
+		return error;
+	if (dir->i_sb != inode->i_sb)
+		return -EXDEV;
+	if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
+		return -EPERM;
+	if (!dir->i_op || !dir->i_op->link)
+		return -EPERM;
+	if (S_ISDIR(old_dentry->d_inode->i_mode))
+		return -EPERM;
+	return 0;
+}
+
+/* Permission checks before security_inode_symlink() is called. */
+static inline int pre_vfs_symlink(struct inode *dir, struct dentry *dentry)
+{
+	int error = may_create(dir, dentry, NULL);
+	if (error)
+		return error;
+	if (!dir->i_op || !dir->i_op->symlink)
+		return -EPERM;
+	return 0;
+}
+
+/* Permission checks before security_inode_rename() is called. */
+static inline int pre_vfs_rename(struct inode *old_dir,
+				 struct dentry *old_dentry,
+				 struct inode *new_dir,
+				 struct dentry *new_dentry)
+{
+	int error;
+	const int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	if (old_dentry->d_inode == new_dentry->d_inode)
+		return 0;
+	error = may_delete(old_dir, old_dentry, is_dir);
+	if (error)
+		return error;
+	if (!new_dentry->d_inode)
+		error = may_create(new_dir, new_dentry, NULL);
+	else
+		error = may_delete(new_dir, new_dentry, is_dir);
+	if (error)
+		return error;
+	if (!old_dir->i_op || !old_dir->i_op->rename)
+		return -EPERM;
+	if (is_dir && new_dir != old_dir)
+		error = permission(old_dentry->d_inode, MAY_WRITE, NULL);
+	return error;
+}
+
+#endif

-- 

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

* [TOMOYO #7 09/30] Access control part of tamper-proof device filesystem.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (7 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 08/30] Some of permission checks from VFS helper functions Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 10/30] Common functions for SAKURA and TOMOYO Tetsuo Handa
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-syaoran-header.patch --]
[-- Type: text/plain, Size: 29442 bytes --]

This file uses a bit tricky approach for fetching address of blkdev_open()
and chrdev_open() so that this filesystem can be compiled as a module.
If I'm permitted to add EXPORT_SYMBOL for blkdev_open() and chrdev_open(),
I will directly call them.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/syaoran.h | 1023 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1023 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/include/linux/syaoran.h
@@ -0,0 +1,1023 @@
+/*
+ * include/linux/syaoran.h
+ *
+ * Implementation of the Tamper-Proof Device Filesystem.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+/*
+ * A brief description about SYAORAN:
+ *
+ *  SYAORAN stands for "Simple Yet All-important Object Realizing Abiding
+ *  Nexus". SYAORAN is a filesystem for /dev with Mandatory Access Control.
+ *
+ *  /dev cannot be mounted for read-only mode, but this means that files on
+ *  /dev might be tampered with. In other words, a device file might have
+ *  inappropriate attributes (e.g. /dev/null has char-1-5 attributes).
+ *  SYAORAN can restrict combinations of (pathname, attribute) that
+ *  the system can create so that all files on this filesystem have appropriate
+ *  attributes (e.g. /dev/null has char-1-3 attributes).
+ *
+ *  The attribute is one of directory, regular file, FIFO, UNIX domain socket,
+ *  symbolic link, character or block device file with major/minor device
+ *  numbers.
+ *
+ *  You can use SYAORAN alone, but I recommend you to use SYAORAN
+ *  with SAKURA and TOMOYO.
+ */
+
+#ifndef _LINUX_SYAORAN_H
+#define _LINUX_SYAORAN_H
+
+#define false 0
+#define true 1
+
+/**
+ * list_for_each_cookie - iterate over a list with cookie.
+ * @pos:        the &struct list_head to use as a loop cursor.
+ * @cookie:     the &struct list_head to use as a cookie.
+ * @head:       the head for your list.
+ *
+ * Same with list_for_each except that this primitive uses cookie
+ * so that we can continue iteration.
+ */
+#define list_for_each_cookie(pos, cookie, head)				\
+	for (({ if (!cookie)						\
+				     cookie = head; }), pos = (cookie)->next; \
+	     prefetch(pos->next), pos != (head) || ((cookie) = NULL);	\
+	     (cookie) = pos, pos = pos->next)
+
+/* The following constants are used to restrict operations.*/
+#define MAY_CREATE         1 /* This file is allowed to mknod()              */
+#define MAY_DELETE         2 /* This file is allowed to unlink()             */
+#define MAY_CHMOD          4 /* This file is allowed to chmod()              */
+#define MAY_CHOWN          8 /* This file is allowed to chown()              */
+#define DEVICE_USED       16 /* This block or character device file is used. */
+#define NO_CREATE_AT_MOUNT 32 /* Don't create this file at mount().          */
+
+/* some random number */
+#define SYAORAN_MAGIC    0x2F646576 /* = '/dev' */
+
+static void syaoran_put_super(struct super_block *sb);
+static int syaoran_initialize(struct super_block *sb, void *data);
+static void syaoran_make_initial_nodes(struct super_block *sb);
+static int syaoran_may_create_node(struct dentry *dentry, int mode, int dev);
+static int syaoran_may_modify_node(struct dentry *dentry, unsigned int flags);
+static int syaoran_create_tracelog(struct super_block *sb,
+				   const char *filename);
+
+/* Wraps blkdev_open() to trace open operation for block devices. */
+static int (*org_blkdev_open) (struct inode *inode, struct file *filp);
+static struct file_operations wrapped_def_blk_fops;
+
+static int wrapped_blkdev_open(struct inode *inode, struct file *filp)
+{
+	int error = org_blkdev_open(inode, filp);
+	if (error != -ENXIO)
+		syaoran_may_modify_node(filp->f_dentry, DEVICE_USED);
+	return error;
+}
+
+/* Wraps chrdev_open() to trace open operation for character devices. */
+static int (*org_chrdev_open) (struct inode *inode, struct file *filp);
+static struct file_operations wrapped_def_chr_fops;
+
+static int wrapped_chrdev_open(struct inode *inode, struct file *filp)
+{
+	int error = org_chrdev_open(inode, filp);
+	if (error != -ENXIO)
+		syaoran_may_modify_node(filp->f_dentry, DEVICE_USED);
+	return error;
+}
+
+/* lookup_create() without nameidata. Called only while initialization. */
+static struct dentry *lookup_create2(const char *name, struct dentry *base,
+				     const bool is_dir)
+{
+	struct dentry *dentry;
+	const int len = name ? strlen(name) : 0;
+	mutex_lock(&base->d_inode->i_mutex);
+	dentry = lookup_one_len(name, base, len);
+	if (IS_ERR(dentry))
+		goto fail;
+	if (!is_dir && name[len] && !dentry->d_inode)
+		goto enoent;
+	return dentry;
+ enoent:
+	dput(dentry);
+	dentry = ERR_PTR(-ENOENT);
+ fail:
+	return dentry;
+}
+
+/* mkdir(). Called only while initialization. */
+static int fs_mkdir(const char *pathname, struct dentry *base, int mode,
+		    uid_t user, gid_t group)
+{
+	struct dentry *dentry = lookup_create2(pathname, base, 1);
+	int error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_mkdir(base->d_inode, dentry, mode);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/* mknod(). Called only while initialization. */
+static int fs_mknod(const char *filename, struct dentry *base, int mode,
+		    dev_t dev, uid_t user, gid_t group)
+{
+	struct dentry *dentry;
+	int error;
+	switch (mode & S_IFMT) {
+	case S_IFCHR:
+	case S_IFBLK:
+	case S_IFIFO:
+	case S_IFSOCK:
+	case S_IFREG:
+		break;
+	default:
+		return -EPERM;
+	}
+	dentry = lookup_create2(filename, base, 0);
+	error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_mknod(base->d_inode, dentry, mode, dev);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/* symlink(). Called only while initialization. */
+static int fs_symlink(const char *pathname, struct dentry *base, char *oldname,
+		      int mode, uid_t user, gid_t group)
+{
+	struct dentry *dentry = lookup_create2(pathname, base, 0);
+	int error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		error = vfs_symlink(base->d_inode, dentry, oldname, S_IALLUGO);
+		if (!error) {
+			lock_kernel();
+			dentry->d_inode->i_mode = mode;
+			dentry->d_inode->i_uid = user;
+			dentry->d_inode->i_gid = group;
+			unlock_kernel();
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	return error;
+}
+
+/*
+ * Format string.
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ */
+static void normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/* Convert text form of filename into binary form. */
+static void unescape(char *filename)
+{
+	char *cp = filename;
+	char c;
+	char d;
+	char e;
+	if (!cp)
+		return;
+	while ((c = *filename++) != '\0') {
+		if (c != '\\') {
+			*cp++ = c;
+			continue;
+		}
+		c = *filename++;
+		if (c == '\\') {
+			*cp++ = c;
+			continue;
+		}
+		if (c < '0' || c > '3')
+			break;
+		d = *filename++;
+		if (d < '0' || d > '7')
+			break;
+		e = *filename++;
+		if (e < '0' || e > '7')
+			break;
+		*(unsigned char *) cp++ = (unsigned char)
+			(((unsigned char) (c - '0') << 6)
+			 + ((unsigned char) (d - '0') << 3)
+			 + (unsigned char) (e - '0'));
+	}
+	*cp = '\0';
+}
+
+static char *strdup(const char *str)
+{
+	const int len = str ? strlen(str) + 1 : 0;
+	char *cp = kzalloc(len, GFP_KERNEL);
+	if (cp)
+		memmove(cp, str, len);
+	return cp;
+}
+
+/* -1: Not specified, 0: Enforce by default, 1: Accept by default. */
+static int syaoran_default_mode = -1;
+
+#if !defined(MODULE)
+static int __init syaoran_setup(char *str)
+{
+	if (!strcmp(str, "accept"))
+		syaoran_default_mode = 1;
+	else if (!strcmp(str, "enforce"))
+		syaoran_default_mode = 0;
+	return 0;
+}
+
+__setup("SYAORAN=", syaoran_setup);
+#endif
+
+/* The structure for possible device list. */
+struct dev_entry {
+	struct list_head list;
+	/* Binary form of pathname under mount point. Never NULL. */
+	char *name;
+	/*
+	 * Mode and permissions.
+	 * setuid/setgid/sticky bits are not supported.
+	 */
+	mode_t mode;
+	uid_t uid;
+	gid_t gid;
+	dev_t kdev;
+	/*
+	 * Binary form of initial contents for the symlink. NULL if not symlink.
+	 */
+	char *symlink_data;
+	/* File access control flags. */
+	unsigned int flags;
+	/* Text form of pathname under mount point. Never NULL. */
+	const char *printable_name;
+	/*
+	 * Text form of initial contents for the symlink. NULL if not symlink.
+	 */
+	const char *printable_symlink_data;
+};
+
+struct syaoran_sb_info {
+	struct list_head list;
+	bool initialize_done;     /* False if initialization is in progress. */
+	bool is_permissive_mode;  /* True if permissive mode.                */
+};
+
+static int syaoran_register_node_info(char *buffer, struct super_block *sb)
+{
+	enum {
+		ARG_FILENAME     = 0,
+		ARG_PERMISSION   = 1,
+		ARG_UID          = 2,
+		ARG_GID          = 3,
+		ARG_FLAGS        = 4,
+		ARG_DEV_TYPE     = 5,
+		ARG_SYMLINK_DATA = 6,
+		ARG_DEV_MAJOR    = 6,
+		ARG_DEV_MINOR    = 7,
+		MAX_ARG          = 8
+	};
+	char *args[MAX_ARG];
+	int i;
+	int error = -EINVAL;
+	unsigned int perm;
+	unsigned int uid;
+	unsigned int gid;
+	unsigned int flags;
+	unsigned int major = 0;
+	unsigned int minor = 0;
+	struct syaoran_sb_info *info =
+		(struct syaoran_sb_info *) sb->s_fs_info;
+	struct dev_entry *entry;
+	if (!info)
+		return -EINVAL;
+	memset(args, 0, sizeof(args));
+	args[0] = buffer;
+	for (i = 1; i < MAX_ARG; i++) {
+		args[i] = strchr(args[i - 1] + 1, ' ');
+		if (!args[i])
+			break;
+		*args[i]++ = '\0';
+	}
+	/*
+	  printk(KERN_DEBUG "<%s> <%s> <%s> <%s> <%s> <%s> <%s> <%s>\n",
+	  args[0], args[1], args[2], args[3], args[4], args[5], args[6],
+	  args[7]);
+	*/
+	if (!args[ARG_FILENAME] || !args[ARG_PERMISSION] || !args[ARG_UID] ||
+	    !args[ARG_GID] || !args[ARG_DEV_TYPE] || !args[ARG_FLAGS])
+		goto out;
+	if (sscanf(args[ARG_PERMISSION], "%o", &perm) != 1 ||
+	    !(perm <= 0777) || sscanf(args[ARG_UID], "%u", &uid) != 1 ||
+	    sscanf(args[ARG_GID], "%u", &gid) != 1 ||
+	    sscanf(args[ARG_FLAGS], "%u", &flags) != 1 ||
+	    *(args[ARG_DEV_TYPE] + 1))
+		goto out;
+	switch (*args[ARG_DEV_TYPE]) {
+	case 'c':
+		perm |= S_IFCHR;
+		if (!args[ARG_DEV_MAJOR] ||
+		    sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1 ||
+		    !args[ARG_DEV_MINOR] ||
+		    sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1)
+			goto out;
+		break;
+	case 'b':
+		perm |= S_IFBLK;
+		if (!args[ARG_DEV_MAJOR] ||
+		    sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1 ||
+		    !args[ARG_DEV_MINOR] ||
+		    sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1)
+			goto out;
+		break;
+	case 'l':
+		perm |= S_IFLNK;
+		if (!args[ARG_SYMLINK_DATA])
+			goto out;
+		break;
+	case 'd':
+		perm |= S_IFDIR;
+		break;
+	case 's':
+		perm |= S_IFSOCK;
+		break;
+	case 'p':
+		perm |= S_IFIFO;
+		break;
+	case 'f':
+		perm |= S_IFREG;
+		break;
+	default:
+		goto out;
+	}
+	error = -ENOMEM;
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		goto out;
+	if (S_ISLNK(perm)) {
+		entry->printable_symlink_data = strdup(args[ARG_SYMLINK_DATA]);
+		if (!entry->printable_symlink_data)
+			goto out_freemem;
+	}
+	entry->printable_name = strdup(args[ARG_FILENAME]);
+	if (!entry->printable_name)
+		goto out_freemem;
+	if (S_ISLNK(perm)) {
+		entry->symlink_data = strdup(entry->printable_symlink_data);
+		if (!entry->symlink_data)
+			goto out_freemem;
+		unescape(entry->symlink_data);
+	}
+	entry->name = strdup(entry->printable_name);
+	if (!entry->name)
+		goto out_freemem;
+	unescape(entry->name);
+	{
+		/*
+		 * Drop trailing '/', for get_local_absolute_path() doesn't
+		 * append trailing '/'.
+		 */
+		const int len = strlen(entry->name);
+		if (len && entry->name[len - 1] == '/')
+			entry->name[len - 1] = '\0';
+	}
+	entry->mode = perm;
+	entry->uid = uid;
+	entry->gid = gid;
+	entry->kdev = S_ISCHR(perm) || S_ISBLK(perm) ? MKDEV(major, minor) : 0;
+	entry->flags = flags;
+	list_add_tail(&entry->list, &info->list);
+	/* printk(KERN_DEBUG "Entry added.\n"); */
+	error = 0;
+ out:
+	return error;
+ out_freemem:
+	kfree(entry->printable_symlink_data);
+	kfree(entry->printable_name);
+	kfree(entry->symlink_data);
+	kfree(entry);
+	goto out;
+}
+
+static void syaoran_put_super(struct super_block *sb)
+{
+	struct syaoran_sb_info *info;
+	struct dev_entry *entry;
+	struct dev_entry *tmp;
+	if (!sb)
+		return;
+	info = (struct syaoran_sb_info *) sb->s_fs_info;
+	if (!info)
+		return;
+	sb->s_fs_info = NULL;
+	list_for_each_entry_safe(entry, tmp, &info->list, list) {
+		kfree(entry->name);
+		kfree(entry->symlink_data);
+		kfree(entry->printable_name);
+		kfree(entry->printable_symlink_data);
+		list_del(&entry->list);
+		/* printk(KERN_DEBUG "Entry removed.\n"); */
+		kfree(entry);
+	}
+	kfree(info);
+	printk(KERN_INFO "%s: Unused memory freed.\n", __func__);
+}
+
+static int syaoran_read_config_file(struct file *file, struct super_block *sb)
+{
+	char *buffer;
+	int len;
+	char *cp;
+	unsigned long offset = 0;
+	int error = -ENOMEM;
+	if (!file)
+		return -EINVAL;
+	buffer = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buffer)
+		goto out;
+	while ((len = kernel_read(file, offset, buffer, PAGE_SIZE)) > 0 &&
+	       (cp = memchr(buffer, '\n', len)) != NULL) {
+		*cp = '\0';
+		offset += cp - buffer + 1;
+		normalize_line(buffer);
+		if (syaoran_register_node_info(buffer, sb) == -ENOMEM)
+			goto out;
+	}
+	error = 0;
+ out:
+	kfree(buffer);
+	return error;
+}
+
+static void syaoran_make_node(struct dev_entry *entry, struct dentry *root)
+{
+	struct dentry *base = dget(root);
+	char *filename = entry->name;
+	char *name = filename;
+	unsigned int c;
+	const mode_t perm = entry->mode;
+	const uid_t uid = entry->uid;
+	const gid_t gid = entry->gid;
+	goto start;
+	while ((c = *(unsigned char *) filename) != '\0') {
+		if (c == '/') {
+			struct dentry *new_base;
+			const int len = filename - name;
+			*filename = '\0';
+			mutex_lock(&base->d_inode->i_mutex);
+			new_base = lookup_one_len(name, base, len);
+			mutex_unlock(&base->d_inode->i_mutex);
+			dput(base);
+			*filename++ = '/';
+			if (IS_ERR(new_base)) {
+				/*
+				  printk(KERN_DEBUG "'%s' = %ld\n", entry->name,
+				  PTR_ERR(new_base));
+				*/
+				return;
+			} else if (!new_base->d_inode ||
+				   !S_ISDIR(new_base->d_inode->i_mode)) {
+				/*
+				  printk(KERN_DEBUG
+				  "Directory '%s' does not exist.\n",
+				  entry->name);
+				*/
+				dput(new_base);
+				return;
+			}
+			/*
+			  printk(KERN_DEBUG "Directory '%s' exists.\n",
+			  entry->name);
+			*/
+			base = new_base;
+ start:
+			name = filename;
+		} else {
+			filename++;
+		}
+	}
+	filename = (char *) name;
+	if (S_ISLNK(perm))
+		fs_symlink(filename, base, entry->symlink_data, perm, uid, gid);
+	else if (S_ISDIR(perm))
+		fs_mkdir(filename, base, perm ^ S_IFDIR, uid, gid);
+	else if (S_ISSOCK(perm) || S_ISFIFO(perm) || S_ISREG(perm))
+		fs_mknod(filename, base, perm, 0, uid, gid);
+	else if (S_ISCHR(perm) || S_ISBLK(perm))
+		fs_mknod(filename, base, perm, entry->kdev, uid, gid);
+	dput(base);
+}
+
+/* Create files according to the policy file. */
+static void syaoran_make_initial_nodes(struct super_block *sb)
+{
+	struct syaoran_sb_info *info;
+	struct dev_entry *entry;
+	if (!sb)
+		return;
+	info = (struct syaoran_sb_info *) sb->s_fs_info;
+	if (!info)
+		return;
+	if (info->is_permissive_mode) {
+		syaoran_create_tracelog(sb, ".syaoran");
+		syaoran_create_tracelog(sb, ".syaoran_all");
+	}
+	list_for_each_entry(entry, &info->list, list) {
+		if ((entry->flags & NO_CREATE_AT_MOUNT) == 0)
+			syaoran_make_node(entry, sb->s_root);
+	}
+	info->initialize_done = true;
+}
+
+/* Read policy file. */
+static int syaoran_initialize(struct super_block *sb, void *data)
+{
+	int error = -EINVAL;
+	struct file *f;
+	char *filename = (char *) data;
+	bool is_permissive_mode = syaoran_default_mode;
+	static bool first = true;
+	if (first) {
+		first = false;
+		printk(KERN_INFO "SYAORAN: 1.6.0   2008/04/01\n");
+	}
+	{
+		struct inode *inode = new_inode(sb);
+		if (!inode)
+			return -EINVAL;
+		/* Create /dev/ram0 to get the value of blkdev_open(). */
+		init_special_inode(inode, S_IFBLK | 0666, MKDEV(1, 0));
+		wrapped_def_blk_fops = *inode->i_fop;
+		iput(inode);
+		org_blkdev_open = wrapped_def_blk_fops.open;
+		wrapped_def_blk_fops.open = wrapped_blkdev_open;
+	}
+	{
+		struct inode *inode = new_inode(sb);
+		if (!inode)
+			return -EINVAL;
+		/* Create /dev/null to get the value of chrdev_open(). */
+		init_special_inode(inode, S_IFCHR | 0666, MKDEV(1, 3));
+		wrapped_def_chr_fops = *inode->i_fop;
+		iput(inode);
+		org_chrdev_open = wrapped_def_chr_fops.open;
+		wrapped_def_chr_fops.open = wrapped_chrdev_open;
+	}
+	if (!filename) {
+		printk(KERN_WARNING "SYAORAN: Missing config-file path.\n");
+		return -EINVAL;
+	}
+	/* If mode is given with mount operation, use it. */
+	if (!strncmp(filename, "accept=", 7)) {
+		filename += 7;
+		is_permissive_mode = true;
+	} else if (!strncmp(filename, "enforce=", 8)) {
+		filename += 8;
+		is_permissive_mode = false;
+	} else if (syaoran_default_mode == -1) {
+		/*
+		 * If mode is not given with command line,
+		 * abort mount.
+		 */
+		printk(KERN_WARNING
+		       "SYAORAN: Missing 'accept=' or 'enforce='.\n");
+		return -EINVAL;
+	}
+	f = filp_open(filename, O_RDONLY, 0600);
+	if (IS_ERR(f)) {
+		printk(KERN_WARNING "SYAORAN: Can't open '%s'\n", filename);
+		return -EINVAL;
+	}
+	if (!S_ISREG(f->f_dentry->d_inode->i_mode))
+		goto out;
+	sb->s_fs_info = kzalloc(sizeof(struct syaoran_sb_info), GFP_KERNEL);
+	if (!sb->s_fs_info)
+		goto out;
+	((struct syaoran_sb_info *) sb->s_fs_info)->is_permissive_mode
+		= is_permissive_mode;
+	INIT_LIST_HEAD(&((struct syaoran_sb_info *) sb->s_fs_info)->list);
+	printk(KERN_INFO "SYAORAN: Reading '%s'\n", filename);
+	error = syaoran_read_config_file(f, sb);
+ out:
+	if (error)
+		printk(KERN_WARNING "SYAORAN: Can't read '%s'\n", filename);
+	filp_close(f, NULL);
+	return error;
+}
+
+/* Get absolute pathname from mount point. */
+static int get_local_absolute_path(struct dentry *dentry, char *buffer,
+				   int buflen)
+{
+	/***** CRITICAL SECTION START *****/
+	char *start = buffer;
+	char *end = buffer + buflen;
+	int namelen;
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+	for (;;) {
+		struct dentry *parent;
+		if (IS_ROOT(dentry))
+			break;
+		parent = dentry->d_parent;
+		namelen = dentry->d_name.len;
+		buflen -= namelen + 1;
+		if (buflen < 0)
+			goto out;
+		end -= namelen;
+		memcpy(end, dentry->d_name.name, namelen);
+		*--end = '/';
+		dentry = parent;
+	}
+	if (*end == '/') {
+		buflen++;
+		end++;
+	}
+	namelen = dentry->d_name.len;
+	buflen -= namelen;
+	if (buflen < 0)
+		goto out;
+	end -= namelen;
+	memcpy(end, dentry->d_name.name, namelen);
+	memmove(start, end, strlen(end) + 1);
+	return 0;
+ out:
+	return -ENOMEM;
+	/***** CRITICAL SECTION END *****/
+}
+
+/* Get absolute pathname of the given dentry from mount point. */
+static int local_ccs_realpath_from_dentry(struct dentry *dentry, char *newname,
+					  int newname_len)
+{
+	/***** CRITICAL SECTION START *****/
+	int error;
+	struct dentry *d_dentry;
+	if (!dentry || !newname || newname_len <= 0)
+		return -EINVAL;
+	d_dentry = dget(dentry);
+	spin_lock(&dcache_lock);
+	error = get_local_absolute_path(d_dentry, newname, newname_len);
+	spin_unlock(&dcache_lock);
+	dput(d_dentry);
+	return error;
+	/***** CRITICAL SECTION END *****/
+}
+
+static int syaoran_check_flags(struct syaoran_sb_info *info,
+			       struct dentry *dentry,
+			       int mode, int dev, unsigned int flags)
+{
+	int error;
+	/*
+	 * I use static buffer, for local_ccs_realpath_from_dentry() needs
+	 * dcache_lock.
+	 */
+	static char filename[PAGE_SIZE];
+	static DEFINE_SPINLOCK(lock);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&lock);
+	memset(filename, 0, sizeof(filename));
+	error = local_ccs_realpath_from_dentry(dentry, filename,
+					       sizeof(filename) - 1);
+	if (!error) {
+		struct dev_entry *entry;
+		error = -EPERM;
+		list_for_each_entry(entry, &info->list, list) {
+			if ((mode & S_IFMT) != (entry->mode & S_IFMT))
+				continue;
+			if ((S_ISBLK(mode) || S_ISCHR(mode)) &&
+			    dev != entry->kdev)
+				continue;
+			if (strcmp(entry->name, filename + 1))
+				continue;
+			if (info->is_permissive_mode) {
+				entry->flags |= flags;
+				error = 0;
+			} else if ((entry->flags & flags) == flags)
+				error = 0;
+			break;
+		}
+	}
+	if (!error) {
+		const char *name;
+		struct task_struct *task = current;
+		const uid_t uid = task->fsuid;
+		const gid_t gid = task->fsgid;
+		const mode_t perm = mode & 0777;
+		flags &= ~DEVICE_USED;
+		{
+			char *end = filename + sizeof(filename) - 1;
+			const char *cp = strchr(filename, '\0') - 1;
+			while (cp > filename && end > cp &&
+			       end > filename + 16) {
+				const unsigned char c = *cp--;
+				if (c == '\\') {
+					*--end = '\\';
+					*--end = '\\';
+				} else if (c > ' ' && c < 127) {
+					*--end = c;
+				} else {
+					*--end = (c & 7) + '0';
+					*--end = ((c >> 3) & 7) + '0';
+					*--end = (c >> 6) + '0';
+					*--end = '\\';
+				}
+			}
+			name = end;
+		}
+		switch (mode & S_IFMT) {
+		case S_IFCHR:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n",
+			       name, perm, uid, gid, flags, 'c',
+			       MAJOR(dev), MINOR(dev));
+			break;
+		case S_IFBLK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n",
+			       name, perm, uid, gid, flags, 'b',
+			       MAJOR(dev), MINOR(dev));
+			break;
+		case S_IFIFO:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name,
+			       perm, uid, gid, flags, 'p');
+			break;
+		case S_IFSOCK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name,
+			       perm, uid, gid, flags, 's');
+			break;
+		case S_IFDIR:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name,
+			       perm, uid, gid, flags, 'd');
+			break;
+		case S_IFLNK:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c %s\n",
+			       name, perm, uid, gid, flags, 'l', "unknown");
+			break;
+		case S_IFREG:
+			printk(KERN_DEBUG
+			       "SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name,
+			       perm, uid, gid, flags, 'f');
+			break;
+		}
+	}
+	spin_unlock(&lock);
+	/***** CRITICAL SECTION END *****/
+	return error;
+}
+
+/* Check whether the given dentry is allowed to mknod. */
+static int syaoran_may_create_node(struct dentry *dentry, int mode, int dev)
+{
+	struct syaoran_sb_info *info
+		= (struct syaoran_sb_info *) dentry->d_sb->s_fs_info;
+	if (!info) {
+		printk(KERN_WARNING "%s: dentry->d_sb->s_fs_info == NULL\n",
+		       __func__);
+		return -EPERM;
+	}
+	if (!info->initialize_done)
+		return 0;
+	return syaoran_check_flags(info, dentry, mode, dev, MAY_CREATE);
+}
+
+/* Check whether the given dentry is allowed to chmod/chown/unlink. */
+static int syaoran_may_modify_node(struct dentry *dentry, unsigned int flags)
+{
+	struct syaoran_sb_info *info
+		= (struct syaoran_sb_info *) dentry->d_sb->s_fs_info;
+	if (!info) {
+		printk(KERN_WARNING "%s: dentry->d_sb->s_fs_info == NULL\n",
+		       __func__);
+		return -EPERM;
+	}
+	if (flags == DEVICE_USED && !info->is_permissive_mode)
+		return 0;
+	if (!dentry->d_inode)
+		return -ENOENT;
+	return syaoran_check_flags(info, dentry, dentry->d_inode->i_mode,
+				   dentry->d_inode->i_rdev, flags);
+}
+
+/*
+ * The following structure and codes are used for transferring data
+ * to interfaces files.
+ */
+
+struct syaoran_read_struct {
+	char *buf;               /* Buffer for reading.                */
+	int avail;               /* Bytes available for reading.       */
+	struct super_block *sb;  /* The super_block of this partition. */
+	struct dev_entry *entry; /* The entry currently reading from.  */
+	bool read_all;           /* Print all entries?                 */
+	struct list_head *pos;   /* Current position.                  */
+};
+
+static void syaoran_read_table(struct syaoran_read_struct *head, char *buf,
+			       int count)
+{
+	struct super_block *sb = head->sb;
+	struct syaoran_sb_info *info = (struct syaoran_sb_info *) sb->s_fs_info;
+	struct list_head *pos;
+	const bool read_all = head->read_all;
+	if (!info)
+		return;
+	if (!head->pos)
+		return;
+	list_for_each_cookie(pos, head->pos, &info->list) {
+		struct dev_entry *entry
+			= list_entry(pos, struct dev_entry, list);
+		const unsigned int flags
+			= read_all ? entry->flags : entry->flags & ~DEVICE_USED;
+		const char *name = entry->printable_name;
+		const uid_t uid = entry->uid;
+		const gid_t gid = entry->gid;
+		const mode_t perm = entry->mode & 0777;
+		int len = 0;
+		switch (entry->mode & S_IFMT) {
+		case S_IFCHR:
+			if (!head->read_all && !(entry->flags & DEVICE_USED))
+				break;
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %3u %3u\n",
+				       name, perm, uid, gid, flags, 'c',
+				       MAJOR(entry->kdev), MINOR(entry->kdev));
+			break;
+		case S_IFBLK:
+			if (!head->read_all && !(entry->flags & DEVICE_USED))
+				break;
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %3u %3u\n",
+				       name, perm, uid, gid, flags, 'b',
+				       MAJOR(entry->kdev), MINOR(entry->kdev));
+			break;
+		case S_IFIFO:
+			len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'p');
+			break;
+		case S_IFSOCK:
+			len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 's');
+			break;
+		case S_IFDIR:
+			len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'd');
+			break;
+		case S_IFLNK:
+			len = snprintf(buf, count,
+				       "%-20s %3o %3u %3u %2u %c %s\n", name,
+				       perm, uid, gid, flags, 'l',
+				       entry->printable_symlink_data);
+			break;
+		case S_IFREG:
+			len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n",
+				       name, perm, uid, gid, flags, 'f');
+			break;
+		}
+		if (len < 0 || count <= len)
+			break;
+		count -= len;
+		buf += len;
+		head->avail += len;
+	}
+}
+
+static int syaoran_trace_open(struct inode *inode, struct file *file)
+{
+	struct syaoran_read_struct *head = kzalloc(sizeof(*head), GFP_KERNEL);
+	if (!head)
+		return -ENOMEM;
+	head->sb = inode->i_sb;
+	head->read_all
+		= (strcmp(file->f_dentry->d_name.name, ".syaoran_all") == 0);
+	head->pos = &((struct syaoran_sb_info *) head->sb->s_fs_info)->list;
+	/* Don't allow open() after unmount() */
+	if (head->sb->s_fs_info)
+		head->buf = kzalloc(PAGE_SIZE * 2, GFP_KERNEL);
+	if (!head->buf) {
+		kfree(head);
+		return -ENOMEM;
+	}
+	file->private_data = head;
+	return 0;
+}
+
+static int syaoran_trace_release(struct inode *inode, struct file *file)
+{
+	struct syaoran_read_struct *head = file->private_data;
+	kfree(head->buf);
+	kfree(head);
+	file->private_data = NULL;
+	return 0;
+}
+
+static ssize_t syaoran_trace_read(struct file *file, char __user *buf,
+				  size_t count, loff_t *ppos)
+{
+	struct syaoran_read_struct *head
+		= (struct syaoran_read_struct *) file->private_data;
+	int len = head->avail;
+	char *cp = head->buf;
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+	syaoran_read_table(head, cp + len, PAGE_SIZE * 2 - len);
+	len = head->avail;
+	if (len > count)
+		len = count;
+	if (len > 0) {
+		if (copy_to_user(buf, cp, len))
+			return -EFAULT;
+		head->avail -= len;
+		memmove(cp, cp + len, head->avail);
+	}
+	return len;
+}
+
+static struct file_operations syaoran_trace_operations = {
+	.open    = syaoran_trace_open,
+	.release = syaoran_trace_release,
+	.read    = syaoran_trace_read,
+};
+
+/* Create interface files for reading status. */
+static int syaoran_create_tracelog(struct super_block *sb, const char *filename)
+{
+	struct dentry *base = dget(sb->s_root);
+	struct dentry *dentry = lookup_create2(filename, base, 0);
+	int error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		struct inode *inode = new_inode(sb);
+		if (inode) {
+			inode->i_mode = S_IFREG | 0400;
+			inode->i_uid = 0;
+			inode->i_gid = 0;
+			inode->i_blocks = 0;
+			inode->i_mapping->a_ops = &syaoran_aops;
+			inode->i_mapping->backing_dev_info
+				= &syaoran_backing_dev_info;
+			inode->i_op = &syaoran_file_inode_operations;
+			inode->i_ctime = CURRENT_TIME;
+			inode->i_mtime = inode->i_ctime;
+			inode->i_atime = inode->i_mtime;
+			inode->i_fop = &syaoran_trace_operations;
+			d_instantiate(dentry, inode);
+			dget(dentry); /* Extra count - pin the dentry in core */
+			error = 0;
+		}
+		dput(dentry);
+	}
+	mutex_unlock(&base->d_inode->i_mutex);
+	dput(base);
+	return error;
+}
+
+#endif

-- 

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

* [TOMOYO #7 10/30] Common functions for SAKURA and TOMOYO.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (8 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 09/30] Access control part of tamper-proof device filesystem Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 11/30] /proc/ccs/ interface for policy management Tetsuo Handa
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-common.patch --]
[-- Type: text/plain, Size: 84402 bytes --]

This file contains common functions (e.g. policy I/O, pattern matching).

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/ccs_common.c | 3061 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 3061 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/ccs_common.c
@@ -0,0 +1,3061 @@
+/*
+ * fs/ccs_common.c
+ *
+ * Common functions for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <stdarg.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/realpath.h>
+#include <linux/ccs_common.h>
+#include <linux/ccs_proc.h>
+#include <linux/tomoyo.h>
+
+/* Set default specified by the kernel config. */
+#define MAX_ACCEPT_ENTRY (CONFIG_TOMOYO_MAX_ACCEPT_ENTRY)
+#define MAX_GRANT_LOG    (CONFIG_TOMOYO_MAX_GRANT_LOG)
+#define MAX_REJECT_LOG   (CONFIG_TOMOYO_MAX_REJECT_LOG)
+
+/* Has /sbin/init started? */
+bool sbin_init_started = false;
+
+/* Log level for SAKURA's printk(). */
+const char *ccs_log_level = KERN_DEBUG;
+
+/* String table for functionality that takes 4 modes. */
+static const char *mode_4[4] = {
+	"disabled", "learning", "permissive", "enforcing"
+};
+/* String table for functionality that takes 2 modes. */
+static const char *mode_2[4] = {
+	"disabled", "enabled", "enabled", "enabled"
+};
+
+/* Table for profile. */
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const unsigned int max_value;
+} ccs_control_array[CCS_MAX_CONTROL_INDEX] = {
+	[CCS_TOMOYO_MAC_FOR_FILE]        = { "MAC_FOR_FILE",        0, 3 },
+	[CCS_TOMOYO_MAC_FOR_ARGV0]       = { "MAC_FOR_ARGV0",       0, 3 },
+	[CCS_TOMOYO_MAC_FOR_ENV]         = { "MAC_FOR_ENV",         0, 3 },
+	[CCS_TOMOYO_MAC_FOR_NETWORK]     = { "MAC_FOR_NETWORK",     0, 3 },
+	[CCS_TOMOYO_MAC_FOR_SIGNAL]      = { "MAC_FOR_SIGNAL",      0, 3 },
+	[CCS_SAKURA_DENY_CONCEAL_MOUNT]  = { "DENY_CONCEAL_MOUNT",  0, 3 },
+	[CCS_SAKURA_RESTRICT_CHROOT]     = { "RESTRICT_CHROOT",     0, 3 },
+	[CCS_SAKURA_RESTRICT_MOUNT]      = { "RESTRICT_MOUNT",      0, 3 },
+	[CCS_SAKURA_RESTRICT_UNMOUNT]    = { "RESTRICT_UNMOUNT",    0, 3 },
+	[CCS_SAKURA_RESTRICT_PIVOT_ROOT] = { "RESTRICT_PIVOT_ROOT", 0, 3 },
+	[CCS_SAKURA_RESTRICT_AUTOBIND]   = { "RESTRICT_AUTOBIND",   0, 1 },
+	[CCS_TOMOYO_MAX_ACCEPT_ENTRY]
+	= { "MAX_ACCEPT_ENTRY",    MAX_ACCEPT_ENTRY, INT_MAX },
+	[CCS_TOMOYO_MAX_GRANT_LOG]
+	= { "MAX_GRANT_LOG",       MAX_GRANT_LOG, INT_MAX },
+	[CCS_TOMOYO_MAX_REJECT_LOG]
+	= { "MAX_REJECT_LOG",      MAX_REJECT_LOG, INT_MAX },
+	[CCS_TOMOYO_VERBOSE]             = { "TOMOYO_VERBOSE",      1, 1 },
+	[CCS_ALLOW_ENFORCE_GRACE]        = { "ALLOW_ENFORCE_GRACE", 0, 1 },
+	[CCS_SLEEP_PERIOD]
+	= { "SLEEP_PERIOD",        0, 3000 }, /* in 0.1 second */
+};
+
+#ifdef CONFIG_TOMOYO
+/* Capability name used by domain policy. */
+const char *capability_control_keyword[TOMOYO_MAX_CAPABILITY_INDEX] = {
+	[TOMOYO_INET_STREAM_SOCKET_CREATE]  = "inet_tcp_create",
+	[TOMOYO_INET_STREAM_SOCKET_LISTEN]  = "inet_tcp_listen",
+	[TOMOYO_INET_STREAM_SOCKET_CONNECT] = "inet_tcp_connect",
+	[TOMOYO_USE_INET_DGRAM_SOCKET]      = "use_inet_udp",
+	[TOMOYO_USE_INET_RAW_SOCKET]        = "use_inet_ip",
+	[TOMOYO_USE_ROUTE_SOCKET]           = "use_route",
+	[TOMOYO_USE_PACKET_SOCKET]          = "use_packet",
+	[TOMOYO_SYS_MOUNT]                  = "SYS_MOUNT",
+	[TOMOYO_SYS_UMOUNT]                 = "SYS_UMOUNT",
+	[TOMOYO_SYS_REBOOT]                 = "SYS_REBOOT",
+	[TOMOYO_SYS_CHROOT]                 = "SYS_CHROOT",
+	[TOMOYO_SYS_KILL]                   = "SYS_KILL",
+	[TOMOYO_SYS_VHANGUP]                = "SYS_VHANGUP",
+	[TOMOYO_SYS_SETTIME]                = "SYS_TIME",
+	[TOMOYO_SYS_NICE]                   = "SYS_NICE",
+	[TOMOYO_SYS_SETHOSTNAME]            = "SYS_SETHOSTNAME",
+	[TOMOYO_USE_KERNEL_MODULE]          = "use_kernel_module",
+	[TOMOYO_CREATE_FIFO]                = "create_fifo",
+	[TOMOYO_CREATE_BLOCK_DEV]           = "create_block_dev",
+	[TOMOYO_CREATE_CHAR_DEV]            = "create_char_dev",
+	[TOMOYO_CREATE_UNIX_SOCKET]         = "create_unix_socket",
+	[TOMOYO_SYS_LINK]                   = "SYS_LINK",
+	[TOMOYO_SYS_SYMLINK]                = "SYS_SYMLINK",
+	[TOMOYO_SYS_RENAME]                 = "SYS_RENAME",
+	[TOMOYO_SYS_UNLINK]                 = "SYS_UNLINK",
+	[TOMOYO_SYS_CHMOD]                  = "SYS_CHMOD",
+	[TOMOYO_SYS_CHOWN]                  = "SYS_CHOWN",
+	[TOMOYO_SYS_IOCTL]                  = "SYS_IOCTL",
+	[TOMOYO_SYS_KEXEC_LOAD]             = "SYS_KEXEC_LOAD",
+	[TOMOYO_SYS_PIVOT_ROOT]             = "SYS_PIVOT_ROOT",
+	[TOMOYO_SYS_PTRACE]                 = "SYS_PTRACE",
+};
+#endif
+
+/* Profile table. Memory is allocated as needed. */
+static struct profile {
+	unsigned int value[CCS_MAX_CONTROL_INDEX];
+	const struct path_info *comment;
+#ifdef CONFIG_TOMOYO
+	unsigned char capability_value[TOMOYO_MAX_CAPABILITY_INDEX];
+#endif
+} *profile_ptr[MAX_PROFILES];
+
+/* Permit policy management by non-root user? */
+static bool manage_by_non_root = false;
+
+/* Utility functions. */
+
+#ifdef CONFIG_TOMOYO
+/**
+ * tomoyo_quiet_setup - Set TOMOYO_VERBOSE=0 by default.
+ *
+ * @str: Unused.
+ *
+ * Returns 0.
+ */
+static int __init tomoyo_quiet_setup(char *str)
+{
+	ccs_control_array[CCS_TOMOYO_VERBOSE].current_value = 0;
+	return 0;
+}
+
+__setup("TOMOYO_QUIET", tomoyo_quiet_setup);
+#endif
+
+/**
+ * is_byte_range - Check whether the string isa \ooo style octal value.
+ *
+ * @str: Pointer to the string.
+ *
+ * Returns true if @str is a \ooo style octal value, false otherwise.
+ */
+static bool is_byte_range(const char *str)
+{
+	return *str >= '0' && *str++ <= '3' &&
+		*str >= '0' && *str++ <= '7' &&
+		*str >= '0' && *str <= '7';
+}
+
+/**
+ * is_decimal - Check whether the character is a decimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a decimal character, false otherwise.
+ */
+static bool is_decimal(const char c)
+{
+	return (c >= '0' && c <= '9');
+}
+
+/**
+ * is_hexadecimal - Check whether the character is a hexadecimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a hexadecimal character, false otherwise.
+ */
+static bool is_hexadecimal(const char c)
+{
+	return ((c >= '0' && c <= '9') ||
+		(c >= 'A' && c <= 'F') ||
+		(c >= 'a' && c <= 'f'));
+}
+
+/**
+ * is_alphabet_char - Check whether the character is an alphabet.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is an alphabet character, false otherwise.
+ */
+static bool is_alphabet_char(const char c)
+{
+	return ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'));
+}
+
+/**
+ * make_byte - Make byte value from three octal characters.
+ *
+ * @c1: The first character.
+ * @c2: The second character.
+ * @c3: The third character.
+ *
+ * Returns byte value.
+ */
+static u8 make_byte(const u8 c1, const u8 c2, const u8 c3)
+{
+	return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
+}
+
+/**
+ * str_starts - Check whether the given string starts with the given keyword.
+ *
+ * @src:  Pointer to pointer to the string.
+ * @find: Pointer to the keyword.
+ *
+ * Returns true if @src starts with @find, false otherwise.
+ *
+ * The @src is updated to point the first character after the @find
+ * if @src starts with @find.
+ */
+static bool str_starts(char **src, const char *find)
+{
+	const int len = strlen(find);
+	char *tmp = *src;
+	if (strncmp(tmp, find, len))
+		return false;
+	tmp += len;
+	*src = tmp;
+	return true;
+}
+
+/**
+ * normalize_line - Format string.
+ *
+ * @buffer: The line to normalize.
+ *
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ *
+ * Returns nothing.
+ */
+static void normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/**
+ * ccs_is_correct_path - Validate a pathname.
+ * @filename:     The pathname to check.
+ * @start_type:   Should the pathname start with '/'?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @pattern_type: Can the pathname contain a wildcard?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @end_type:     Should the pathname end with '/'?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @function:     The name of function calling me.
+ *
+ * Check whether the given filename follows the naming rules.
+ * Returns true if @filename follows the naming rules, false otherwise.
+ */
+bool ccs_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function)
+{
+	bool contains_pattern = false;
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *original_filename = filename;
+	if (!filename)
+		goto out;
+	c = *filename;
+	if (start_type == 1) { /* Must start with '/' */
+		if (c != '/')
+			goto out;
+	} else if (start_type == -1) { /* Must not start with '/' */
+		if (c == '/')
+			goto out;
+	}
+	if (c)
+		c = *(strchr(filename, '\0') - 1);
+	if (end_type == 1) { /* Must end with '/' */
+		if (c != '/')
+			goto out;
+	} else if (end_type == -1) { /* Must not end with '/' */
+		if (c == '/')
+			goto out;
+	}
+	while ((c = *filename++) != '\0') {
+		if (c == '\\') {
+			switch ((c = *filename++)) {
+			case '\\':  /* "\\" */
+				continue;
+			case '$':   /* "\$" */
+			case '+':   /* "\+" */
+			case '?':   /* "\?" */
+			case '*':   /* "\*" */
+			case '@':   /* "\@" */
+			case 'x':   /* "\x" */
+			case 'X':   /* "\X" */
+			case 'a':   /* "\a" */
+			case 'A':   /* "\A" */
+			case '-':   /* "\-" */
+				if (pattern_type == -1)
+					break; /* Must not contain pattern */
+				contains_pattern = true;
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				d = *filename++;
+				if (d < '0' || d > '7')
+					break;
+				e = *filename++;
+				if (e < '0' || e > '7')
+					break;
+				c = make_byte(c, d, e);
+				if (c && (c <= ' ' || c >= 127))
+					continue; /* pattern is not \000 */
+			}
+			goto out;
+		} else if (c <= ' ' || c >= 127) {
+			goto out;
+		}
+	}
+	if (pattern_type == 1) { /* Must contain pattern */
+		if (!contains_pattern)
+			goto out;
+	}
+	return true;
+ out:
+	printk(KERN_DEBUG "%s: Invalid pathname '%s'\n", function,
+	       original_filename);
+	return false;
+}
+
+/**
+ * ccs_is_correct_domain - Check whether the given domainname follows the naming rules.
+ * @domainname:   The domainname to check.
+ * @function:     The name of function calling me.
+ *
+ * Returns true if @domainname follows the naming rules, false otherwise.
+ */
+bool ccs_is_correct_domain(const unsigned char *domainname,
+			   const char *function)
+{
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *org_domainname = domainname;
+	if (!domainname || strncmp(domainname, ROOT_NAME, ROOT_NAME_LEN))
+		goto out;
+	domainname += ROOT_NAME_LEN;
+	if (!*domainname)
+		return true;
+	do {
+		if (*domainname++ != ' ')
+			goto out;
+		if (*domainname++ != '/')
+			goto out;
+		while ((c = *domainname) != '\0' && c != ' ') {
+			domainname++;
+			if (c == '\\') {
+				c = *domainname++;
+				switch ((c)) {
+				case '\\':  /* "\\" */
+					continue;
+				case '0':   /* "\ooo" */
+				case '1':
+				case '2':
+				case '3':
+					d = *domainname++;
+					if (d < '0' || d > '7')
+						break;
+					e = *domainname++;
+					if (e < '0' || e > '7')
+						break;
+					c = make_byte(c, d, e);
+					if (c && (c <= ' ' || c >= 127))
+						/* pattern is not \000 */
+						continue;
+				}
+				goto out;
+			} else if (c < ' ' || c >= 127) {
+				goto out;
+			}
+		}
+	} while (*domainname);
+	return true;
+ out:
+	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function,
+	       org_domainname);
+	return false;
+}
+
+/**
+ * ccs_is_domain_def - Check whether the given token can be a domainname.
+ *
+ * @buffer: The token to check.
+ *
+ * Returns true if @buffer possibly be a domainname, false otherwise.
+ */
+bool ccs_is_domain_def(const unsigned char *buffer)
+{
+	return !strncmp(buffer, ROOT_NAME, ROOT_NAME_LEN);
+}
+
+/**
+ * ccs_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct domain_info" if found, NULL otherwise.
+ */
+struct domain_info *ccs_find_domain(const char *domainname)
+{
+	struct domain_info *domain;
+	struct path_info name;
+	name.name = domainname;
+	ccs_fill_path_info(&name);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (!domain->is_deleted &&
+		    !ccs_pathcmp(&name, domain->domainname))
+			return domain;
+	}
+	return NULL;
+}
+
+/**
+ * path_depth - Evaluate the number of '/' in a string.
+ *
+ * @pathname: The string to evaluate.
+ *
+ * Returns path depth of the string.
+ *
+ * I score 2 for each of the '/' in the @pathname
+ * and score 1 if the @pathname ends with '/'.
+ */
+static int path_depth(const char *pathname)
+{
+	int i = 0;
+	if (pathname) {
+		char *ep = strchr(pathname, '\0');
+		if (pathname < ep--) {
+			if (*ep != '/')
+				i++;
+			while (pathname <= ep)
+				if (*ep-- == '/')
+					i += 2;
+		}
+	}
+	return i;
+}
+
+/**
+ * const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int const_part_length(const char *filename)
+{
+	char c;
+	int len = 0;
+	if (!filename)
+		return 0;
+	while ((c = *filename++) != '\0') {
+		if (c != '\\') {
+			len++;
+			continue;
+		}
+		c = *filename++;
+		switch (c) {
+		case '\\':  /* "\\" */
+			len += 2;
+			continue;
+		case '0':   /* "\ooo" */
+		case '1':
+		case '2':
+		case '3':
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			len += 4;
+			continue;
+		}
+		break;
+	}
+	return len;
+}
+
+/**
+ * ccs_fill_path_info - Fill in "struct path_info" members.
+ *
+ * @ptr: Pointer to "struct path_info" to fill in.
+ *
+ * The caller sets "struct path_info"->name.
+ */
+void ccs_fill_path_info(struct path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+	ptr->total_len = len;
+	ptr->const_len = const_part_length(name);
+	ptr->is_dir = len && (name[len - 1] == '/');
+	ptr->is_patterned = (ptr->const_len < len);
+	ptr->hash = full_name_hash(name, len);
+	ptr->depth = path_depth(name);
+}
+
+/**
+ * file_matches_to_pattern2 - Pattern matching without '/' character
+ * and "\-" pattern.
+ *
+ * @filename:     The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern:      The start of pattern to compare.
+ * @pattern_end:  The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool file_matches_to_pattern2(const char *filename,
+				     const char *filename_end,
+				     const char *pattern,
+				     const char *pattern_end)
+{
+	while (filename < filename_end && pattern < pattern_end) {
+		char c;
+		if (*pattern != '\\') {
+			if (*filename++ != *pattern++)
+				return false;
+			continue;
+		}
+		c = *filename;
+		pattern++;
+		switch (*pattern) {
+			int i;
+			int j;
+		case '?':
+			if (c == '/') {
+				return false;
+			} else if (c == '\\') {
+				if (filename[1] == '\\')
+					filename++;
+				else if (is_byte_range(filename + 1))
+					filename += 3;
+				else
+					return false;
+			}
+			break;
+		case '\\':
+			if (c != '\\')
+				return false;
+			if (*++filename != '\\')
+				return false;
+			break;
+		case '+':
+			if (!is_decimal(c))
+				return false;
+			break;
+		case 'x':
+			if (!is_hexadecimal(c))
+				return false;
+			break;
+		case 'a':
+			if (!is_alphabet_char(c))
+				return false;
+			break;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+			if (c == '\\' && is_byte_range(filename + 1)
+			    && strncmp(filename + 1, pattern, 3) == 0) {
+				filename += 3;
+				pattern += 2;
+				break;
+			}
+			return false; /* Not matched. */
+		case '*':
+		case '@':
+			for (i = 0; i <= filename_end - filename; i++) {
+				if (file_matches_to_pattern2(filename + i,
+							     filename_end,
+							     pattern + 1,
+							     pattern_end))
+					return true;
+				c = filename[i];
+				if (c == '.' && *pattern == '@')
+					break;
+				if (c != '\\')
+					continue;
+				if (filename[i + 1] == '\\')
+					i++;
+				else if (is_byte_range(filename + i + 1))
+					i += 3;
+				else
+					break; /* Bad pattern. */
+			}
+			return false; /* Not matched. */
+		default:
+			j = 0;
+			c = *pattern;
+			if (c == '$') {
+				while (is_decimal(filename[j]))
+					j++;
+			} else if (c == 'X') {
+				while (is_hexadecimal(filename[j]))
+					j++;
+			} else if (c == 'A') {
+				while (is_alphabet_char(filename[j]))
+					j++;
+			}
+			for (i = 1; i <= j; i++) {
+				if (file_matches_to_pattern2(filename + i,
+							     filename_end,
+							     pattern + 1,
+							     pattern_end))
+					return true;
+			}
+			return false; /* Not matched or bad pattern. */
+		}
+		filename++;
+		pattern++;
+	}
+	while (*pattern == '\\' &&
+	       (*(pattern + 1) == '*' || *(pattern + 1) == '@'))
+		pattern += 2;
+	return (filename == filename_end && pattern == pattern_end);
+}
+
+/**
+ * file_matches_to_pattern - Pattern matching without without '/' character.
+ *
+ * @filename:     The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern:      The start of pattern to compare.
+ * @pattern_end:  The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool file_matches_to_pattern(const char *filename,
+				    const char *filename_end,
+				    const char *pattern,
+				    const char *pattern_end)
+{
+	const char *pattern_start = pattern;
+	bool first = true;
+	bool result;
+	while (pattern < pattern_end - 1) {
+		/* Split at "\-" pattern. */
+		if (*pattern++ != '\\' || *pattern++ != '-')
+			continue;
+		result = file_matches_to_pattern2(filename, filename_end,
+						  pattern_start, pattern - 2);
+		if (first)
+			result = !result;
+		if (result)
+			return false;
+		first = false;
+		pattern_start = pattern;
+	}
+	result = file_matches_to_pattern2(filename, filename_end,
+					  pattern_start, pattern_end);
+	return first ? result : !result;
+}
+
+/**
+ * ccs_path_matches_pattern - Check whether the given filename matches the given pattern.
+ * @filename: The filename to check.
+ * @pattern:  The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ *   \\     \ itself.
+ *   \ooo   Octal representation of a byte.
+ *   \*     More than or equals to 0 character other than '/'.
+ *   \@     More than or equals to 0 character other than '/' or '.'.
+ *   \?     1 byte character other than '/'.
+ *   \$     More than or equals to 1 decimal digit.
+ *   \+     1 decimal digit.
+ *   \X     More than or equals to 1 hexadecimal digit.
+ *   \x     1 hexadecimal digit.
+ *   \A     More than or equals to 1 alphabet character.
+ *   \a     1 alphabet character.
+ *   \-     Subtraction operator.
+ */
+bool ccs_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern)
+{
+	/*
+	  if (!filename || !pattern)
+	  return false;
+	*/
+	const char *f = filename->name;
+	const char *p = pattern->name;
+	const int len = pattern->const_len;
+	/* If @pattern doesn't contain pattern, I can use strcmp(). */
+	if (!pattern->is_patterned)
+		return !ccs_pathcmp(filename, pattern);
+	/* Dont compare if the number of '/' differs. */
+	if (filename->depth != pattern->depth)
+		return false;
+	/* Compare the initial length without patterns. */
+	if (strncmp(f, p, len))
+		return false;
+	f += len;
+	p += len;
+	/* Main loop. Compare each directory component. */
+	while (*f && *p) {
+		const char *f_delimiter = strchr(f, '/');
+		const char *p_delimiter = strchr(p, '/');
+		if (!f_delimiter)
+			f_delimiter = strchr(f, '\0');
+		if (!p_delimiter)
+			p_delimiter = strchr(p, '\0');
+		if (!file_matches_to_pattern(f, f_delimiter, p, p_delimiter))
+			return false;
+		f = f_delimiter;
+		if (*f)
+			f++;
+		p = p_delimiter;
+		if (*p)
+			p++;
+	}
+	/* Ignore trailing "\*" and "\@" in @pattern. */
+	while (*p == '\\' &&
+	       (*(p + 1) == '*' || *(p + 1) == '@'))
+		p += 2;
+	return (!*f && !*p);
+}
+
+/**
+ * ccs_io_printf - Transactional printf() to "struct ccs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @fmt:  The printf()'s format string, followed by parameters.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * The snprintf() will truncate, but ccs_io_printf() won't.
+ */
+bool ccs_io_printf(struct ccs_io_buffer *head, const char *fmt, ...)
+{
+	va_list args;
+	int len;
+	int pos = head->read_avail;
+	int size = head->readbuf_size - pos;
+	if (size <= 0)
+		return false;
+	va_start(args, fmt);
+	len = vsnprintf(head->read_buf + pos, size, fmt, args);
+	va_end(args);
+	if (pos + len >= head->readbuf_size)
+		return false;
+	head->read_avail += len;
+	return true;
+}
+
+/**
+ * ccs_get_exe - Get ccs_realpath() of current process.
+ *
+ * Returns the ccs_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses ccs_alloc(), so the caller must ccs_free()
+ * if this function didn't return NULL.
+ */
+const char *ccs_get_exe(void)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *vma;
+	const char *cp = NULL;
+	if (!mm)
+		return NULL;
+	down_read(&mm->mmap_sem);
+	for (vma = mm->mmap; vma; vma = vma->vm_next) {
+		if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
+			cp = ccs_realpath_from_dentry(vma->vm_file->f_dentry,
+						      vma->vm_file->f_vfsmnt);
+			break;
+		}
+	}
+	up_read(&mm->mmap_sem);
+	return cp;
+}
+
+/**
+ * ccs_get_msg - Get warning message.
+ *
+ * @is_enforce: Is it enforcing mode?
+ *
+ * Returns "ERROR" or "WARNING".
+ */
+const char *ccs_get_msg(const bool is_enforce)
+{
+	if (is_enforce)
+		return "ERROR";
+	else
+		return "WARNING";
+}
+
+/**
+ * ccs_check_flags_no_sleep_check - Check mode for specified functionality.
+ *
+ * @index: The functionality to check mode.
+ *
+ * Returns the mode of specified functionality.
+ */
+unsigned int ccs_check_flags_no_sleep_check(const u8 index)
+{
+	const u8 profile = current->domain_info->profile;
+	return sbin_init_started && index < CCS_MAX_CONTROL_INDEX
+#if MAX_PROFILES != 256
+		&& profile < MAX_PROFILES
+#endif
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->value[index] : 0;
+}
+
+/**
+ * sleep_check - Check whether it is permitted to do operations that may sleep.
+ *
+ * Returns true if it is permitted to do operations that may sleep,
+ * false otherwise.
+ *
+ * TOMOYO Linux supports interactive enforcement that lets processes
+ * wait for the administrator's decision.
+ * All hooks but the one for ccs_may_autobind() are inserted where
+ * it is permitted to do operations that may sleep.
+ * Thus, this warning should not happen.
+ */
+static bool sleep_check(void)
+{
+	static u8 count = 20;
+	if (likely(!in_interrupt()))
+		return true;
+	if (count) {
+		count--;
+		printk(KERN_ERR "BUG: sleeping function called "
+		       "from invalid context.\n");
+		dump_stack();
+	}
+	return false;
+}
+
+/**
+ * ccs_check_flags - Check mode for specified functionality.
+ *
+ * @index: The functionality to check mode.
+ *
+ * Returns the mode of specified functionality.
+ */
+unsigned int ccs_check_flags(const u8 index)
+{
+	return sleep_check() ? ccs_check_flags_no_sleep_check(index) : 0;
+}
+
+#ifdef CONFIG_TOMOYO
+/**
+ * ccs_check_capability_flags - Check mode for specified capability.
+ *
+ * @index: The capability to check mode.
+ *
+ * Returns the mode of specified capability.
+ */
+u8 ccs_check_capability_flags(const u8 index)
+{
+	const u8 profile = current->domain_info->profile;
+	return sbin_init_started && index < TOMOYO_MAX_CAPABILITY_INDEX
+#if MAX_PROFILES != 256
+		&& profile < MAX_PROFILES
+#endif
+		&& sleep_check()
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->capability_value[index] : 0;
+}
+
+/**
+ * ccs_cap2keyword - Convert capability operation to capability name.
+ *
+ * @operation: The capability index.
+ *
+ * Returns the name of the specified capability's name.
+ */
+const char *ccs_cap2keyword(const u8 operation)
+{
+	return operation < TOMOYO_MAX_CAPABILITY_INDEX
+		? capability_control_keyword[operation] : NULL;
+}
+
+#endif
+
+/**
+ * ccs_verbose_mode - Check whether TOMOYO is verbose mode.
+ *
+ * Returns true if domain policy violation warning should be printed to
+ * console.
+ */
+bool ccs_verbose_mode(void)
+{
+	return ccs_check_flags(CCS_TOMOYO_VERBOSE) != 0;
+}
+
+/**
+ * ccs_check_domain_quota - Check for domain's quota.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns true if the domain is not exceeded quota, false otherwise.
+ */
+bool ccs_check_domain_quota(struct domain_info * const domain)
+{
+	unsigned int count = 0;
+	struct acl_info *ptr;
+	if (!domain)
+		return true;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ptr->type & ACL_DELETED)
+			continue;
+		switch (ccs_acl_type2(ptr)) {
+			struct single_path_acl_record *acl1;
+			struct double_path_acl_record *acl2;
+			u16 perm;
+		case TYPE_SINGLE_PATH_ACL:
+			acl1 = container_of(ptr, struct single_path_acl_record,
+					    head);
+			perm = acl1->perm;
+			if (perm & (1 << TYPE_EXECUTE_ACL))
+				count++;
+			if (perm &
+			    ((1 << TYPE_READ_ACL) | (1 << TYPE_WRITE_ACL)))
+				count++;
+			if (perm & (1 << TYPE_CREATE_ACL))
+				count++;
+			if (perm & (1 << TYPE_UNLINK_ACL))
+				count++;
+			if (perm & (1 << TYPE_MKDIR_ACL))
+				count++;
+			if (perm & (1 << TYPE_RMDIR_ACL))
+				count++;
+			if (perm & (1 << TYPE_MKFIFO_ACL))
+				count++;
+			if (perm & (1 << TYPE_MKSOCK_ACL))
+				count++;
+			if (perm & (1 << TYPE_MKBLOCK_ACL))
+				count++;
+			if (perm & (1 << TYPE_MKCHAR_ACL))
+				count++;
+			if (perm & (1 << TYPE_TRUNCATE_ACL))
+				count++;
+			if (perm & (1 << TYPE_SYMLINK_ACL))
+				count++;
+			if (perm & (1 << TYPE_REWRITE_ACL))
+				count++;
+			break;
+		case TYPE_DOUBLE_PATH_ACL:
+			acl2 = container_of(ptr, struct double_path_acl_record,
+					    head);
+			perm = acl2->perm;
+			if (perm & (1 << TYPE_LINK_ACL))
+				count++;
+			if (perm & (1 << TYPE_RENAME_ACL))
+				count++;
+			break;
+		case TYPE_EXECUTE_HANDLER:
+		case TYPE_DENIED_EXECUTE_HANDLER:
+			break;
+		default:
+			count++;
+		}
+	}
+	if (count < ccs_check_flags(CCS_TOMOYO_MAX_ACCEPT_ENTRY))
+		return true;
+	if (!domain->quota_warned) {
+		domain->quota_warned = true;
+		printk(KERN_WARNING "TOMOYO-WARNING: "
+		       "Domain '%s' has so many ACLs to hold. "
+		       "Stopped learning mode.\n", domain->domainname->name);
+	}
+	return false;
+}
+
+/**
+ * ccs_find_or_assign_new_profile - Create a new profile.
+ *
+ * @profile: Profile number to create.
+ *
+ * Returns pointer to "struct profile" on success, NULL otherwise.
+ */
+static struct profile *ccs_find_or_assign_new_profile(const unsigned int
+						      profile)
+{
+	static DEFINE_MUTEX(profile_lock);
+	struct profile *ptr = NULL;
+	mutex_lock(&profile_lock);
+	if (profile < MAX_PROFILES) {
+		ptr = profile_ptr[profile];
+		if (ptr)
+			goto ok;
+		ptr = ccs_alloc_element(sizeof(*ptr));
+		if (ptr) {
+			int i;
+			for (i = 0; i < CCS_MAX_CONTROL_INDEX; i++)
+				ptr->value[i]
+					= ccs_control_array[i].current_value;
+			/*
+			 * Needn't to initialize "ptr->capability_value"
+			 * because they are always 0.
+			 */
+			mb(); /* Avoid out-of-order execution. */
+			profile_ptr[profile] = ptr;
+		}
+	}
+ ok:
+	mutex_unlock(&profile_lock);
+	return ptr;
+}
+
+/**
+ * write_profile - Write profile table.
+ *
+ * @head: Pointer to "struct ccs_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_profile(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int i;
+	unsigned int value;
+	char *cp;
+	struct profile *profile;
+	i = simple_strtoul(data, &cp, 10);
+	if (data != cp) {
+		if (*cp != '-')
+			return -EINVAL;
+		data = cp + 1;
+	}
+	profile = ccs_find_or_assign_new_profile(i);
+	if (!profile)
+		return -EINVAL;
+	cp = strchr(data, '=');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	ccs_update_counter(CCS_UPDATES_COUNTER_PROFILE);
+	if (!strcmp(data, "COMMENT")) {
+		profile->comment = ccs_save_name(cp + 1);
+		return 0;
+	}
+#ifdef CONFIG_TOMOYO
+	if (str_starts(&data, KEYWORD_MAC_FOR_CAPABILITY)) {
+		if (sscanf(cp + 1, "%u", &value) != 1) {
+			for (i = 0; i < 4; i++) {
+				if (strcmp(cp + 1, mode_4[i]))
+					continue;
+				value = i;
+				break;
+			}
+			if (i == 4)
+				return -EINVAL;
+		}
+		if (value > 3)
+			value = 3;
+		for (i = 0; i < TOMOYO_MAX_CAPABILITY_INDEX; i++) {
+			if (strcmp(data, capability_control_keyword[i]))
+				continue;
+			profile->capability_value[i] = value;
+			return 0;
+		}
+		return -EINVAL;
+	}
+#endif
+	for (i = 0; i < CCS_MAX_CONTROL_INDEX; i++) {
+		if (strcmp(data, ccs_control_array[i].keyword))
+			continue;
+		if (sscanf(cp + 1, "%u", &value) != 1) {
+			int j;
+			const char **modes;
+			switch (i) {
+			case CCS_SAKURA_RESTRICT_AUTOBIND:
+			case CCS_TOMOYO_VERBOSE:
+			case CCS_ALLOW_ENFORCE_GRACE:
+				modes = mode_2;
+				break;
+			default:
+				modes = mode_4;
+				break;
+			}
+			for (j = 0; j < 4; j++) {
+				if (strcmp(cp + 1, modes[j]))
+					continue;
+				value = j;
+				break;
+			}
+			if (j == 4)
+				return -EINVAL;
+		} else if (value > ccs_control_array[i].max_value) {
+			value = ccs_control_array[i].max_value;
+		}
+		switch (i) {
+		case CCS_SAKURA_DENY_CONCEAL_MOUNT:
+		case CCS_SAKURA_RESTRICT_UNMOUNT:
+			if (value == 1)
+				value = 2; /* learning mode is not supported. */
+		}
+		profile->value[i] = value;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * read_profile - Read profile table.
+ *
+ * @head: Pointer to "struct ccs_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_profile(struct ccs_io_buffer *head)
+{
+	static const int total
+		= CCS_MAX_CONTROL_INDEX + TOMOYO_MAX_CAPABILITY_INDEX + 1;
+	int step;
+	if (head->read_eof)
+		return 0;
+	for (step = head->read_step; step < MAX_PROFILES * total; step++) {
+		const u8 index = step / total;
+		u8 type = step % total;
+		const struct profile *profile = profile_ptr[index];
+		head->read_step = step;
+		if (!profile)
+			continue;
+#if !defined(CONFIG_SAKURA) || !defined(CONFIG_TOMOYO)
+		switch (type) {
+#ifndef CONFIG_SAKURA
+		case CCS_SAKURA_DENY_CONCEAL_MOUNT:
+		case CCS_SAKURA_RESTRICT_CHROOT:
+		case CCS_SAKURA_RESTRICT_MOUNT:
+		case CCS_SAKURA_RESTRICT_UNMOUNT:
+		case CCS_SAKURA_RESTRICT_PIVOT_ROOT:
+		case CCS_SAKURA_RESTRICT_AUTOBIND:
+#endif
+#ifndef CONFIG_TOMOYO
+		case CCS_TOMOYO_MAC_FOR_FILE:
+		case CCS_TOMOYO_MAC_FOR_ARGV0:
+		case CCS_TOMOYO_MAC_FOR_ENV:
+		case CCS_TOMOYO_MAC_FOR_NETWORK:
+		case CCS_TOMOYO_MAC_FOR_SIGNAL:
+		case CCS_TOMOYO_MAX_ACCEPT_ENTRY:
+		case CCS_TOMOYO_MAX_GRANT_LOG:
+		case CCS_TOMOYO_MAX_REJECT_LOG:
+		case CCS_TOMOYO_VERBOSE:
+#endif
+			continue;
+		}
+#endif
+		if (!type) { /* Print profile' comment tag. */
+			if (!ccs_io_printf(head, "%u-COMMENT=%s\n",
+					   index, profile->comment ?
+					   profile->comment->name : ""))
+				break;
+			continue;
+		}
+		type--;
+		if (type >= CCS_MAX_CONTROL_INDEX) {
+#ifdef CONFIG_TOMOYO
+			const int i = type - CCS_MAX_CONTROL_INDEX;
+			const u8 value = profile->capability_value[i];
+			if (!ccs_io_printf(head,
+					   "%u-" KEYWORD_MAC_FOR_CAPABILITY
+					   "%s=%s\n", index,
+					   capability_control_keyword[i],
+					   mode_4[value]))
+				break;
+#endif
+		} else {
+			const unsigned int value = profile->value[type];
+			const char **modes = NULL;
+			const char *keyword = ccs_control_array[type].keyword;
+			switch (ccs_control_array[type].max_value) {
+			case 3:
+				modes = mode_4;
+				break;
+			case 1:
+				modes = mode_2;
+				break;
+			}
+			if (modes) {
+				if (!ccs_io_printf(head, "%u-%s=%s\n", index,
+						   keyword, modes[value]))
+					break;
+			} else {
+				if (!ccs_io_printf(head, "%u-%s=%u\n", index,
+						   keyword, value))
+					break;
+			}
+		}
+	}
+	if (step == MAX_PROFILES * total)
+		head->read_eof = true;
+	return 0;
+}
+
+/* Structure for policy manager. */
+struct policy_manager_entry {
+	struct list1_head list;
+	/* A path to program or a domainname. */
+	const struct path_info *manager;
+	bool is_domain;  /* True if manager is a domainname. */
+	bool is_deleted; /* True if this entry is deleted. */
+};
+
+/* The list for "struct policy_manager_entry". */
+static LIST1_HEAD(policy_manager_list);
+
+/**
+ * update_manager_entry - Add a manager entry.
+ *
+ * @manager:   The path to manager or the domainnamme.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_manager_entry(const char *manager, const bool is_delete)
+{
+	struct policy_manager_entry *new_entry;
+	struct policy_manager_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_manager;
+	int error = -ENOMEM;
+	bool is_domain = false;
+	if (ccs_is_domain_def(manager)) {
+		if (!ccs_is_correct_domain(manager, __func__))
+			return -EINVAL;
+		is_domain = true;
+	} else {
+		if (!ccs_is_correct_path(manager, 1, -1, -1, __func__))
+			return -EINVAL;
+	}
+	saved_manager = ccs_save_name(manager);
+	if (!saved_manager)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (ptr->manager != saved_manager)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->manager = saved_manager;
+	new_entry->is_domain = is_domain;
+	list1_add_tail_mb(&new_entry->list, &policy_manager_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	if (!error)
+		ccs_update_counter(CCS_UPDATES_COUNTER_MANAGER);
+	return error;
+}
+
+/**
+ * write_manager_policy - Write manager policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_manager_policy(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+	if (!strcmp(data, "manage_by_non_root")) {
+		manage_by_non_root = !is_delete;
+		return 0;
+	}
+	return update_manager_entry(data, is_delete);
+}
+
+/**
+ * read_manager_policy - Read manager policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_manager_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var2, &policy_manager_list) {
+		struct policy_manager_entry *ptr;
+		ptr = list1_entry(pos, struct policy_manager_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, "%s\n", ptr->manager->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * is_policy_manager - Check whether the current process is a policy manager.
+ *
+ * Returns true if the current process is permitted to modify policy
+ * via /proc/ccs/ interface.
+ */
+static bool is_policy_manager(void)
+{
+	struct policy_manager_entry *ptr;
+	const char *exe;
+	const struct task_struct *task = current;
+	const struct path_info *domainname = task->domain_info->domainname;
+	bool found = false;
+	if (!sbin_init_started)
+		return true;
+	if (!manage_by_non_root && (task->uid || task->euid))
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && ptr->is_domain
+		    && !ccs_pathcmp(domainname, ptr->manager))
+			return true;
+	}
+	exe = ccs_get_exe();
+	if (!exe)
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && !ptr->is_domain
+		    && !strcmp(exe, ptr->manager->name)) {
+			found = true;
+			break;
+		}
+	}
+	if (!found) { /* Reduce error messages. */
+		static pid_t last_pid;
+		const pid_t pid = current->pid;
+		if (last_pid != pid) {
+			printk(KERN_WARNING "%s ( %s ) is not permitted to "
+			       "update policies.\n", domainname->name, exe);
+			last_pid = pid;
+		}
+	}
+	ccs_free(exe);
+	return found;
+}
+
+#ifdef CONFIG_TOMOYO
+
+/**
+ * ccs_find_condition_part - Find condition part from the statement.
+ *
+ * @data: String to parse.
+ *
+ * Returns pointer to the condition part if it was found in the statement,
+ * NULL otherwise.
+ */
+static char *ccs_find_condition_part(char *data)
+{
+	char *cp = strstr(data, " if ");
+	if (cp) {
+		char *cp2;
+		while ((cp2 = strstr(cp + 3, " if ")) != NULL)
+			cp = cp2;
+		*cp++ = '\0';
+	} else {
+		cp = strstr(data, " ; set ");
+		if (cp)
+			*cp++ = '\0';
+	}
+	return cp;
+}
+
+/**
+ * write_domain_policy - Write domain policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_domain_policy(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct domain_info *domain = head->write_var1;
+	bool is_delete = false;
+	bool is_select = false;
+	bool is_undelete = false;
+	unsigned int profile;
+	const struct condition_list *cond = NULL;
+	char *cp;
+	if (str_starts(&data, KEYWORD_DELETE))
+		is_delete = true;
+	else if (str_starts(&data, KEYWORD_SELECT))
+		is_select = true;
+	else if (str_starts(&data, KEYWORD_UNDELETE))
+		is_undelete = true;
+	if (ccs_is_domain_def(data)) {
+		domain = NULL;
+		if (is_delete)
+			ccs_delete_domain(data);
+		else if (is_select)
+			domain = ccs_find_domain(data);
+		else if (is_undelete)
+			domain = ccs_undelete_domain(data);
+		else
+			domain = ccs_find_or_assign_new_domain(data, 0);
+		head->write_var1 = domain;
+		ccs_update_counter(CCS_UPDATES_COUNTER_DOMAIN_POLICY);
+		return 0;
+	}
+	if (!domain)
+		return -EINVAL;
+
+	if (sscanf(data, KEYWORD_USE_PROFILE "%u", &profile) == 1
+	    && profile < MAX_PROFILES) {
+		if (profile_ptr[profile] || !sbin_init_started)
+			domain->profile = (u8) profile;
+		return 0;
+	}
+	if (!strcmp(data, KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) {
+		ccs_set_domain_flag(domain, is_delete,
+				    DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ);
+		return 0;
+	}
+	if (!strcmp(data, KEYWORD_IGNORE_GLOBAL_ALLOW_ENV)) {
+		ccs_set_domain_flag(domain, is_delete,
+				    DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_ENV);
+		return 0;
+	}
+	cp = ccs_find_condition_part(data);
+	if (cp) {
+		cond = ccs_find_or_assign_new_condition(cp);
+		if (!cond)
+			return -EINVAL;
+	}
+	if (str_starts(&data, KEYWORD_ALLOW_CAPABILITY))
+		return ccs_write_capability_policy(data, domain, cond,
+						   is_delete);
+	else if (str_starts(&data, KEYWORD_ALLOW_NETWORK))
+		return ccs_write_network_policy(data, domain, cond, is_delete);
+	else if (str_starts(&data, KEYWORD_ALLOW_SIGNAL))
+		return ccs_write_signal_policy(data, domain, cond, is_delete);
+	else if (str_starts(&data, KEYWORD_ALLOW_ARGV0))
+		return ccs_write_argv0_policy(data, domain, cond, is_delete);
+	else if (str_starts(&data, KEYWORD_ALLOW_ENV))
+		return ccs_write_env_policy(data, domain, cond, is_delete);
+	else
+		return ccs_write_file_policy(data, domain, cond, is_delete);
+}
+
+/**
+ * print_single_path_acl - Print a single path ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct single_path_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_single_path_acl(struct ccs_io_buffer *head,
+				  struct single_path_acl_record *ptr,
+				  const struct condition_list *cond)
+{
+	int pos;
+	u8 bit;
+	const char *atmark = "";
+	const char *filename;
+	const u16 perm = ptr->perm;
+	if (ptr->u_is_group) {
+		atmark = "@";
+		filename = ptr->u.group->group_name->name;
+	} else {
+		filename = ptr->u.filename->name;
+	}
+	for (bit = head->read_bit; bit < MAX_SINGLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		/* Print "read/write" instead of "read" and "write". */
+		if ((bit == TYPE_READ_ACL || bit == TYPE_WRITE_ACL)
+		    && (perm & (1 << TYPE_READ_WRITE_ACL)))
+			continue;
+		msg = ccs_sp2keyword(bit);
+		pos = head->read_avail;
+		if (!ccs_io_printf(head, "allow_%s %s%s", msg,
+				   atmark, filename) ||
+		    !ccs_print_condition(head, cond))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+ out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_double_path_acl - Print a double path ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct double_path_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_double_path_acl(struct ccs_io_buffer *head,
+				  struct double_path_acl_record *ptr,
+				  const struct condition_list *cond)
+{
+	int pos;
+	const char *atmark1 = "";
+	const char *atmark2 = "";
+	const char *filename1;
+	const char *filename2;
+	const u8 perm = ptr->perm;
+	u8 bit;
+	if (ptr->u1_is_group) {
+		atmark1 = "@";
+		filename1 = ptr->u1.group1->group_name->name;
+	} else {
+		filename1 = ptr->u1.filename1->name;
+	}
+	if (ptr->u2_is_group) {
+		atmark2 = "@";
+		filename2 = ptr->u2.group2->group_name->name;
+	} else {
+		filename2 = ptr->u2.filename2->name;
+	}
+	for (bit = head->read_bit; bit < MAX_DOUBLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		msg = ccs_dp2keyword(bit);
+		pos = head->read_avail;
+		if (!ccs_io_printf(head, "allow_%s %s%s %s%s", msg,
+				   atmark1, filename1, atmark2, filename2) ||
+		    !ccs_print_condition(head, cond))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+ out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_argv0_acl - Print an argv[0] ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct argv0_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_argv0_acl(struct ccs_io_buffer *head,
+			    struct argv0_acl_record *ptr,
+			    const struct condition_list *cond)
+{
+	int pos = head->read_avail;
+	if (!ccs_io_printf(head, KEYWORD_ALLOW_ARGV0 "%s %s",
+			   ptr->filename->name, ptr->argv0->name))
+		goto out;
+	if (!ccs_print_condition(head, cond))
+		goto out;
+	return true;
+ out:
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_env_acl - Print an evironment variable name's ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct env_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_env_acl(struct ccs_io_buffer *head,
+			  struct env_acl_record *ptr,
+			  const struct condition_list *cond)
+{
+	int pos = head->read_avail;
+	if (!ccs_io_printf(head, KEYWORD_ALLOW_ENV "%s", ptr->env->name))
+		goto out;
+	if (!ccs_print_condition(head, cond))
+		goto out;
+	return true;
+ out:
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_capability_acl - Print a capability ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct capability_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_capability_acl(struct ccs_io_buffer *head,
+				 struct capability_acl_record *ptr,
+				 const struct condition_list *cond)
+{
+	int pos = head->read_avail;
+	if (!ccs_io_printf(head, KEYWORD_ALLOW_CAPABILITY "%s",
+			   ccs_cap2keyword(ptr->operation)))
+		goto out;
+	if (!ccs_print_condition(head, cond))
+		goto out;
+	return true;
+ out:
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_ipv4_entry - Print IPv4 address of a network ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct ip_network_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_ipv4_entry(struct ccs_io_buffer *head,
+			     struct ip_network_acl_record *ptr)
+{
+	const u32 min_address = ptr->u.ipv4.min;
+	const u32 max_address = ptr->u.ipv4.max;
+	if (!ccs_io_printf(head, "%u.%u.%u.%u", HIPQUAD(min_address)))
+		return false;
+	if (min_address != max_address
+	    && !ccs_io_printf(head, "-%u.%u.%u.%u", HIPQUAD(max_address)))
+		return false;
+	return true;
+}
+
+/**
+ * print_ipv6_entry - Print IPv6 address of a network ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct ip_network_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_ipv6_entry(struct ccs_io_buffer *head,
+			     struct ip_network_acl_record *ptr)
+{
+	char buf[64];
+	const struct in6_addr *min_address = ptr->u.ipv6.min;
+	const struct in6_addr *max_address = ptr->u.ipv6.max;
+	ccs_print_ipv6(buf, sizeof(buf), min_address);
+	if (!ccs_io_printf(head, "%s", buf))
+		return false;
+	if (min_address != max_address) {
+		ccs_print_ipv6(buf, sizeof(buf), max_address);
+		if (!ccs_io_printf(head, "-%s", buf))
+			return false;
+	}
+	return true;
+}
+
+/**
+ * print_port_entry - Print port number of a network ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct ip_network_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_port_entry(struct ccs_io_buffer *head,
+			     struct ip_network_acl_record *ptr)
+{
+	const u16 min_port = ptr->min_port, max_port = ptr->max_port;
+	if (!ccs_io_printf(head, " %u", min_port))
+		return false;
+	if (min_port != max_port && !ccs_io_printf(head, "-%u", max_port))
+		return false;
+	return true;
+}
+
+/**
+ * print_network_acl - Print a network ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct ip_network_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_network_acl(struct ccs_io_buffer *head,
+			      struct ip_network_acl_record *ptr,
+			      const struct condition_list *cond)
+{
+	int pos = head->read_avail;
+	if (!ccs_io_printf(head, KEYWORD_ALLOW_NETWORK "%s ",
+			   ccs_net2keyword(ptr->operation_type)))
+		goto out;
+	switch (ptr->record_type) {
+	case IP_RECORD_TYPE_ADDRESS_GROUP:
+		if (!ccs_io_printf(head, "@%s", ptr->u.group->group_name->name))
+			goto out;
+		break;
+	case IP_RECORD_TYPE_IPv4:
+		if (!print_ipv4_entry(head, ptr))
+			goto out;
+		break;
+	case IP_RECORD_TYPE_IPv6:
+		if (!print_ipv6_entry(head, ptr))
+			goto out;
+		break;
+	}
+	if (!print_port_entry(head, ptr))
+		goto out;
+	if (!ccs_print_condition(head, cond))
+		goto out;
+	return true;
+ out:
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_signal_acl - Print a signal ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to "struct signale_acl_record".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_signal_acl(struct ccs_io_buffer *head,
+			     struct signal_acl_record *ptr,
+			     const struct condition_list *cond)
+{
+	int pos = head->read_avail;
+	if (!ccs_io_printf(head, KEYWORD_ALLOW_SIGNAL "%u %s",
+			   ptr->sig, ptr->domainname->name))
+		goto out;
+	if (!ccs_print_condition(head, cond))
+		goto out;
+	return true;
+ out:
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_execute_handler_record - Print an execute handler ACL entry.
+ *
+ * @head:    Pointer to "struct ccs_io_buffer".
+ * @keyword: Name of the keyword.
+ * @ptr:     Pointer to "struct execute_handler_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_execute_handler_record(struct ccs_io_buffer *head,
+					 const char *keyword,
+					 struct execute_handler_record *ptr)
+{
+	return ccs_io_printf(head, "%s %s\n", keyword, ptr->handler->name);
+}
+
+/**
+ * print_entry - Print an ACL entry.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @ptr:  Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_entry(struct ccs_io_buffer *head, struct acl_info *ptr)
+{
+	const struct condition_list *cond = ccs_get_condition_part(ptr);
+	const u8 acl_type = ccs_acl_type2(ptr);
+	if (acl_type & ACL_DELETED)
+		return true;
+	if (acl_type == TYPE_SINGLE_PATH_ACL) {
+		struct single_path_acl_record *acl
+			= container_of(ptr, struct single_path_acl_record,
+				       head);
+		return print_single_path_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_DOUBLE_PATH_ACL) {
+		struct double_path_acl_record *acl
+			= container_of(ptr, struct double_path_acl_record,
+				       head);
+		return print_double_path_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_ARGV0_ACL) {
+		struct argv0_acl_record *acl
+			= container_of(ptr, struct argv0_acl_record, head);
+		return print_argv0_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_ENV_ACL) {
+		struct env_acl_record *acl
+			= container_of(ptr, struct env_acl_record, head);
+		return print_env_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_CAPABILITY_ACL) {
+		struct capability_acl_record *acl
+			= container_of(ptr, struct capability_acl_record, head);
+		return print_capability_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_IP_NETWORK_ACL) {
+		struct ip_network_acl_record *acl
+			= container_of(ptr, struct ip_network_acl_record, head);
+		return print_network_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_SIGNAL_ACL) {
+		struct signal_acl_record *acl
+			= container_of(ptr, struct signal_acl_record, head);
+		return print_signal_acl(head, acl, cond);
+	}
+	if (acl_type == TYPE_EXECUTE_HANDLER) {
+		struct execute_handler_record *acl
+			= container_of(ptr, struct execute_handler_record,
+				       head);
+		const char *keyword = KEYWORD_EXECUTE_HANDLER;
+		return print_execute_handler_record(head, keyword, acl);
+	}
+	if (acl_type == TYPE_DENIED_EXECUTE_HANDLER) {
+		struct execute_handler_record *acl
+			= container_of(ptr, struct execute_handler_record,
+				       head);
+		const char *keyword = KEYWORD_DENIED_EXECUTE_HANDLER;
+		return print_execute_handler_record(head, keyword, acl);
+	}
+	BUG(); /* This must not happen. */
+	return false;
+}
+
+/**
+ * read_domain_policy - Read domain policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0.
+ */
+static int read_domain_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *dpos;
+	struct list1_head *apos;
+	if (head->read_eof)
+		return 0;
+	if (head->read_step == 0)
+		head->read_step = 1;
+	list1_for_each_cookie(dpos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		const char *quota_exceeded = "";
+		const char *ignore_global_allow_read = "";
+		const char *ignore_global_allow_env = "";
+		domain = list1_entry(dpos, struct domain_info, list);
+		if (head->read_step != 1)
+			goto acl_loop;
+		if (domain->is_deleted)
+			continue;
+		/* Print domainname and flags. */
+		if (domain->quota_warned)
+			quota_exceeded = "quota_exceeded\n";
+		if (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ)
+			ignore_global_allow_read
+				= KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
+		if (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_ENV)
+			ignore_global_allow_env
+				= KEYWORD_IGNORE_GLOBAL_ALLOW_ENV "\n";
+		if (!ccs_io_printf(head, "%s\n" KEYWORD_USE_PROFILE "%u\n"
+				   "%s%s%s\n", domain->domainname->name,
+				   domain->profile, quota_exceeded,
+				   ignore_global_allow_read,
+				   ignore_global_allow_env))
+			return 0;
+		head->read_step = 2;
+ acl_loop:
+		if (head->read_step == 3)
+			goto tail_mark;
+		/* Print ACL entries in the domain. */
+		list1_for_each_cookie(apos, head->read_var2,
+				      &domain->acl_info_list) {
+			struct acl_info *ptr
+				= list1_entry(apos, struct acl_info, list);
+			if (!print_entry(head, ptr))
+				return 0;
+		}
+		head->read_step = 3;
+ tail_mark:
+		if (!ccs_io_printf(head, "\n"))
+			return 0;
+		head->read_step = 1;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+#endif
+
+/**
+ * write_domain_profile - Assign profile for specified domain.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ *
+ * This is equivalent to doing
+ *
+ *     ( echo "select " $domainname; echo "use_profile " $profile ) |
+ *     /usr/lib/ccs/loadpolicy -d
+ */
+static int write_domain_profile(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp = strchr(data, ' ');
+	struct domain_info *domain;
+	unsigned int profile;
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	domain = ccs_find_domain(cp + 1);
+	profile = simple_strtoul(data, NULL, 10);
+	if (domain && profile < MAX_PROFILES
+	    && (profile_ptr[profile] || !sbin_init_started))
+		domain->profile = (u8) profile;
+	ccs_update_counter(CCS_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * read_domain_profile - Read only domainname and profile.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns list of profile number and domainname pairs.
+ *
+ * This is equivalent to doing
+ *
+ *     grep -A 1 '^<kernel>' /proc/ccs/domain_policy |
+ *     awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" )
+ *     domainname = $0; } else if ( $1 == "use_profile" ) {
+ *     print $2 " " domainname; domainname = ""; } } ; '
+ */
+static int read_domain_profile(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		domain = list1_entry(pos, struct domain_info, list);
+		if (domain->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, "%u %s\n", domain->profile,
+				   domain->domainname->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * write_pid: Specify PID to obtain domainname.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0.
+ */
+static int write_pid(struct ccs_io_buffer *head)
+{
+	head->read_step = (int) simple_strtoul(head->write_buf, NULL, 10);
+	head->read_eof = false;
+	return 0;
+}
+
+/**
+ * read_pid - Get domainname of the specified PID.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns the domainname which the specified PID is in on success,
+ * empty string otherwise.
+ * The PID is specified by write_pid() so that the user can obtain
+ * using read()/write() interface rather than sysctl() interface.
+ */
+static int read_pid(struct ccs_io_buffer *head)
+{
+	if (head->read_avail == 0 && !head->read_eof) {
+		const int pid = head->read_step;
+		struct task_struct *p;
+		struct domain_info *domain = NULL;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		p = find_task_by_vpid(pid);
+		if (p)
+			domain = p->domain_info;
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (domain)
+			ccs_io_printf(head, "%d %u %s", pid, domain->profile,
+				      domain->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_TOMOYO
+
+/**
+ * write_exception_policy - Write exception policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_exception_policy(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+	if (str_starts(&data, KEYWORD_KEEP_DOMAIN))
+		return ccs_write_domain_keeper_policy(data, false, is_delete);
+	if (str_starts(&data, KEYWORD_NO_KEEP_DOMAIN))
+		return ccs_write_domain_keeper_policy(data, true, is_delete);
+	if (str_starts(&data, KEYWORD_INITIALIZE_DOMAIN))
+		return ccs_write_domain_initializer_policy(data, false,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_NO_INITIALIZE_DOMAIN))
+		return ccs_write_domain_initializer_policy(data, true,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_ALIAS))
+		return ccs_write_alias_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_AGGREGATOR))
+		return ccs_write_aggregator_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_READ))
+		return ccs_write_globally_readable_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_ENV))
+		return ccs_write_globally_usable_env_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_FILE_PATTERN))
+		return ccs_write_pattern_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_PATH_GROUP))
+		return ccs_write_path_group_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_DENY_REWRITE))
+		return ccs_write_no_rewrite_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ADDRESS_GROUP))
+		return ccs_write_address_group_policy(data, is_delete);
+	return -EINVAL;
+}
+
+/**
+ * read_exception_policy - Read exception policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int read_exception_policy(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		switch (head->read_step) {
+		case 0:
+			head->read_var2 = NULL;
+			head->read_step = 1;
+		case 1:
+			if (!ccs_read_domain_keeper_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 2;
+		case 2:
+			if (!ccs_read_globally_readable_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 3;
+		case 3:
+			if (!ccs_read_globally_usable_env_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 4;
+		case 4:
+			if (!ccs_read_domain_initializer_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 5;
+		case 5:
+			if (!ccs_read_alias_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 6;
+		case 6:
+			if (!ccs_read_aggregator_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 7;
+		case 7:
+			if (!ccs_read_file_pattern(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 8;
+		case 8:
+			if (!ccs_read_no_rewrite_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 9;
+		case 9:
+			if (!ccs_read_path_group_policy(head))
+				break;
+			head->read_var1 = NULL;
+			head->read_var2 = NULL;
+			head->read_step = 10;
+		case 10:
+			if (!ccs_read_address_group_policy(head))
+				break;
+			head->read_eof = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+#endif
+
+#ifdef CONFIG_SAKURA
+
+/**
+ * write_system_policy - Write system policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_system_policy(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = false;
+	if (str_starts(&data, KEYWORD_DELETE))
+		is_delete = true;
+	if (str_starts(&data, KEYWORD_ALLOW_MOUNT))
+		return ccs_write_mount_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_DENY_UNMOUNT))
+		return ccs_write_no_umount_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_CHROOT))
+		return ccs_write_chroot_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_PIVOT_ROOT))
+		return ccs_write_pivot_root_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_DENY_AUTOBIND))
+		return ccs_write_reserved_port_policy(data, is_delete);
+	return -EINVAL;
+}
+
+/**
+ * read_system_policy - Read system policy.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int read_system_policy(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		switch (head->read_step) {
+		case 0:
+			head->read_var2 = NULL;
+			head->read_step = 1;
+		case 1:
+			if (!ccs_read_mount_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 2;
+		case 2:
+			if (!ccs_read_no_umount_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 3;
+		case 3:
+			if (!ccs_read_chroot_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 4;
+		case 4:
+			if (!ccs_read_pivot_root_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 5;
+		case 5:
+			if (!ccs_read_reserved_port_policy(head))
+				break;
+			head->read_eof = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+#endif
+
+/* Profile loaded by policy loader? */
+static bool profile_loaded = false;
+
+/* Path to the policy loader. The default is /sbin/ccs-init. */
+static const char *ccs_loader;
+
+/**
+ * loader_setup - Specify the policy loader to use.
+ *
+ * @str: Path to the policy loader.
+ *
+ * Returns 0.
+ */
+static int __init loader_setup(char *str)
+{
+	ccs_loader = str;
+	return 0;
+}
+
+__setup("CCS_loader=", loader_setup);
+
+/**
+ * policy_loader_exists - Check whether /sbin/ccs-init exists.
+ *
+ * Returns true if /sbin/ccs-init exists, false otherwise.
+ */
+static bool policy_loader_exists(void)
+{
+	/*
+	 * Don't activate MAC if the path given by 'CCS_loader=' option doesn't
+	 * exist. If the initrd includes /sbin/init but real-root-dev has not
+	 * mounted on / yet, activating MAC will block the system since
+	 * policies are not loaded yet.
+	 * Thus, let do_execve() call this function everytime.
+	 */
+	struct nameidata nd;
+	if (!ccs_loader)
+		ccs_loader = "/sbin/ccs-init";
+	if (path_lookup(ccs_loader, LOOKUP_FOLLOW, &nd)) {
+		printk(KERN_INFO "Not activating Mandatory Access Control now "
+		       "since %s doesn't exist.\n", ccs_loader);
+		return false;
+	}
+	path_put(&nd.path);
+	return true;
+}
+
+/**
+ * ccs_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * This function checks whether @filename is /sbin/init , and if so
+ * invoke /sbin/ccs-init and wait for the termination of /sbin/ccs-init
+ * and then continues invocation of /sbin/init.
+ * /sbin/ccs-init reads policy files in /etc/ccs/ directory and
+ * writes to /proc/ccs/ interfaces.
+ *
+ * Returns nothing.
+ */
+void ccs_load_policy(const char *filename)
+{
+	if (sbin_init_started)
+		return;
+	/*
+	 * Check filename is /sbin/init or /sbin/ccs-start.
+	 * /sbin/ccs-start is a dummy filename in case where /sbin/init can't
+	 * be passed.
+	 * You can create /sbin/ccs-start by "ln -s /bin/true /sbin/ccs-start".
+	 */
+	if (strcmp(filename, "/sbin/init") &&
+	    strcmp(filename, "/sbin/ccs-start"))
+		return;
+	if (!policy_loader_exists())
+		return;
+	if (!profile_loaded) {
+		char *argv[2];
+		char *envp[3];
+		printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+		       ccs_loader);
+		argv[0] = (char *) ccs_loader;
+		argv[1] = NULL;
+		envp[0] = "HOME=/";
+		envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+		envp[2] = NULL;
+		call_usermodehelper(argv[0], argv, envp, 1);
+		while (!profile_loaded) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(HZ / 10);
+		}
+	}
+#ifdef CONFIG_SAKURA
+	printk(KERN_INFO "SAKURA: 1.6.0   2008/04/01\n");
+#endif
+#ifdef CONFIG_TOMOYO
+	printk(KERN_INFO "TOMOYO: 1.6.0   2008/04/01\n");
+#endif
+	printk(KERN_INFO "Mandatory Access Control activated.\n");
+	sbin_init_started = true;
+	ccs_log_level = KERN_WARNING;
+	{ /* Check all profiles currently assigned to domains are defined. */
+		struct domain_info *domain;
+		list1_for_each_entry(domain, &domain_list, list) {
+			const u8 profile = domain->profile;
+			if (profile_ptr[profile])
+				continue;
+			panic("Profile %u (used by '%s') not defined.\n",
+			      profile, domain->domainname->name);
+		}
+	}
+}
+
+/* Wait queue for query_list. */
+static DECLARE_WAIT_QUEUE_HEAD(query_wait);
+
+/* Lock for manipurating query_list. */
+static DEFINE_SPINLOCK(query_lock);
+
+/* Structure for query. */
+struct query_entry {
+	struct list_head list;
+	char *query;
+	int query_len;
+	unsigned int serial;
+	int timer;
+	int answer;
+};
+
+/* The list for "struct query_entry". */
+static LIST_HEAD(query_list);
+
+/* Number of "struct file" referring /proc/ccs/query interface. */
+static atomic_t queryd_watcher = ATOMIC_INIT(0);
+
+/**
+ * ccs_check_supervisor - Ask for the supervisor's decision.
+ *
+ * @fmt: The printf()'s format string, followed by parameters.
+ *
+ * Returns 0 if the supervisor decided to permit the access request which
+ * violated the policy in enforcing mode, -EPERM otherwise.
+ */
+int ccs_check_supervisor(const char *fmt, ...)
+{
+	va_list args;
+	int error = -EPERM;
+	int pos;
+	int len;
+	static unsigned int serial;
+	struct query_entry *query_entry;
+	if (!ccs_check_flags(CCS_ALLOW_ENFORCE_GRACE)
+	    || !atomic_read(&queryd_watcher)) {
+		int i;
+		if (current->tomoyo_flags & CCS_DONT_SLEEP_ON_ENFORCE_ERROR)
+			return -EPERM;
+		for (i = 0; i < ccs_check_flags(CCS_SLEEP_PERIOD); i++) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(HZ / 10);
+		}
+		return -EPERM;
+	}
+	va_start(args, fmt);
+	len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32;
+	va_end(args);
+	query_entry = ccs_alloc(sizeof(*query_entry));
+	if (!query_entry)
+		goto out;
+	query_entry->query = ccs_alloc(len);
+	if (!query_entry->query)
+		goto out;
+	INIT_LIST_HEAD(&query_entry->list);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	query_entry->serial = serial++;
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	pos = snprintf(query_entry->query, len - 1, "Q%u\n",
+		       query_entry->serial);
+	va_start(args, fmt);
+	vsnprintf(query_entry->query + pos, len - 1 - pos, fmt, args);
+	query_entry->query_len = strlen(query_entry->query) + 1;
+	va_end(args);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_add_tail(&query_entry->list, &query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	ccs_update_counter(CCS_UPDATES_COUNTER_QUERY);
+	/* Give 10 seconds for supervisor's opinion. */
+	for (query_entry->timer = 0; atomic_read(&queryd_watcher)
+		     && ccs_check_flags(CCS_ALLOW_ENFORCE_GRACE)
+		     && query_entry->timer < 100; query_entry->timer++) {
+		wake_up(&query_wait);
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ / 10);
+		if (query_entry->answer)
+			break;
+	}
+	ccs_update_counter(CCS_UPDATES_COUNTER_QUERY);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_del(&query_entry->list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	switch (query_entry->answer) {
+	case 1:
+		/* Granted by administrator. */
+		error = 0;
+		break;
+	case 0:
+		/* Timed out. */
+		break;
+	default:
+		/* Rejected by administrator. */
+		break;
+	}
+ out:
+	if (query_entry)
+		ccs_free(query_entry->query);
+	ccs_free(query_entry);
+	return error;
+}
+
+/**
+ * poll_query - poll() for /proc/ccs/query.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
+ *
+ * Returns POLLIN | POLLRDNORM when ready to read, 0 otherwise.
+ *
+ * Waits for access requests which violated policy in enforcing mode.
+ */
+static int poll_query(struct file *file, poll_table *wait)
+{
+	bool found;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	found = !list_empty(&query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	if (found)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &query_wait, wait);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	found = !list_empty(&query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	if (found)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+/**
+ * read_query - Read access requests which violated policy in enforcing mode.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0.
+ */
+static int read_query(struct ccs_io_buffer *head)
+{
+	struct list_head *tmp;
+	int pos = 0;
+	int len = 0;
+	char *buf;
+	if (head->read_avail)
+		return 0;
+	if (head->read_buf) {
+		ccs_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		if (pos++ != head->read_step)
+			continue;
+		len = ptr->query_len;
+		break;
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	if (!len) {
+		head->read_step = 0;
+		return 0;
+	}
+	buf = ccs_alloc(len);
+	if (buf) {
+		pos = 0;
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&query_lock);
+		list_for_each(tmp, &query_list) {
+			struct query_entry *ptr
+				= list_entry(tmp, struct query_entry, list);
+			if (pos++ != head->read_step)
+				continue;
+			/*
+			 * Some query can be skipped because query_list
+			 * can change, but I don't care.
+			 */
+			if (len == ptr->query_len)
+				memmove(buf, ptr->query, len);
+			break;
+		}
+		spin_unlock(&query_lock);
+		/***** CRITICAL SECTION END *****/
+		if (buf[0]) {
+			head->read_avail = len;
+			head->readbuf_size = head->read_avail;
+			head->read_buf = buf;
+			head->read_step++;
+		} else {
+			ccs_free(buf);
+		}
+	}
+	return 0;
+}
+
+/**
+ * write_answer - Write the supervisor's decision.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int write_answer(struct ccs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct list_head *tmp;
+	unsigned int serial;
+	unsigned int answer;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		ptr->timer = 0;
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	if (sscanf(data, "A%u=%u", &serial, &answer) != 2)
+		return -EINVAL;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		if (ptr->serial != serial)
+			continue;
+		if (!ptr->answer)
+			ptr->answer = answer;
+		break;
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+	return 0;
+}
+
+/* Policy updates counter. */
+static unsigned int updates_counter[MAX_CCS_UPDATES_COUNTER];
+
+/* Policy updates counter lock. */
+static DEFINE_SPINLOCK(updates_counter_lock);
+
+/**
+ * ccs_update_counter - Increment policy change counter.
+ *
+ * @index: Type of policy.
+ *
+ * Returns nothing.
+ */
+void ccs_update_counter(const unsigned char index)
+{
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&updates_counter_lock);
+	if (index < MAX_CCS_UPDATES_COUNTER)
+		updates_counter[index]++;
+	spin_unlock(&updates_counter_lock);
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * read_updates_counter - Check for policy change counter.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns how many times policy has changed since the previous check.
+ */
+static int read_updates_counter(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		unsigned int counter[MAX_CCS_UPDATES_COUNTER];
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&updates_counter_lock);
+		memmove(counter, updates_counter, sizeof(updates_counter));
+		memset(updates_counter, 0, sizeof(updates_counter));
+		spin_unlock(&updates_counter_lock);
+		/***** CRITICAL SECTION END *****/
+		ccs_io_printf(head,
+			      "/proc/ccs/system_policy:    %10u\n"
+			      "/proc/ccs/domain_policy:    %10u\n"
+			      "/proc/ccs/exception_policy: %10u\n"
+			      "/proc/ccs/profile:          %10u\n"
+			      "/proc/ccs/query:            %10u\n"
+			      "/proc/ccs/manager:          %10u\n"
+			      "/proc/ccs/grant_log:        %10u\n"
+			      "/proc/ccs/reject_log:       %10u\n",
+			      counter[CCS_UPDATES_COUNTER_SYSTEM_POLICY],
+			      counter[CCS_UPDATES_COUNTER_DOMAIN_POLICY],
+			      counter[CCS_UPDATES_COUNTER_EXCEPTION_POLICY],
+			      counter[CCS_UPDATES_COUNTER_PROFILE],
+			      counter[CCS_UPDATES_COUNTER_QUERY],
+			      counter[CCS_UPDATES_COUNTER_MANAGER],
+			      counter[CCS_UPDATES_COUNTER_GRANT_LOG],
+			      counter[CCS_UPDATES_COUNTER_REJECT_LOG]);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_version: Get version.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns version information.
+ */
+static int read_version(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		ccs_io_printf(head, "1.6.0");
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_memory_counter - Check for memory usage.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns memory usage.
+ */
+static int read_memory_counter(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		const int shared = ccs_get_memory_used_for_save_name();
+		const int private = ccs_get_memory_used_for_elements();
+		const int dynamic = ccs_get_memory_used_for_dynamic();
+		ccs_io_printf(head, "Shared:  %10u\nPrivate: %10u\n"
+			      "Dynamic: %10u\nTotal:   %10u\n",
+			      shared, private, dynamic,
+			      shared + private + dynamic);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_self_domain - Get the current process's domainname.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns the current process's domainname.
+ */
+static int read_self_domain(struct ccs_io_buffer *head)
+{
+	if (!head->read_eof) {
+		/*
+		 * current->domain_info->domainname != NULL
+		 * because every process belongs to a domain and
+		 * the domain's name cannot be NULL.
+		 */
+		ccs_io_printf(head, "%s",
+			      current->domain_info->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * ccs_open_control - open() for /proc/ccs/ interface.
+ *
+ * @type: Type of interface.
+ * @file: Pointer to "struct file".
+ *
+ * Associates policy handler and returns 0 on success, -ENOMEM otherwise.
+ */
+int ccs_open_control(const u8 type, struct file *file)
+{
+	struct ccs_io_buffer *head = ccs_alloc(sizeof(*head));
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->read_sem);
+	mutex_init(&head->write_sem);
+	switch (type) {
+#ifdef CONFIG_SAKURA
+	case CCS_SYSTEMPOLICY: /* /proc/ccs/system_policy */
+		head->write = write_system_policy;
+		head->read = read_system_policy;
+		break;
+#endif
+#ifdef CONFIG_TOMOYO
+	case CCS_DOMAINPOLICY: /* /proc/ccs/domain_policy */
+		head->write = write_domain_policy;
+		head->read = read_domain_policy;
+		break;
+	case CCS_EXCEPTIONPOLICY: /* /proc/ccs/exception_policy */
+		head->write = write_exception_policy;
+		head->read = read_exception_policy;
+		break;
+	case CCS_GRANTLOG: /* /proc/ccs/grant_log */
+		head->poll = ccs_poll_grant_log;
+		head->read = ccs_read_grant_log;
+		break;
+	case CCS_REJECTLOG: /* /proc/ccs/reject_log */
+		head->poll = ccs_poll_reject_log;
+		head->read = ccs_read_reject_log;
+		break;
+#endif
+	case CCS_SELFDOMAIN: /* /proc/ccs/self_domain */
+		head->read = read_self_domain;
+		break;
+	case CCS_DOMAIN_STATUS: /* /proc/ccs/.domain_status */
+		head->write = write_domain_profile;
+		head->read = read_domain_profile;
+		break;
+	case CCS_PROCESS_STATUS: /* /proc/ccs/.process_status */
+		head->write = write_pid;
+		head->read = read_pid;
+		break;
+	case CCS_VERSION: /* /proc/ccs/version */
+		head->read = read_version;
+		head->readbuf_size = 128;
+		break;
+	case CCS_MEMINFO: /* /proc/ccs/meminfo */
+		head->read = read_memory_counter;
+		head->readbuf_size = 128;
+		break;
+	case CCS_PROFILE: /* /proc/ccs/profile */
+		head->write = write_profile;
+		head->read = read_profile;
+		break;
+	case CCS_QUERY: /* /proc/ccs/query */
+		head->poll = poll_query;
+		head->write = write_answer;
+		head->read = read_query;
+		break;
+	case CCS_MANAGER: /* /proc/ccs/manager */
+		head->write = write_manager_policy;
+		head->read = read_manager_policy;
+		break;
+	case CCS_UPDATESCOUNTER: /* /proc/ccs/.updates_counter */
+		head->read = read_updates_counter;
+		break;
+	}
+	/*
+	 * Don't allocate buffer for reading if the file is one of
+	 * /proc/ccs/grant_log , /proc/ccs/reject_log , /proc/ccs/query.
+	 */
+	if (type != CCS_GRANTLOG && type != CCS_REJECTLOG
+	    && type != CCS_QUERY) {
+		if (!head->readbuf_size)
+			head->readbuf_size = 4096 * 2;
+		head->read_buf = ccs_alloc(head->readbuf_size);
+		if (!head->read_buf) {
+			ccs_free(head);
+			return -ENOMEM;
+		}
+	}
+	if (head->write) {
+		head->writebuf_size = 4096 * 2;
+		head->write_buf = ccs_alloc(head->writebuf_size);
+		if (!head->write_buf) {
+			ccs_free(head->read_buf);
+			ccs_free(head);
+			return -ENOMEM;
+		}
+	}
+	file->private_data = head;
+	/*
+	 * Call the handler now if the file is /proc/ccs/self_domain
+	 * so that the user can use "cat < /proc/ccs/self_domain" to
+	 * know the current process's domainname.
+	 */
+	if (type == CCS_SELFDOMAIN)
+		ccs_read_control(file, NULL, 0);
+	/*
+	 * If the file is /proc/ccs/query , increment the monitor count.
+	 * The monitor count is used by ccs_check_supervisor() to see if
+	 * there is some process monitoring /proc/ccs/query.
+	 */
+	else if (head->write == write_answer)
+		atomic_inc(&queryd_watcher);
+	return 0;
+}
+
+/**
+ * ccs_poll_control - poll() for /proc/ccs/ interface.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
+ *
+ * Waits for read readiness.
+ * /proc/ccs/query is handled by /usr/lib/ccs/ccs-queryd and
+ * /proc/ccs/grant_log and /proc/ccs/reject_log are handled by
+ * /usr/lib/ccs/ccs-auditd.
+ */
+int ccs_poll_control(struct file *file, poll_table *wait)
+{
+	struct ccs_io_buffer *head = file->private_data;
+	if (!head->poll)
+		return -ENOSYS;
+	return head->poll(file, wait);
+}
+
+/**
+ * ccs_read_control - read() for /proc/ccs/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Poiner to buffer to write to.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+int ccs_read_control(struct file *file, char __user *buffer,
+		     const int buffer_len)
+{
+	int len = 0;
+	struct ccs_io_buffer *head = file->private_data;
+	char *cp;
+	if (!head->read)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+		return -EFAULT;
+	if (mutex_lock_interruptible(&head->read_sem))
+		return -EINTR;
+	/* Call the policy handler. */
+	len = head->read(head);
+	if (len < 0)
+		goto out;
+	/* Write to buffer. */
+	len = head->read_avail;
+	if (len > buffer_len)
+		len = buffer_len;
+	if (!len)
+		goto out;
+	/* head->read_buf changes by some functions. */
+	cp = head->read_buf;
+	if (copy_to_user(buffer, cp, len)) {
+		len = -EFAULT;
+		goto out;
+	}
+	head->read_avail -= len;
+	memmove(cp, cp + len, head->read_avail);
+ out:
+	mutex_unlock(&head->read_sem);
+	return len;
+}
+
+/**
+ * ccs_write_control - write() for /proc/ccs/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Pointer to buffer to read from.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns @buffer_len on success, negative value otherwise.
+ */
+int ccs_write_control(struct file *file, const char __user *buffer,
+		      const int buffer_len)
+{
+	struct ccs_io_buffer *head = file->private_data;
+	int error = buffer_len;
+	int avail_len = buffer_len;
+	char *cp0 = head->write_buf;
+	if (!head->write)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_READ, buffer, buffer_len))
+		return -EFAULT;
+	/* Don't allow updating policies by non manager programs. */
+	if (head->write != write_pid && !is_policy_manager())
+		return -EPERM;
+	if (mutex_lock_interruptible(&head->write_sem))
+		return -EINTR;
+	/* Read a line and dispatch it to the policy handler. */
+	while (avail_len > 0) {
+		char c;
+		if (head->write_avail >= head->writebuf_size - 1) {
+			error = -ENOMEM;
+			break;
+		} else if (get_user(c, buffer)) {
+			error = -EFAULT;
+			break;
+		}
+		buffer++;
+		avail_len--;
+		cp0[head->write_avail++] = c;
+		if (c != '\n')
+			continue;
+		cp0[head->write_avail - 1] = '\0';
+		head->write_avail = 0;
+		normalize_line(cp0);
+		head->write(head);
+	}
+	mutex_unlock(&head->write_sem);
+	return error;
+}
+
+/**
+ * ccs_close_control - close() for /proc/ccs/ interface.
+ *
+ * @file: Pointer to "struct file".
+ *
+ * Releases memory and returns 0.
+ */
+int ccs_close_control(struct file *file)
+{
+	struct ccs_io_buffer *head = file->private_data;
+	/*
+	 * If the file is /proc/ccs/query , decrement the monitor count.
+	 */
+	if (head->write == write_answer)
+		atomic_dec(&queryd_watcher);
+	/*
+	 * If the file is /proc/ccs/meminfo , regard policy loading by
+	 * the policy loader executed from ccs_load_policy() has finished.
+	 * This hack is needed because 2.4 kernel's call_usermodehelper()
+	 * returns before the executed program terminates in some situations.
+	 * Thus, I'm using the close() request of /proc/ccs/meminfo as
+	 * the trigger rather than complicating the code to wait for
+	 * termination of the policy loader.
+	 * So, the policy loader must open and close /proc/ccs/meminfo
+	 * when loading policy has finished.
+	 */
+	else if (head->read == read_memory_counter)
+		profile_loaded = true;
+	/* Release memory used for policy I/O. */
+	ccs_free(head->read_buf);
+	head->read_buf = NULL;
+	ccs_free(head->write_buf);
+	head->write_buf = NULL;
+	ccs_free(head);
+	head = NULL;
+	file->private_data = NULL;
+	return 0;
+}
+
+/**
+ * ccs_alloc_acl_element - Allocate permanent memory for ACL entry.
+ *
+ * @acl_type:  Type of ACL entry.
+ * @condition: Pointer to condition part of the ACL entry. May be NULL.
+ *
+ * Returns pointer to the ACL entry on success, NULL otherwise.
+ */
+void *ccs_alloc_acl_element(const u8 acl_type,
+			    const struct condition_list *condition)
+{
+	int len;
+	struct acl_info *ptr;
+	switch (acl_type) {
+	case TYPE_SINGLE_PATH_ACL:
+		len = sizeof(struct single_path_acl_record);
+		break;
+	case TYPE_DOUBLE_PATH_ACL:
+		len = sizeof(struct double_path_acl_record);
+		break;
+	case TYPE_ARGV0_ACL:
+		len = sizeof(struct argv0_acl_record);
+		break;
+	case TYPE_ENV_ACL:
+		len = sizeof(struct env_acl_record);
+		break;
+	case TYPE_CAPABILITY_ACL:
+		len = sizeof(struct capability_acl_record);
+		break;
+	case TYPE_IP_NETWORK_ACL:
+		len = sizeof(struct ip_network_acl_record);
+		break;
+	case TYPE_SIGNAL_ACL:
+		len = sizeof(struct signal_acl_record);
+		break;
+	case TYPE_EXECUTE_HANDLER:
+	case TYPE_DENIED_EXECUTE_HANDLER:
+		len = sizeof(struct execute_handler_record);
+		break;
+	default:
+		return NULL;
+	}
+	/*
+	 * If the ACL doesn't have condition part, reduce memory usage
+	 * by eliminating sizeof(struct condition_list *).
+	 */
+	if (!condition)
+		len -= sizeof(ptr->access_me_via_ccs_get_condition_part);
+	ptr = ccs_alloc_element(len);
+	if (!ptr)
+		return NULL;
+	if (condition) {
+		ptr->access_me_via_ccs_get_condition_part = condition;
+		ptr->type = acl_type | ACL_WITH_CONDITION;
+		return ptr;
+	}
+	/*
+	 * Substract sizeof(struct condition_list *) because I eliminated
+	 * sizeof(struct condition_list *) from "struct acl_info"
+	 * but I must return the start address of "struct acl_info".
+	 */
+	ptr = (void *) (((u8 *) ptr)
+			- sizeof(ptr->access_me_via_ccs_get_condition_part));
+	ptr->type = acl_type;
+	return ptr;
+}
+
+/**
+ * ccs_get_condition_part - Get condition part of the given ACL entry.
+ *
+ * @acl: Pointer to "struct acl_info". Pointer to an ACL entry.
+ *
+ * Returns pointer to the condition part if the ACL has it, NULL otherwise.
+ */
+const struct condition_list *ccs_get_condition_part(const struct acl_info *acl)
+{
+	return (acl->type & ACL_WITH_CONDITION) ?
+		acl->access_me_via_ccs_get_condition_part : NULL;
+}

-- 

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

* [TOMOYO #7 11/30] /proc/ccs/ interface for policy management.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (9 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 10/30] Common functions for SAKURA and TOMOYO Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 12/30] Memory and pathname management functions Tetsuo Handa
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-proc-interface.patch --]
[-- Type: text/plain, Size: 5159 bytes --]

This file handles entries in /proc/ccs/ directory.


Kyle Moffett advised me not to use /proc/ , but I want to use /proc/ because

(1) securityfs is not always ready at /sys/kernel/security/ .
    I have to mount securityfs when /sbin/init starts.
    But proc is always ready at /proc/ .
(2) TOMOYO Linux doesn't require LSM.
    TOMOYO Linux can be built without CONFIG_SECURITY=y .
(3) TOMOYO Linux is not only a tool for access control
    but also a tool for analyzing a system's behavior.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/proc/ccs_proc.c |  156 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 156 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/proc/ccs_proc.c
@@ -0,0 +1,156 @@
+/*
+ * fs/proc/ccs_proc.c
+ *
+ * /proc interface for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/ccs_proc.h>
+#include <linux/ccs_common.h>
+
+#if defined(CONFIG_SAKURA) || defined(CONFIG_TOMOYO)
+
+/**
+ * ccs_open - open() for /proc/ccs/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int ccs_open(struct inode *inode, struct file *file)
+{
+	return ccs_open_control(((u8 *) PDE(inode)->data) - ((u8 *) NULL),
+				file);
+}
+
+/**
+ * ccs_release - close() for /proc/ccs/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int ccs_release(struct inode *inode, struct file *file)
+{
+	return ccs_close_control(file);
+}
+
+/**
+ * ccs_poll - poll() for /proc/ccs/ interface.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static unsigned int ccs_poll(struct file *file, poll_table *wait)
+{
+	return ccs_poll_control(file, wait);
+}
+
+/**
+ * ccs_read - read() for /proc/ccs/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static ssize_t ccs_read(struct file *file, char __user *buf, size_t count,
+			loff_t *ppos)
+{
+	return ccs_read_control(file, buf, count);
+}
+
+/**
+ * ccs_write - write() for /proc/ccs/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ */
+static ssize_t ccs_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	return ccs_write_control(file, buf, count);
+}
+
+/* Operations for /proc/ccs/interface. */
+static struct file_operations ccs_operations = {
+	.open    = ccs_open,
+	.release = ccs_release,
+	.poll    = ccs_poll,
+	.read    = ccs_read,
+	.write   = ccs_write,
+};
+
+/**
+ * create_entry - Create interface files under /proc/ccs/ directory.
+ *
+ * @name:   The name of the interface file.
+ * @mode:   The permission of the interface file.
+ * @parent: The parent directory.
+ * @key:    Type of interface.
+ *
+ * Returns nothing.
+ */
+static void __init create_entry(const char *name, const mode_t mode,
+				struct proc_dir_entry *parent, const u8 key)
+{
+	struct proc_dir_entry *entry = create_proc_entry(name, mode, parent);
+	if (entry) {
+		entry->proc_fops = &ccs_operations;
+		entry->data = ((u8 *) NULL) + key;
+	}
+}
+
+/**
+ * ccs_proc_init - Initialize /proc/ccs/ interface.
+ *
+ * Returns 0.
+ */
+static int __init ccs_proc_init(void)
+{
+	struct proc_dir_entry *ccs_dir = proc_mkdir("ccs", NULL);
+	create_entry("query",            0600, ccs_dir, CCS_QUERY);
+#ifdef CONFIG_SAKURA
+	create_entry("system_policy",    0600, ccs_dir, CCS_SYSTEMPOLICY);
+#endif
+#ifdef CONFIG_TOMOYO
+	create_entry("domain_policy",    0600, ccs_dir, CCS_DOMAINPOLICY);
+	create_entry("exception_policy", 0600, ccs_dir, CCS_EXCEPTIONPOLICY);
+	create_entry("grant_log",        0400, ccs_dir, CCS_GRANTLOG);
+	create_entry("reject_log",       0400, ccs_dir, CCS_REJECTLOG);
+#endif
+	create_entry("self_domain",      0400, ccs_dir, CCS_SELFDOMAIN);
+	create_entry(".domain_status",   0600, ccs_dir, CCS_DOMAIN_STATUS);
+	create_entry(".process_status",  0600, ccs_dir, CCS_PROCESS_STATUS);
+	create_entry("meminfo",          0400, ccs_dir, CCS_MEMINFO);
+	create_entry("profile",          0600, ccs_dir, CCS_PROFILE);
+	create_entry("manager",          0600, ccs_dir, CCS_MANAGER);
+	create_entry(".updates_counter", 0400, ccs_dir, CCS_UPDATESCOUNTER);
+	create_entry("version",          0400, ccs_dir, CCS_VERSION);
+	if (sizeof(struct ccs_page_buffer) <  CCS_MAX_PATHNAME_LEN - 16)
+		panic("Bad size!");
+	return 0;
+}
+
+security_initcall(ccs_proc_init);
+
+#endif

-- 

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

* [TOMOYO #7 12/30] Memory and pathname management functions.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (10 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 11/30] /proc/ccs/ interface for policy management Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 13/30] mount restriction part Tetsuo Handa
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-realpath.patch --]
[-- Type: text/plain, Size: 15107 bytes --]

TOMOYO Linux performs pathname based access control.
To remove factors that make pathname based access control difficult
(e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath
of requested pathname from "struct dentry" and "struct vfsmount".

The maximum length of string data is limited to 4000 including trailing '\0'.
Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable
characters, may be TOMOYO Linux should be able to support 16336 (which means
(NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1)))
including trailing '\0'), but I think 4000 is enough for practical use.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/realpath.c |  557 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 557 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/realpath.c
@@ -0,0 +1,557 @@
+/*
+ * fs/realpath.c
+ *
+ * Get the canonicalized absolute pathnames. The basis for SAKURA and TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/realpath.h>
+#include <linux/proc_fs.h>
+#include <linux/ccs_common.h>
+
+/**
+ * get_absolute_path - Get the path of a dentry but ignores chroot'ed root.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @vfsmnt: Pointer to "struct vfsmount".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns 0 on success, -ENOMEM otherwise.
+ *
+ * Caller holds the dcache_lock and vfsmount_lock.
+ * Based on __d_path() in fs/dcache.c
+ *
+ * If dentry is a directory, trailing '/' is appended.
+ * Characters out of 0x20 < c < 0x7F range are converted to
+ * \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+static int get_absolute_path(struct dentry *dentry, struct vfsmount *vfsmnt,
+			     char *buffer, int buflen)
+{
+	/***** CRITICAL SECTION START *****/
+	char *start = buffer;
+	char *end = buffer + buflen;
+	bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode));
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+
+	for (;;) {
+		struct dentry *parent;
+
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Global root? */
+			if (vfsmnt->mnt_parent == vfsmnt)
+				break;
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			continue;
+		}
+		if (is_dir) {
+			is_dir = false;
+			*--end = '/';
+			buflen--;
+		}
+		parent = dentry->d_parent;
+		{
+			const char *sp = dentry->d_name.name;
+			const char *cp = sp + dentry->d_name.len - 1;
+			unsigned char c;
+
+			/*
+			 * Exception: Use /proc/self/ rather than
+			 * /proc/\$/ for current process.
+			 */
+			if (IS_ROOT(parent) && *sp > '0' && *sp <= '9' &&
+			    parent->d_sb &&
+			    parent->d_sb->s_magic == PROC_SUPER_MAGIC) {
+				char *ep;
+				const pid_t pid
+					= (pid_t) simple_strtoul(sp, &ep, 10);
+				if (!*ep && pid == current->tgid) {
+					sp = "self";
+					cp = sp + 3;
+				}
+			}
+
+			while (sp <= cp) {
+				c = *(unsigned char *) cp;
+				if (c == '\\') {
+					buflen -= 2;
+					if (buflen < 0)
+						goto out;
+					*--end = '\\';
+					*--end = '\\';
+				} else if (c > ' ' && c < 127) {
+					if (--buflen < 0)
+						goto out;
+					*--end = (char) c;
+				} else {
+					buflen -= 4;
+					if (buflen < 0)
+						goto out;
+					*--end = (c & 7) + '0';
+					*--end = ((c >> 3) & 7) + '0';
+					*--end = (c >> 6) + '0';
+					*--end = '\\';
+				}
+				cp--;
+			}
+			if (--buflen < 0)
+				goto out;
+			*--end = '/';
+		}
+		dentry = parent;
+	}
+	if (*end == '/') {
+		buflen++;
+		end++;
+	}
+	{
+		const char *sp = dentry->d_name.name;
+		const char *cp = sp + dentry->d_name.len - 1;
+		unsigned char c;
+		while (sp <= cp) {
+			c = *(unsigned char *) cp;
+			if (c == '\\') {
+				buflen -= 2;
+				if (buflen < 0)
+					goto out;
+				*--end = '\\';
+				*--end = '\\';
+			} else if (c > ' ' && c < 127) {
+				if (--buflen < 0)
+					goto out;
+				*--end = (char) c;
+			} else {
+				buflen -= 4;
+				if (buflen < 0)
+					goto out;
+				*--end = (c & 7) + '0';
+				*--end = ((c >> 3) & 7) + '0';
+				*--end = (c >> 6) + '0';
+				*--end = '\\';
+			}
+			cp--;
+		}
+	}
+	/* Move the pathname to the top of the buffer. */
+	memmove(start, end, strlen(end) + 1);
+	return 0;
+ out:
+	return -ENOMEM;
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * ccs_realpath_from_dentry2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
+ *
+ * @dentry:      Pointer to "struct dentry".
+ * @mnt:         Pointer to "struct vfsmount".
+ * @newname:     Pointer to buffer to return value in.
+ * @newname_len: Size of @newname.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_realpath_from_dentry2(struct dentry *dentry, struct vfsmount *mnt,
+			      char *newname, int newname_len)
+{
+	int error;
+	struct dentry *d_dentry;
+	struct vfsmount *d_mnt;
+	if (!dentry || !mnt || !newname || newname_len <= 0)
+		return -EINVAL;
+	d_dentry = dget(dentry);
+	d_mnt = mntget(mnt);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&dcache_lock);
+	spin_lock(&vfsmount_lock);
+	error = get_absolute_path(d_dentry, d_mnt, newname, newname_len);
+	spin_unlock(&vfsmount_lock);
+	spin_unlock(&dcache_lock);
+	/***** CRITICAL SECTION END *****/
+	dput(d_dentry);
+	mntput(d_mnt);
+	if (error)
+		printk(KERN_WARNING "ccs_realpath: Pathname too long.\n");
+	return error;
+}
+
+/**
+ * ccs_realpath_from_dentry - Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @mnt:    Pointer to "struct vfsmount".
+ *
+ * Returns the realpath of the given @dentry and @mnt on success,
+ * NULL otherwise.
+ *
+ * These functions use ccs_alloc(), so caller must ccs_free()
+ * if these functions didn't return NULL.
+ */
+char *ccs_realpath_from_dentry(struct dentry *dentry, struct vfsmount *mnt)
+{
+	char *buf = ccs_alloc(sizeof(struct ccs_page_buffer));
+	if (buf && ccs_realpath_from_dentry2(dentry, mnt, buf,
+					     CCS_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+	ccs_free(buf);
+	return NULL;
+}
+
+/**
+ * ccs_realpath - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *ccs_realpath(const char *pathname)
+{
+	struct nameidata nd;
+	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+		char *buf = ccs_realpath_from_dentry(nd.path.dentry,
+						     nd.path.mnt);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * ccs_realpath_nofollow - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *ccs_realpath_nofollow(const char *pathname)
+{
+	struct nameidata nd;
+	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+		char *buf = ccs_realpath_from_dentry(nd.path.dentry,
+						     nd.path.mnt);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * round_up - Round up an integer so that the returned pointers are appropriately aligned.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns rounded value of @size.
+ *
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int round_up(const unsigned int size)
+{
+	if (sizeof(void *) >= sizeof(long))
+		return ((size + sizeof(void *) - 1)
+			/ sizeof(void *)) * sizeof(void *);
+	else
+		return ((size + sizeof(long) - 1)
+			/ sizeof(long)) * sizeof(long);
+}
+
+static unsigned int allocated_memory_for_elements;
+
+/**
+ * ccs_get_memory_used_for_elements - Get memory used for keeping ACL structures.
+ *
+ * Returns memory used for keeping ACL structures.
+ */
+unsigned int ccs_get_memory_used_for_elements(void)
+{
+	return allocated_memory_for_elements;
+}
+
+/**
+ * ccs_alloc_element - Allocate permanent memory for structures.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ *
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *ccs_alloc_element(const unsigned int size)
+{
+	static DEFINE_MUTEX(lock);
+	static char *buf;
+	static unsigned int buf_used_len = PAGE_SIZE;
+	char *ptr = NULL;
+	const unsigned int word_aligned_size = round_up(size);
+	if (word_aligned_size > PAGE_SIZE)
+		return NULL;
+	mutex_lock(&lock);
+	if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+		ptr = kzalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!ptr) {
+			printk(KERN_WARNING "ERROR: Out of memory "
+			       "for ccs_alloc_element().\n");
+			if (!sbin_init_started)
+				panic("MAC Initialization failed.\n");
+		} else {
+			buf = ptr;
+			allocated_memory_for_elements += PAGE_SIZE;
+			buf_used_len = word_aligned_size;
+			ptr = buf;
+		}
+	} else if (word_aligned_size) {
+		int i;
+		ptr = buf + buf_used_len;
+		buf_used_len += word_aligned_size;
+		for (i = 0; i < word_aligned_size; i++) {
+			if (!ptr[i])
+				continue;
+			printk(KERN_ERR "WARNING: Reserved memory was tainted! "
+			       "The system might go wrong.\n");
+			ptr[i] = '\0';
+		}
+	}
+	mutex_unlock(&lock);
+	return ptr;
+}
+
+static unsigned int allocated_memory_for_savename;
+
+/**
+ * ccs_get_memory_used_for_save_name - Get memory used for keeping string data.
+ *
+ * Returns memory used for keeping string data.
+ */
+unsigned int ccs_get_memory_used_for_save_name(void)
+{
+	return allocated_memory_for_savename;
+}
+
+#define MAX_HASH 256
+
+/* Structure for string data. */
+struct name_entry {
+	struct list1_head list;
+	struct path_info entry;
+};
+
+/* Structure for available memory region. */
+struct free_memory_block_list {
+	struct list_head list;
+	char *ptr;             /* Pointer to a free area. */
+	int len;               /* Length of the area.     */
+};
+
+/* The list for "struct name_entry". */
+static struct list1_head name_list[MAX_HASH];
+
+/**
+ * ccs_save_name - Allocate permanent memory for string data.
+ *
+ * @name: The string to store into the permernent memory.
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ *
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *ccs_save_name(const char *name)
+{
+	static LIST_HEAD(fmb_list);
+	static DEFINE_MUTEX(lock);
+	struct name_entry *ptr;
+	unsigned int hash;
+	struct free_memory_block_list *fmb;
+	int len;
+	char *cp;
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	if (len > CCS_MAX_PATHNAME_LEN) {
+		printk(KERN_WARNING "ERROR: Name too long "
+		       "for ccs_save_name().\n");
+		return NULL;
+	}
+	hash = full_name_hash((const unsigned char *) name, len - 1);
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &name_list[hash % MAX_HASH], list) {
+		if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name))
+			goto out;
+	}
+	list_for_each_entry(fmb, &fmb_list, list) {
+		if (len <= fmb->len)
+			goto ready;
+	}
+	cp = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	fmb = kzalloc(sizeof(*fmb), GFP_KERNEL);
+	if (!cp || !fmb) {
+		kfree(cp);
+		kfree(fmb);
+		printk(KERN_WARNING "ERROR: Out of memory "
+		       "for ccs_save_name().\n");
+		if (!sbin_init_started)
+			panic("MAC Initialization failed.\n");
+		ptr = NULL;
+		goto out;
+	}
+	allocated_memory_for_savename += PAGE_SIZE;
+	list_add(&fmb->list, &fmb_list);
+	fmb->ptr = cp;
+	fmb->len = PAGE_SIZE;
+ ready:
+	ptr = ccs_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto out;
+	ptr->entry.name = fmb->ptr;
+	memmove(fmb->ptr, name, len);
+	ccs_fill_path_info(&ptr->entry);
+	fmb->ptr += len;
+	fmb->len -= len;
+	list1_add_tail_mb(&ptr->list, &name_list[hash % MAX_HASH]);
+	if (fmb->len == 0) {
+		list_del(&fmb->list);
+		kfree(fmb);
+	}
+ out:
+	mutex_unlock(&lock);
+	return ptr ? &ptr->entry : NULL;
+}
+
+/* Structure for temporarily allocated memory. */
+struct cache_entry {
+	struct list_head list;
+	void *ptr;
+	int size;
+};
+
+static struct kmem_cache *ccs_cachep;
+
+/**
+ * ccs_realpath_init - Initialize realpath related code.
+ *
+ * Returns 0.
+ */
+static int __init ccs_realpath_init(void)
+{
+	int i;
+	if (CCS_MAX_PATHNAME_LEN > PAGE_SIZE)
+		panic("Bad size.");
+	ccs_cachep = kmem_cache_create("ccs_cache", sizeof(struct cache_entry),
+				       0, 0, NULL);
+	if (!ccs_cachep)
+		panic("Can't create cache.\n");
+	for (i = 0; i < MAX_HASH; i++)
+		INIT_LIST1_HEAD(&name_list[i]);
+	INIT_LIST1_HEAD(&KERNEL_DOMAIN.acl_info_list);
+	KERNEL_DOMAIN.domainname = ccs_save_name(ROOT_NAME);
+	list1_add_tail_mb(&KERNEL_DOMAIN.list, &domain_list);
+	if (ccs_find_domain(ROOT_NAME) != &KERNEL_DOMAIN)
+		panic("Can't register KERNEL_DOMAIN");
+	return 0;
+}
+
+security_initcall(ccs_realpath_init);
+
+/* The list for "struct cache_entry". */
+static LIST_HEAD(cache_list);
+
+static DEFINE_SPINLOCK(cache_list_lock);
+
+static unsigned int dynamic_memory_size;
+
+/**
+ * ccs_get_memory_used_for_dynamic - Get memory used for temporal purpose.
+ *
+ * Returns memory used for temporal purpose.
+ */
+unsigned int ccs_get_memory_used_for_dynamic(void)
+{
+	return dynamic_memory_size;
+}
+
+/**
+ * ccs_alloc - Allocate memory for temporal purpose.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ */
+void *ccs_alloc(const size_t size)
+{
+	struct cache_entry *new_entry;
+	void *ret = kzalloc(size, GFP_KERNEL);
+	if (!ret)
+		goto out;
+	new_entry = kmem_cache_alloc(ccs_cachep, GFP_KERNEL);
+	if (!new_entry) {
+		kfree(ret);
+		ret = NULL;
+		goto out;
+	}
+	INIT_LIST_HEAD(&new_entry->list);
+	new_entry->ptr = ret;
+	new_entry->size = ksize(ret);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&cache_list_lock);
+	list_add_tail(&new_entry->list, &cache_list);
+	dynamic_memory_size += new_entry->size;
+	spin_unlock(&cache_list_lock);
+	/***** CRITICAL SECTION END *****/
+ out:
+	return ret;
+}
+
+/**
+ * ccs_free - Release memory allocated by ccs_alloc().
+ *
+ * @p: Pointer returned by ccs_alloc(). May be NULL.
+ *
+ * Returns nothing.
+ */
+void ccs_free(const void *p)
+{
+	struct list_head *v;
+	struct cache_entry *entry = NULL;
+	if (!p)
+		return;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&cache_list_lock);
+	list_for_each(v, &cache_list) {
+		entry = list_entry(v, struct cache_entry, list);
+		if (entry->ptr != p) {
+			entry = NULL;
+			continue;
+		}
+		list_del(&entry->list);
+		dynamic_memory_size -= entry->size;
+		break;
+	}
+	spin_unlock(&cache_list_lock);
+	/***** CRITICAL SECTION END *****/
+	if (entry) {
+		kfree(p);
+		kmem_cache_free(ccs_cachep, entry);
+	} else {
+		printk(KERN_WARNING "BUG: ccs_free() with invalid pointer.\n");
+	}
+}

-- 

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

* [TOMOYO #7 13/30] mount restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (11 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 12/30] Memory and pathname management functions Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 14/30] Shadow mount prevention part Tetsuo Handa
                   ` (17 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-mount-restriction-part.patch --]
[-- Type: text/plain, Size: 17335 bytes --]

This file controls mount() requests.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_mount.c |  544 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 544 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_mount.c
@@ -0,0 +1,544 @@
+/*
+ * fs/sakura_mount.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+#include <linux/namei.h>
+
+/* Keywords for mount restrictions. */
+
+/* Allow to call 'mount --bind /source_dir /dest_dir' */
+#define MOUNT_BIND_KEYWORD                               "--bind"
+/* Allow to call 'mount --move /old_dir    /new_dir ' */
+#define MOUNT_MOVE_KEYWORD                               "--move"
+/* Allow to call 'mount -o remount /dir             ' */
+#define MOUNT_REMOUNT_KEYWORD                            "--remount"
+/* Allow to call 'mount --make-unbindable /dir'       */
+#define MOUNT_MAKE_UNBINDABLE_KEYWORD                    "--make-unbindable"
+/* Allow to call 'mount --make-private /dir'          */
+#define MOUNT_MAKE_PRIVATE_KEYWORD                       "--make-private"
+/* Allow to call 'mount --make-slave /dir'            */
+#define MOUNT_MAKE_SLAVE_KEYWORD                         "--make-slave"
+/* Allow to call 'mount --make-shared /dir'           */
+#define MOUNT_MAKE_SHARED_KEYWORD                        "--make-shared"
+
+/* Structure for "allow_mount" keyword. */
+struct mount_entry {
+	struct list1_head list;
+	const struct path_info *dev_name;
+	const struct path_info *dir_name;
+	const struct path_info *fs_type;
+	unsigned long flags;
+	bool is_deleted;
+};
+
+/* The list for "struct mount_entry". */
+static LIST1_HEAD(mount_list);
+
+/**
+ * update_mount_acl - Update "struct mount_entry" list.
+ *
+ * @dev_name:  Name of device file.
+ * @dir_name:  Name of mount point.
+ * @fs_type:   Name of filesystem.
+ * @flags:     Mount options.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_mount_acl(const char *dev_name, const char *dir_name,
+			    const char *fs_type, const unsigned long flags,
+			    const bool is_delete)
+{
+	struct file_system_type *type = NULL;
+	struct mount_entry *new_entry;
+	struct mount_entry *ptr;
+	const struct path_info *fs;
+	const struct path_info *dev;
+	const struct path_info *dir;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	fs = ccs_save_name(fs_type);
+	if (!fs)
+		return -EINVAL;
+	if (!dev_name)
+		/* Map dev_name to "<NULL>" for if no dev_name given. */
+		dev_name = "<NULL>";
+	if (!strcmp(fs->name, MOUNT_REMOUNT_KEYWORD))
+		/* Fix dev_name to "any" for remount permission. */
+		dev_name = "any";
+	if (!strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+	    !strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+	    !strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) ||
+	    !strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD))
+		dev_name = "any";
+	if (!ccs_is_correct_path(dev_name, 0, 0, 0, __func__) ||
+	    !ccs_is_correct_path(dir_name, 0, 0, 0, __func__))
+		return -EINVAL;
+	dev = ccs_save_name(dev_name);
+	dir = ccs_save_name(dir_name);
+	if (!dev || !dir)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &mount_list, list) {
+		if (ptr->flags != flags ||
+		    ccs_pathcmp(ptr->dev_name, dev) ||
+		    ccs_pathcmp(ptr->dir_name, dir) ||
+		    ccs_pathcmp(ptr->fs_type, fs))
+			continue;
+		error = 0;
+		if (is_delete) {
+			ptr->is_deleted = true;
+			goto out;
+		} else {
+			if (ptr->is_deleted) {
+				ptr->is_deleted = false;
+				goto update;
+			}
+			goto out; /* No changes. */
+		}
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->dev_name = dev;
+	new_entry->dir_name = dir;
+	new_entry->fs_type = fs;
+	new_entry->flags = flags;
+	list1_add_tail_mb(&new_entry->list, &mount_list);
+	error = 0;
+	ptr = new_entry;
+ update:
+	if (!strcmp(fs->name, MOUNT_REMOUNT_KEYWORD)) {
+		printk(KERN_CONT "%sAllow remount %s with options 0x%lX.\n",
+		       ccs_log_level, dir->name, ptr->flags);
+	} else if (!strcmp(fs->name, MOUNT_BIND_KEYWORD)
+		   || !strcmp(fs->name, MOUNT_MOVE_KEYWORD)) {
+		printk(KERN_CONT "%sAllow mount %s %s %s with options 0x%lX\n",
+		       ccs_log_level, fs->name, dev->name, dir->name,
+		       ptr->flags);
+	} else if (!strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+		   !strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+		   !strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) ||
+		   !strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD)) {
+		printk(KERN_CONT "%sAllow mount %s %s with options 0x%lX.\n",
+		       ccs_log_level, fs->name, dir->name, ptr->flags);
+	} else {
+		type = get_fs_type(fs->name);
+		if (type && (type->fs_flags & FS_REQUIRES_DEV) != 0)
+			printk(KERN_CONT "%sAllow mount -t %s %s %s "
+			       "with options 0x%lX.\n", ccs_log_level,
+			       fs->name, dev->name, dir->name, ptr->flags);
+		else
+			printk(KERN_CONT "%sAllow mount %s on %s "
+			       "with options 0x%lX.\n", ccs_log_level,
+			       fs->name, dir->name, ptr->flags);
+	}
+	if (type)
+		put_filesystem(type);
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_SYSTEM_POLICY);
+	return error;
+}
+
+/**
+ * print_success - Print success messages.
+ *
+ * @dev_name: Name of device file.
+ * @dir_name: Name of mount point.
+ * @type:     Name of filesystem type.
+ * @flags:    Mount options.
+ * @need_dev: Type of @dev_name.
+ *
+ * Returns nothing.
+ */
+static void print_success(const char *dev_name, const char *dir_name,
+			  const char *type, const unsigned long flags,
+			  const int need_dev)
+{
+	if (need_dev > 0) {
+		printk(KERN_DEBUG "SAKURA-NOTICE: "
+		       "'mount -t %s %s %s 0x%lX' accepted.\n",
+		       type, dev_name, dir_name, flags);
+	} else if (need_dev < 0) {
+		printk(KERN_DEBUG "SAKURA-NOTICE: "
+		       "'mount %s %s %s 0x%lX' accepted.\n",
+		       type, dev_name, dir_name, flags);
+	} else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+		printk(KERN_DEBUG "SAKURA-NOTICE: "
+		       "'mount -o remount %s 0x%lX' accepted.\n",
+		       dir_name, flags);
+	} else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) {
+		printk(KERN_DEBUG "SAKURA-NOTICE: "
+		       "'mount %s %s 0x%lX' accepted.\n",
+		       type, dir_name, flags);
+	} else {
+		printk(KERN_DEBUG "SAKURA-NOTICE: "
+		       "'mount %s on %s 0x%lX' accepted.\n",
+		       type, dir_name, flags);
+	}
+}
+
+/**
+ * print_error - Print error messages.
+ *
+ * @dev_name:   Name of device file.
+ * @dir_name:   Name of mount point.
+ * @type:       Name of filesystem type.
+ * @flags:      Mount options.
+ * @is_enforce: True if it is enforcing mode.
+ * @error:      Error value.
+ *
+ * Returns 0 if permitted by the administrator's decision, negative value
+ * otherwise.
+ */
+static int print_error(const char *dev_name, const char *dir_name,
+		       const char *type, const unsigned long flags,
+		       const bool is_enforce, int error)
+{
+	const char *realname1 = ccs_realpath(dev_name);
+	const char *realname2 = ccs_realpath(dir_name);
+	const char *exename = ccs_get_exe();
+	if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+		printk(KERN_WARNING "SAKURA-%s: mount -o remount %s 0x%lX "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce),
+		       realname2 ? realname2 : dir_name,
+		       flags, current->pid, exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "mount -o remount %s "
+						     "0x%lX\n", exename,
+						     realname2 ? realname2
+						     : dir_name, flags);
+	} else if (!strcmp(type, MOUNT_BIND_KEYWORD)
+		   || !strcmp(type, MOUNT_MOVE_KEYWORD)) {
+		printk(KERN_WARNING "SAKURA-%s: mount %s %s %s 0x%lX "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce), type,
+		       realname1 ? realname1 : dev_name,
+		       realname2 ? realname2 : dir_name,
+		       flags, current->pid, exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "mount %s %s %s 0x%lX\n",
+						     exename, type,
+						     realname1 ? realname1 :
+						     dev_name,
+						     realname2 ? realname2 :
+						     dir_name, flags);
+	} else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) {
+		printk(KERN_WARNING "SAKURA-%s: mount %s %s 0x%lX "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce), type,
+		       realname2 ? realname2 : dir_name,
+		       flags, current->pid, exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "mount %s %s 0x%lX",
+						     exename, type,
+						     realname2 ? realname2 :
+						     dir_name, flags);
+	} else {
+		printk(KERN_WARNING "SAKURA-%s: mount -t %s %s %s 0x%lX "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce), type,
+		       realname1 ? realname1 : dev_name,
+		       realname2 ? realname2 : dir_name,
+		       flags, current->pid, exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "mount -t %s %s %s "
+						     "0x%lX\n", exename, type,
+						     realname1 ? realname1 :
+						     dev_name,
+						     realname2 ? realname2 :
+						     dir_name, flags);
+	}
+	ccs_free(exename);
+	ccs_free(realname2);
+	ccs_free(realname1);
+	return error;
+}
+
+/**
+ * check_mount_permission2 - Check permission for mount() operation.
+ *
+ * @dev_name: Name of device file.
+ * @dir_name: Name of mount point.
+ * @type:     Name of filesystem type. May be NULL.
+ * @flags:    Mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_mount_permission2(char *dev_name, char *dir_name, char *type,
+				   unsigned long flags)
+{
+	const u8 mode = ccs_check_flags(CCS_SAKURA_RESTRICT_MOUNT);
+	const bool is_enforce = (mode == 3);
+	int error = -EPERM;
+	if (!mode)
+		return 0;
+	if (!type)
+		type = "<NULL>";
+	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+		flags &= ~MS_MGC_MSK;
+	switch (flags & (MS_REMOUNT | MS_MOVE | MS_BIND)) {
+	case MS_REMOUNT:
+	case MS_MOVE:
+	case MS_BIND:
+	case 0:
+		break;
+	default:
+		printk(KERN_WARNING "SAKURA-ERROR: "
+		       "%s%s%sare given for single mount operation.\n",
+		       flags & MS_REMOUNT ? "'remount' " : "",
+		       flags & MS_MOVE    ? "'move' " : "",
+		       flags & MS_BIND    ? "'bind' " : "");
+		return -EINVAL;
+	}
+	switch (flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)) {
+	case MS_UNBINDABLE:
+	case MS_PRIVATE:
+	case MS_SLAVE:
+	case MS_SHARED:
+	case 0:
+		break;
+	default:
+		printk(KERN_WARNING "SAKURA-ERROR: "
+		       "%s%s%s%sare given for single mount operation.\n",
+		       flags & MS_UNBINDABLE ? "'unbindable' " : "",
+		       flags & MS_PRIVATE    ? "'private' " : "",
+		       flags & MS_SLAVE      ? "'slave' " : "",
+		       flags & MS_SHARED     ? "'shared' " : "");
+		return -EINVAL;
+	}
+	if (flags & MS_REMOUNT) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_REMOUNT_KEYWORD,
+						flags & ~MS_REMOUNT);
+	} else if (flags & MS_MOVE) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_MOVE_KEYWORD,
+						flags & ~MS_MOVE);
+	} else if (flags & MS_BIND) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_BIND_KEYWORD,
+						flags & ~MS_BIND);
+	} else if (flags & MS_UNBINDABLE) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_MAKE_UNBINDABLE_KEYWORD,
+						flags & ~MS_UNBINDABLE);
+	} else if (flags & MS_PRIVATE) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_MAKE_PRIVATE_KEYWORD,
+						flags & ~MS_PRIVATE);
+	} else if (flags & MS_SLAVE) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_MAKE_SLAVE_KEYWORD,
+						flags & ~MS_SLAVE);
+	} else if (flags & MS_SHARED) {
+		error = check_mount_permission2(dev_name, dir_name,
+						MOUNT_MAKE_SHARED_KEYWORD,
+						flags & ~MS_SHARED);
+	} else {
+		struct mount_entry *ptr;
+		struct file_system_type *fstype = NULL;
+		const char *requested_dir_name = NULL;
+		const char *requested_dev_name = NULL;
+		struct path_info rdev;
+		struct path_info rdir;
+		int need_dev = 0;
+
+		requested_dir_name = ccs_realpath(dir_name);
+		if (!requested_dir_name) {
+			error = -ENOENT;
+			goto cleanup;
+		}
+		rdir.name = requested_dir_name;
+		ccs_fill_path_info(&rdir);
+
+		/* Compare fs name. */
+		if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+			/* Needn't to resolve dev_name */
+		} else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+			   !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+			   !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+			   !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) {
+			/* Needn't to resolve dev_name */
+		} else if (!strcmp(type, MOUNT_BIND_KEYWORD) ||
+			   !strcmp(type, MOUNT_MOVE_KEYWORD)) {
+			requested_dev_name = ccs_realpath(dev_name);
+			if (!requested_dev_name) {
+				error = -ENOENT;
+				goto cleanup;
+			}
+			rdev.name = requested_dev_name;
+			ccs_fill_path_info(&rdev);
+			/* dev_name is a directory */
+			need_dev = -1;
+		} else {
+			fstype = get_fs_type(type);
+			if (fstype) {
+				if (fstype->fs_flags & FS_REQUIRES_DEV) {
+					requested_dev_name
+						= ccs_realpath(dev_name);
+					if (!requested_dev_name) {
+						error = -ENOENT;
+						goto cleanup;
+					}
+					rdev.name = requested_dev_name;
+					ccs_fill_path_info(&rdev);
+					/* dev_name is a block device file */
+					need_dev = 1;
+				}
+			} else {
+				error = -ENODEV;
+				goto cleanup;
+			}
+		}
+		list1_for_each_entry(ptr, &mount_list, list) {
+			if (ptr->is_deleted)
+				continue;
+
+			/* Compare options */
+			if (ptr->flags != flags)
+				continue;
+
+			/* Compare fs name. */
+			if (strcmp(type, ptr->fs_type->name))
+				continue;
+
+			/* Compare mount point. */
+			if (ccs_path_matches_pattern(&rdir, ptr->dir_name) == 0)
+				continue;
+
+			/* Compare device name. */
+			if (requested_dev_name &&
+			    ccs_path_matches_pattern(&rdev, ptr->dev_name) == 0)
+				continue;
+
+			/* OK. */
+			error = 0;
+			print_success(requested_dev_name, requested_dir_name,
+				      type, flags, need_dev);
+			break;
+		}
+		if (error)
+			error = print_error(dev_name, dir_name, type, flags,
+					    is_enforce, error);
+		if (error && mode == 1)
+			update_mount_acl(need_dev ?
+					 requested_dev_name : dev_name,
+					 requested_dir_name, type, flags, 0);
+ cleanup:
+		ccs_free(requested_dev_name);
+		ccs_free(requested_dir_name);
+		if (fstype)
+			put_filesystem(fstype);
+	}
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * ccs_check_mount_permission - Check permission for mount() operation.
+ *
+ * @dev_name: Name of device file.
+ * @dir_name: Name of mount point.
+ * @type:     Name of filesystem type. May be NULL.
+ * @flags:    Mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * This is a wrapper to allow use of 1.4.x patch for 1.5.x.
+ */
+int ccs_check_mount_permission(char *dev_name, char *dir_name, char *type,
+			       const unsigned long *flags)
+{
+	return check_mount_permission2(dev_name, dir_name, type, *flags);
+}
+
+/**
+ * ccs_write_mount_policy - Write "struct mount_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_mount_policy(char *data, const bool is_delete)
+{
+	char *cp;
+	char *cp2;
+	const char *fs;
+	const char *dev;
+	const char *dir;
+	unsigned long flags = 0;
+	cp2 = data;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	dev = cp2;
+	cp2 = cp + 1;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	dir = cp2;
+	cp2 = cp + 1;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	fs = cp2;
+	flags = simple_strtoul(cp + 1, NULL, 0);
+	return update_mount_acl(dev, dir, fs, flags, is_delete);
+}
+
+/**
+ * ccs_read_mount_policy - Read "struct mount_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_mount_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &mount_list) {
+		struct mount_entry *ptr;
+		ptr = list1_entry(pos, struct mount_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALLOW_MOUNT "%s %s %s 0x%lX\n",
+				   ptr->dev_name->name, ptr->dir_name->name,
+				   ptr->fs_type->name, ptr->flags))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 14/30] Shadow mount prevention part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (12 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 13/30] mount restriction part Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 15/30] Automatic bind port selection control part Tetsuo Handa
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-shadow-mount-prevention-part.patch --]
[-- Type: text/plain, Size: 3729 bytes --]

It is usually an undesirable thing to allow mount requests that shadow
already mounted tree. For example, mounting /usr when /usr/local is
already mounted is undesirable because it makes /usr/local inaccessible.

This file forbids such mount request.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_maymount.c |  113 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_maymount.c
@@ -0,0 +1,113 @@
+/*
+ * fs/sakura_maymount.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+#include <linux/mount.h>
+#include <linux/mnt_namespace.h>
+#include <linux/namei.h>
+
+/**
+ * check_conceal_mount - Check whether this mount request shadows existing mounts.
+ *
+ * @nd:     Pointer to "struct nameidata".
+ * @vfsmnt: Pointer to "struct vfsmount".
+ * @dentry: Pointer to "struct dentry".
+ *
+ * Returns true if @vfsmnt is parent directory compared to @nd, false otherwise.
+ */
+static bool check_conceal_mount(struct nameidata *nd, struct vfsmount *vfsmnt,
+				struct dentry *dentry)
+{
+	/***** CRITICAL SECTION START *****/
+	while (1) {
+		if (nd->path.mnt->mnt_root == vfsmnt->mnt_root &&
+		    nd->path.dentry == dentry)
+			return true;
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			if (vfsmnt->mnt_parent == vfsmnt)
+				break;
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			continue;
+		}
+		dentry = dentry->d_parent;
+	}
+	return false;
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * print_error - Print error message.
+ *
+ * @nd:   Pointer to "struct nameidata".
+ * @mode: Access control mode.
+ *
+ * Returns 0 if @mode is not enforcing or permitted by the administrator's
+ * decision, negative value otherwise.
+ */
+static int print_error(struct nameidata *nd, const u8 mode)
+{
+	int error;
+	const bool is_enforce = (mode == 3);
+	const char *dir = ccs_realpath_from_dentry(nd->path.dentry,
+						   nd->path.mnt);
+	const char *exename = ccs_get_exe();
+	printk(KERN_WARNING "SAKURA-%s: mount %s (pid=%d:exe=%s): "
+	       "Permission denied.\n", ccs_get_msg(is_enforce), dir,
+	       current->pid, exename);
+	if (is_enforce)
+		error = ccs_check_supervisor("# %s is requesting\n"
+					     "mount on %s\n", exename, dir);
+	else
+		error = 0;
+	ccs_free(exename);
+	ccs_free(dir);
+	return error;
+}
+
+/**
+ * ccs_may_mount - Check whether this mount request shadows existing mounts.
+ *
+ * @nd: Pointer to "struct nameidata".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_may_mount(struct nameidata *nd)
+{
+	struct list_head *p;
+	bool flag = false;
+	const u8 mode = ccs_check_flags(CCS_SAKURA_DENY_CONCEAL_MOUNT);
+	struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+	if (!mode)
+		return 0;
+	if (!namespace)
+		return 0;
+	list_for_each(p, &namespace->list) {
+		struct vfsmount *vfsmnt = list_entry(p, struct vfsmount,
+						     mnt_list);
+		struct dentry *dentry = vfsmnt->mnt_root;
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&dcache_lock);
+		spin_lock(&vfsmount_lock);
+		if (IS_ROOT(dentry) || !d_unhashed(dentry))
+			flag = check_conceal_mount(nd, vfsmnt, dentry);
+		spin_unlock(&vfsmount_lock);
+		spin_unlock(&dcache_lock);
+		/***** CRITICAL SECTION END *****/
+		if (flag)
+			break;
+	}
+	if (flag)
+		return print_error(nd, mode);
+	return 0;
+}

-- 

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

* [TOMOYO #7 15/30] Automatic bind port selection control part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (13 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 14/30] Shadow mount prevention part Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 16/30] Unmount restriction part Tetsuo Handa
                   ` (15 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-automatic-bind-restriction-part.patch --]
[-- Type: text/plain, Size: 5005 bytes --]

LSM doesn't provide hook for selecting local port. I don't know why.

Don't you think it is not happy if some port
(in /proc/sys/net/ipv4/ip_local_port_range) is implicitly allocated by
bind() with "struct sockaddr_in *"->sin_port = htons(0) or
connect() without bind()?
For example, 8080 could be unexpectedly used by some process
although Squid wanted to use 8080 but the administrator did

  echo 5000 10000 > /proc/sys/net/ipv4/ip_local_port_range


Since selecting local port is done with a spinlock held,
TOMOYO Linux can't support "delayed enforcing" mode for this functionality.
But that doesn't matter.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_bind.c |  145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_bind.c
@@ -0,0 +1,145 @@
+/*
+ * fs/sakura_bind.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+
+/* Structure for "deny_autobind" keyword. */
+struct reserved_entry {
+	struct list1_head list;
+	bool is_deleted;             /* Delete flag.                         */
+	u16 min_port;                /* Start of port number range.          */
+	u16 max_port;                /* End of port number range.            */
+};
+
+/* The list for "struct reserved_entry". */
+static LIST1_HEAD(reservedport_list);
+
+/**
+ * update_reserved_entry - Update "struct reserved_entry" list.
+ *
+ * @min_port: Start of port number range.
+ * @max_port: End of port number range.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_reserved_entry(const u16 min_port, const u16 max_port,
+				 const bool is_delete)
+{
+	struct reserved_entry *new_entry, *ptr;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &reservedport_list, list) {
+		if (ptr->min_port != min_port || max_port != ptr->max_port)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->min_port = min_port;
+	new_entry->max_port = max_port;
+	list1_add_tail_mb(&new_entry->list, &reservedport_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_SYSTEM_POLICY);
+	return error;
+}
+
+/**
+ * ccs_may_autobind - Check permission for bind()'s automatic port number selection.
+ *
+ * @port: Port number.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+int ccs_may_autobind(const u16 port)
+{
+	/***** CRITICAL SECTION START *****/
+	struct reserved_entry *ptr;
+	if (!ccs_check_flags_no_sleep_check(CCS_SAKURA_RESTRICT_AUTOBIND))
+		return 0;
+	list1_for_each_entry(ptr, &reservedport_list, list) {
+		if (ptr->min_port <= port && port <= ptr->max_port &&
+		    !ptr->is_deleted)
+			return -EPERM;
+	}
+	return 0;
+	/***** CRITICAL SECTION END *****/
+}
+EXPORT_SYMBOL(ccs_may_autobind); /* for net/ipv4/ and net/ipv6/ */
+
+/**
+ * ccs_write_reserved_port_policy - Write "struct reserved_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_reserved_port_policy(char *data, const bool is_delete)
+{
+	unsigned int from;
+	unsigned int to;
+	if (strchr(data, ' '))
+		goto out;
+	if (sscanf(data, "%u-%u", &from, &to) == 2) {
+		if (from <= to && to < 65536)
+			return update_reserved_entry(from, to, is_delete);
+	} else if (sscanf(data, "%u", &from) == 1) {
+		if (from < 65536)
+			return update_reserved_entry(from, from, is_delete);
+	}
+ out:
+	printk(KERN_WARNING "%s: ERROR: Invalid port range '%s'\n",
+	       __func__, data);
+	return -EINVAL;
+}
+
+/**
+ * ccs_read_reserved_port_policy - Read "struct reserved_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_reserved_port_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	char buffer[16];
+	memset(buffer, 0, sizeof(buffer));
+	list1_for_each_cookie(pos, head->read_var2, &reservedport_list) {
+		u16 min_port, max_port;
+		struct reserved_entry *ptr;
+		ptr = list1_entry(pos, struct reserved_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		min_port = ptr->min_port;
+		max_port = ptr->max_port;
+		snprintf(buffer, sizeof(buffer) - 1, "%u%c%u", min_port,
+			 min_port != max_port ? '-' : '\0', max_port);
+		if (!ccs_io_printf(head, KEYWORD_DENY_AUTOBIND "%s\n", buffer))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 16/30] Unmount restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (14 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 15/30] Automatic bind port selection control part Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:22 ` [TOMOYO #7 17/30] chroot " Tetsuo Handa
                   ` (14 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-unmount-restriction-part.patch --]
[-- Type: text/plain, Size: 4441 bytes --]

Some partitions such as /proc/ /sys/ should not be unmounted
for proper operation.

This file forbids unmount operation for specified directory.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_umount.c |  157 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 157 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_umount.c
@@ -0,0 +1,157 @@
+/*
+ * fs/sakura_umount.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+#include <linux/mount.h>
+
+/* Structure for "deny_unmount" keyword. */
+struct no_umount_entry {
+	struct list1_head list;
+	const struct path_info *dir;
+	bool is_deleted;
+};
+
+/* The list for "struct no_umount_entry". */
+static LIST1_HEAD(no_umount_list);
+
+/**
+ * update_no_umount_acl - Update "struct no_umount_entry" list.
+ *
+ * @dir:       The name of directrory.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_no_umount_acl(const char *dir, const bool is_delete)
+{
+	struct no_umount_entry *new_entry;
+	struct no_umount_entry *ptr;
+	const struct path_info *saved_dir;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(dir, 1, 0, 1, __func__))
+		return -EINVAL;
+	saved_dir = ccs_save_name(dir);
+	if (!saved_dir)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &no_umount_list, list) {
+		if (ptr->dir != saved_dir)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->dir = saved_dir;
+	list1_add_tail_mb(&new_entry->list, &no_umount_list);
+	error = 0;
+	printk(KERN_CONT "%sDon't allow umount %s\n", ccs_log_level, dir);
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_SYSTEM_POLICY);
+	return error;
+}
+
+/**
+ * ccs_may_umount - Check permission for unmount.
+ *
+ * @mnt: Pointer to "struct vfsmount".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_may_umount(struct vfsmount *mnt)
+{
+	int error = -EPERM;
+	const char *dir0;
+	const u8 mode = ccs_check_flags(CCS_SAKURA_RESTRICT_UNMOUNT);
+	const bool is_enforce = (mode == 3);
+	struct no_umount_entry *ptr;
+	struct path_info dir;
+	bool found = false;
+	if (!mode)
+		return 0;
+	dir0 = ccs_realpath_from_dentry(mnt->mnt_root, mnt);
+	if (!dir0)
+		goto out;
+	dir.name = dir0;
+	ccs_fill_path_info(&dir);
+	list1_for_each_entry(ptr, &no_umount_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_path_matches_pattern(&dir, ptr->dir))
+			continue;
+		found = true;
+		break;
+	}
+	if (found) {
+		const char *exename = ccs_get_exe();
+		printk(KERN_WARNING "SAKURA-%s: umount %s "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce), dir0, current->pid,
+		       exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "unmount %s\n",
+						     exename, dir0);
+		ccs_free(exename);
+	}
+	ccs_free(dir0);
+ out:
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * ccs_write_no_umount_policy - Write "struct no_umount_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on sucess, negative value otherwise.
+ */
+int ccs_write_no_umount_policy(char *data, const bool is_delete)
+{
+	return update_no_umount_acl(data, is_delete);
+}
+
+/**
+ * ccs_read_no_umount_policy - Read "struct no_umount_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_no_umount_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &no_umount_list) {
+		struct no_umount_entry *ptr;
+		ptr = list1_entry(pos, struct no_umount_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_DENY_UNMOUNT "%s\n",
+				   ptr->dir->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 17/30] chroot restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (15 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 16/30] Unmount restriction part Tetsuo Handa
@ 2008-04-04 12:22 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 18/30] pivot_root " Tetsuo Handa
                   ` (13 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:22 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-chroot-restriction-part.patch --]
[-- Type: text/plain, Size: 4831 bytes --]

This file controls chroot() requests.
LSM hook for chroot() is not provided. I don't know why.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_chroot.c |  173 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 173 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_chroot.c
@@ -0,0 +1,173 @@
+/*
+ * fs/sakura_chroot.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+#include <linux/namei.h>
+
+/* Structure for "allow_chroot" keyword. */
+struct chroot_entry {
+	struct list1_head list;
+	const struct path_info *dir;
+	bool is_deleted;
+};
+
+/* The list for "struct chroot_entry". */
+static LIST1_HEAD(chroot_list);
+
+/**
+ * update_chroot_acl - Update "struct chroot_entry" list.
+ *
+ * @dir:       The name of directory.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_chroot_acl(const char *dir, const bool is_delete)
+{
+	struct chroot_entry *new_entry;
+	struct chroot_entry *ptr;
+	const struct path_info *saved_dir;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(dir, 1, 0, 1, __func__))
+		return -EINVAL;
+	saved_dir = ccs_save_name(dir);
+	if (!saved_dir)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &chroot_list, list) {
+		if (ptr->dir != saved_dir)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->dir = saved_dir;
+	list1_add_tail_mb(&new_entry->list, &chroot_list);
+	error = 0;
+	printk(KERN_CONT "%sAllow chroot() to %s\n", ccs_log_level, dir);
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_SYSTEM_POLICY);
+	return error;
+}
+
+/**
+ * print_error - Print error message.
+ *
+ * @root_name: Requested directory name.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 if @mode is not enforcing mode or permitted by the administrator's
+ * decision, negative value otherwise.
+ */
+static int print_error(const char *root_name, const u8 mode)
+{
+	int error;
+	const bool is_enforce = (mode == 3);
+	const char *exename = ccs_get_exe();
+	printk(KERN_WARNING "SAKURA-%s: chroot %s (pid=%d:exe=%s): "
+	       "Permission denied.\n", ccs_get_msg(is_enforce),
+	       root_name, current->pid, exename);
+	if (is_enforce)
+		error = ccs_check_supervisor("# %s is requesting\n"
+					     "chroot %s\n", exename, root_name);
+	else
+		error = 0;
+	if (exename)
+		ccs_free(exename);
+	if (mode == 1 && root_name)
+		update_chroot_acl(root_name, false);
+	return error;
+}
+
+/**
+ * ccs_check_chroot_permission - Check permission for chroot().
+ *
+ * @nd: Pointer to "struct nameidata".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_chroot_permission(struct nameidata *nd)
+{
+	int error = -EPERM;
+	char *root_name;
+	const u8 mode = ccs_check_flags(CCS_SAKURA_RESTRICT_CHROOT);
+	if (!mode)
+		return 0;
+	root_name = ccs_realpath_from_dentry(nd->path.dentry, nd->path.mnt);
+	if (root_name) {
+		struct path_info dir;
+		dir.name = root_name;
+		ccs_fill_path_info(&dir);
+		if (dir.is_dir) {
+			struct chroot_entry *ptr;
+			list1_for_each_entry(ptr, &chroot_list, list) {
+				if (ptr->is_deleted)
+					continue;
+				if (!ccs_path_matches_pattern(&dir, ptr->dir))
+					continue;
+				error = 0;
+				break;
+			}
+		}
+	}
+	if (error)
+		error = print_error(root_name, mode);
+	ccs_free(root_name);
+	return error;
+}
+
+/**
+ * ccs_write_chroot_policy - Write "struct chroot_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_chroot_policy(char *data, const bool is_delete)
+{
+	return update_chroot_acl(data, is_delete);
+}
+
+/**
+ * ccs_read_chroot_policy - Read "struct chroot_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_chroot_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &chroot_list) {
+		struct chroot_entry *ptr;
+		ptr = list1_entry(pos, struct chroot_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALLOW_CHROOT "%s\n",
+				   ptr->dir->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 18/30] pivot_root restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (16 preceding siblings ...)
  2008-04-04 12:22 ` [TOMOYO #7 17/30] chroot " Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 19/30] Auditing functions for TOMOYO Tetsuo Handa
                   ` (12 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-pivot_root-restriction-part.patch --]
[-- Type: text/plain, Size: 5645 bytes --]

This file controls pivot_root() requests.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/sakura_pivot.c |  185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 185 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/sakura_pivot.c
@@ -0,0 +1,185 @@
+/*
+ * fs/sakura_pivot.c
+ *
+ * Implementation of the Domain-Free Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/sakura.h>
+#include <linux/realpath.h>
+#include <linux/namei.h>
+
+/* Structure for "allow_pivot_root" keyword. */
+struct pivot_root_entry {
+	struct list1_head list;
+	const struct path_info *old_root;
+	const struct path_info *new_root;
+	bool is_deleted;
+};
+
+/* The list for "struct pivot_root_entry". */
+static LIST1_HEAD(pivot_root_list);
+
+/**
+ * update_pivot_root_acl - Update "struct pivot_root_entry" list.
+ *
+ * @old_root:  The name of old root directory.
+ * @new_root:  The name of new root directory.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_pivot_root_acl(const char *old_root, const char *new_root,
+				 const bool is_delete)
+{
+	struct pivot_root_entry *new_entry;
+	struct pivot_root_entry *ptr;
+	const struct path_info *saved_old_root;
+	const struct path_info *saved_new_root;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(old_root, 1, 0, 1, __func__) ||
+	    !ccs_is_correct_path(new_root, 1, 0, 1, __func__))
+		return -EINVAL;
+	saved_old_root = ccs_save_name(old_root);
+	saved_new_root = ccs_save_name(new_root);
+	if (!saved_old_root || !saved_new_root)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &pivot_root_list, list) {
+		if (ptr->old_root != saved_old_root ||
+		    ptr->new_root != saved_new_root)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->old_root = saved_old_root;
+	new_entry->new_root = saved_new_root;
+	list1_add_tail_mb(&new_entry->list, &pivot_root_list);
+	error = 0;
+	printk(KERN_CONT "%sAllow pivot_root(%s, %s)\n", ccs_log_level,
+	       new_root, old_root);
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_SYSTEM_POLICY);
+	return error;
+}
+
+/**
+ * ccs_check_pivot_root_permission - Check permission for pivot_root().
+ *
+ * @old_nd: Pointer to "struct nameidata".
+ * @new_nd: Pointer to "struct nameidata".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_pivot_root_permission(struct nameidata *old_nd,
+				    struct nameidata *new_nd)
+{
+	int error = -EPERM;
+	char *old_root, *new_root;
+	const u8 mode = ccs_check_flags(CCS_SAKURA_RESTRICT_PIVOT_ROOT);
+	if (!mode)
+		return 0;
+	old_root = ccs_realpath_from_dentry(old_nd->path.dentry,
+					    old_nd->path.mnt);
+	new_root = ccs_realpath_from_dentry(new_nd->path.dentry,
+					    new_nd->path.mnt);
+	if (old_root && new_root) {
+		struct path_info old_root_dir, new_root_dir;
+		old_root_dir.name = old_root;
+		ccs_fill_path_info(&old_root_dir);
+		new_root_dir.name = new_root;
+		ccs_fill_path_info(&new_root_dir);
+		if (old_root_dir.is_dir && new_root_dir.is_dir) {
+			struct pivot_root_entry *ptr;
+			list1_for_each_entry(ptr, &pivot_root_list, list) {
+				if (ptr->is_deleted)
+					continue;
+				if (!ccs_path_matches_pattern(&old_root_dir,
+							      ptr->old_root) ||
+				    !ccs_path_matches_pattern(&new_root_dir,
+							      ptr->new_root))
+					continue;
+				error = 0;
+				break;
+			}
+		}
+	}
+	if (error) {
+		const bool is_enforce = (mode == 3);
+		const char *exename = ccs_get_exe();
+		printk(KERN_WARNING "SAKURA-%s: pivot_root %s %s "
+		       "(pid=%d:exe=%s): Permission denied.\n",
+		       ccs_get_msg(is_enforce), new_root, old_root,
+		       current->pid, exename);
+		if (is_enforce)
+			error = ccs_check_supervisor("# %s is requesting\n"
+						     "pivot_root %s %s\n",
+						     exename, new_root,
+						     old_root);
+		else
+			error = 0;
+		if (exename)
+			ccs_free(exename);
+		if (mode == 1 && old_root && new_root)
+			update_pivot_root_acl(old_root, new_root, 0);
+	}
+	ccs_free(old_root);
+	ccs_free(new_root);
+	return error;
+}
+
+/**
+ * ccs_write_pivot_root_policy - Write "struct pivot_root_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_pivot_root_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return update_pivot_root_acl(cp, data, is_delete);
+}
+
+/**
+ * ccs_read_pivot_root_policy - Read "struct pivot_root_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_pivot_root_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &pivot_root_list) {
+		struct pivot_root_entry *ptr;
+		ptr = list1_entry(pos, struct pivot_root_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALLOW_PIVOT_ROOT "%s %s\n",
+				   ptr->new_root->name, ptr->old_root->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 19/30] Auditing functions for TOMOYO.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (17 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 18/30] pivot_root " Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 20/30] Socket operation restriction part Tetsuo Handa
                   ` (11 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-auditing-part.patch --]
[-- Type: text/plain, Size: 10373 bytes --]

This file handles audit logs for TOMOYO.

TOMOYO uses /proc/ccs/ interface for reporting access logs in domain policy
format. One is 'grant_log', used for auditing accesses which are granted
by the domain policy. The other is 'reject_log', used for auditing accesses
which are not granted by the domain policy.
The userland daemon /usr/lib/ccs/ccs-auditd will save these logs.

By the way, copy_strings() seems to assume that kmap() never fails.
Does get_user_pages() guarantee that the kmap() never fails?

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_audit.c |  393 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 393 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_audit.c
@@ -0,0 +1,393 @@
+/*
+ * fs/tomoyo_audit.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+
+/**
+ * ccs_print_bprm - Print "struct linux_binprm" for auditing.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns the contents of @bprm on success, NULL otherwise.
+ */
+static char *ccs_print_bprm(struct linux_binprm *bprm)
+{
+	static const int buffer_len = 4096 * 2;
+	char *buffer = ccs_alloc(buffer_len);
+	char *cp;
+	char *last_start;
+	int len;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+	int argv_count = bprm->argc;
+	int envp_count = bprm->envc;
+	bool truncated = false;
+	if (!buffer)
+		return NULL;
+	len = snprintf(buffer, buffer_len - 1,
+		       "argc=%d envc=%d argv[]={ ", argv_count, envp_count);
+	cp = buffer + len;
+	if (!argv_count) {
+		memmove(cp, "} envp[]={ ", 11);
+		cp += 11;
+	}
+	last_start = cp;
+	while (argv_count || envp_count) {
+		struct page *page;
+		const char *kaddr;
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page,
+				   NULL) <= 0)
+			goto out;
+		pos += PAGE_SIZE - offset;
+#else
+		page = bprm->page[i];
+#endif
+		/* Map */
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			goto out;
+		}
+		/* Read. */
+		while (offset < PAGE_SIZE) {
+			const unsigned char c = kaddr[offset++];
+			if (cp == last_start)
+				*cp++ = '"';
+			if (cp >= buffer + buffer_len - 32) {
+				/* Preserve some room for "..." string. */
+				truncated = true;
+			} else if (c == '\\') {
+				*cp++ = '\\';
+				*cp++ = '\\';
+			} else if (c > ' ' && c < 127) {
+				*cp++ = c;
+			} else if (!c) {
+				*cp++ = '"';
+				*cp++ = ' ';
+				last_start = cp;
+			} else {
+				*cp++ = '\\';
+				*cp++ = (c >> 6) + '0';
+				*cp++ = ((c >> 3) & 7) + '0';
+				*cp++ = (c & 7) + '0';
+			}
+			if (c)
+				continue;
+			if (argv_count) {
+				if (--argv_count == 0) {
+					if (truncated) {
+						cp = last_start;
+						memmove(cp, "... ", 4);
+						cp += 4;
+					}
+					memmove(cp, "} envp[]={ ", 11);
+					cp += 11;
+					last_start = cp;
+					truncated = false;
+				}
+			} else if (envp_count) {
+				if (--envp_count == 0) {
+					if (truncated) {
+						cp = last_start;
+						memmove(cp, "... ", 4);
+						cp += 4;
+					}
+				}
+			}
+			if (!argv_count && !envp_count)
+				break;
+		}
+		/* Unmap. */
+		kunmap(page);
+#ifdef CONFIG_MMU
+		put_page(page);
+#endif
+		i++;
+		offset = 0;
+	}
+	*cp++ = '}';
+	*cp = '\0';
+	return buffer;
+ out:
+	snprintf(buffer, buffer_len - 1,
+		 "argc=%d envc=%d argv[]={ ... } envp[]= { ... }",
+		 argv_count, envp_count);
+	return buffer;
+}
+
+static DECLARE_WAIT_QUEUE_HEAD(grant_log_wait);
+static DECLARE_WAIT_QUEUE_HEAD(reject_log_wait);
+
+static DEFINE_SPINLOCK(audit_log_lock);
+
+/* Structure for audit log. */
+struct log_entry {
+	struct list_head list;
+	char *log;
+};
+
+/* The list for "struct log_entry". */
+static LIST_HEAD(grant_log);
+
+/* The list for "struct log_entry". */
+static LIST_HEAD(reject_log);
+
+static int grant_log_count;
+static int reject_log_count;
+
+/**
+ * ccs_init_audit_log - Allocate buffer for audit logs.
+ *
+ * @len:     Required size.
+ * @profile: Profile number.
+ * @mode:    Access control mode.
+ * @bprm:    Pointer to "struct linux_binprm". May be NULL.
+ *
+ * Returns pointer to allocated memory.
+ *
+ * The @len is updated to add the header lines' size on success.
+ */
+char *ccs_init_audit_log(int *len, const u8 profile, const u8 mode,
+			 struct linux_binprm *bprm)
+{
+	static const char *mode_4[4] = {
+		"disabled", "learning", "permissive", "enforcing"
+	};
+	char *buf;
+	char *bprm_info = "";
+	struct timeval tv;
+	struct task_struct *task = current;
+	u32 tomoyo_flags = task->tomoyo_flags;
+	const char *domainname = current->domain_info->domainname->name;
+	do_gettimeofday(&tv);
+	*len += strlen(domainname) + 256;
+	if (bprm) {
+		bprm_info = ccs_print_bprm(bprm);
+		if (!bprm_info)
+			return NULL;
+		*len += strlen(bprm_info);
+	}
+	buf = ccs_alloc(*len);
+	if (buf)
+		snprintf(buf, (*len) - 1,
+			 "#timestamp=%lu profile=%u mode=%s pid=%d uid=%d "
+			 "gid=%d euid=%d egid=%d suid=%d sgid=%d fsuid=%d "
+			 "fsgid=%d state[0]=%u state[1]=%u state[2]=%u %s\n"
+			 "%s\n", tv.tv_sec, profile, mode_4[mode], task->pid,
+			 task->uid, task->gid, task->euid, task->egid,
+			 task->suid, task->sgid, task->fsuid, task->fsgid,
+			 (u8) (tomoyo_flags >> 24), (u8) (tomoyo_flags >> 16),
+			 (u8) (tomoyo_flags >> 8), bprm_info, domainname);
+	if (bprm)
+		ccs_free(bprm_info);
+	return buf;
+}
+
+/**
+ * get_max_grant_log - Get max number of spoolable grant logs.
+ *
+ * Returns max number of spoolable grant logs.
+ */
+static unsigned int get_max_grant_log(void)
+{
+	return ccs_check_flags(CCS_TOMOYO_MAX_GRANT_LOG);
+}
+
+/**
+ * get_max_reject_log - Get max number of spoolable reject logs.
+ *
+ * Returns max number of spoolable reject logs.
+ */
+static unsigned int get_max_reject_log(void)
+{
+	return ccs_check_flags(CCS_TOMOYO_MAX_REJECT_LOG);
+}
+
+/**
+ * ccs_write_audit_log - Write audit log.
+ *
+ * @buf:        Pointer to audit log.
+ * @is_granted: True if this is a granted log.
+ *
+ * Returns 0 on success, -ENOMEM otherwise.
+ *
+ * Caller must allocate @buf with ccs_init_audit_log().
+ */
+int ccs_write_audit_log(char *buf, const bool is_granted)
+{
+	struct log_entry *new_entry = ccs_alloc(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	INIT_LIST_HEAD(&new_entry->list);
+	new_entry->log = buf;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (is_granted) {
+		if (grant_log_count < get_max_grant_log()) {
+			list_add_tail(&new_entry->list, &grant_log);
+			grant_log_count++;
+			buf = NULL;
+			ccs_update_counter(CCS_UPDATES_COUNTER_GRANT_LOG);
+		}
+	} else {
+		if (reject_log_count < get_max_reject_log()) {
+			list_add_tail(&new_entry->list, &reject_log);
+			reject_log_count++;
+			buf = NULL;
+			ccs_update_counter(CCS_UPDATES_COUNTER_REJECT_LOG);
+		}
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (is_granted)
+		wake_up(&grant_log_wait);
+	else
+		wake_up(&reject_log_wait);
+	if (!buf)
+		return 0;
+	ccs_free(new_entry);
+ out:
+	ccs_free(buf);
+	return -ENOMEM;
+}
+
+/**
+ * ccs_can_save_audit_log - Check whether the kernel can save new audit log.
+ *
+ * @is_granted: True if this is a granted log.
+ *
+ * Returns 0 if the kernel can save, -ENOMEM otherwise.
+ */
+int ccs_can_save_audit_log(const bool is_granted)
+{
+	if (is_granted) {
+		if (grant_log_count < get_max_grant_log())
+			return 0;
+	} else {
+		if (reject_log_count < get_max_reject_log())
+			return 0;
+	}
+	return -ENOMEM;
+}
+
+/**
+ * ccs_read_grant_log - Read a grant log.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0.
+ */
+int ccs_read_grant_log(struct ccs_io_buffer *head)
+{
+	struct log_entry *ptr = NULL;
+	if (head->read_avail)
+		return 0;
+	if (head->read_buf) {
+		ccs_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (!list_empty(&grant_log)) {
+		ptr = list_entry(grant_log.next, struct log_entry, list);
+		list_del(&ptr->list);
+		grant_log_count--;
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (ptr) {
+		head->read_buf = ptr->log;
+		head->read_avail = strlen(ptr->log) + 1;
+		head->readbuf_size = head->read_avail;
+		ccs_free(ptr);
+	}
+	return 0;
+}
+
+/**
+ * ccs_poll_grant_log - Wait for a grant log.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
+ *
+ * Returns POLLIN | POLLRDNORM when ready to read a grant log.
+ */
+int ccs_poll_grant_log(struct file *file, poll_table *wait)
+{
+	if (grant_log_count)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &grant_log_wait, wait);
+	if (grant_log_count)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+/**
+ * ccs_read_reject_log - Read a reject log.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0.
+ */
+int ccs_read_reject_log(struct ccs_io_buffer *head)
+{
+	struct log_entry *ptr = NULL;
+	if (head->read_avail)
+		return 0;
+	if (head->read_buf) {
+		ccs_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (!list_empty(&reject_log)) {
+		ptr = list_entry(reject_log.next, struct log_entry, list);
+		list_del(&ptr->list);
+		reject_log_count--;
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (ptr) {
+		head->read_buf = ptr->log;
+		head->read_avail = strlen(ptr->log) + 1;
+		head->readbuf_size = head->read_avail;
+		ccs_free(ptr);
+	}
+	return 0;
+}
+
+/**
+ * ccs_poll_reject_log - Wait for a reject log.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
+ *
+ * Returns POLLIN | POLLRDNORM when ready to read a reject log.
+ */
+int ccs_poll_reject_log(struct file *file, poll_table *wait)
+{
+	if (reject_log_count)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &reject_log_wait, wait);
+	if (reject_log_count)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}

-- 

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

* [TOMOYO #7 20/30] Socket operation restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (18 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 19/30] Auditing functions for TOMOYO Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 21/30] Capability " Tetsuo Handa
                   ` (10 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-socket-restriction-part.patch --]
[-- Type: text/plain, Size: 28146 bytes --]

This file controls socket related operations.

TOMOYO checks permission using the following four parameters.
  * protocol type (TCP, UDP, RAW)
  * access type (bind, listen, connect, accept)
  * IPv4/IPv6 address
  * port number


YOSHIFUJI Hideaki advised to use in4_pton() and in6_pton() rather than
simple sscanf(). But they depends on CONFIG_NET=y.

May I add "depends on NET" in Kconfig?
Nobody tries to build TOMOYO Linux kernel without CONFIG_NET?

When printing IPv6 address,
I'm using "%x:%x:%x:%x:%x:%x:%x:%x" to make the output as short as possible.
But using "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" (= NIP6_FMT) is better?

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_network.c |  906 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 906 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_network.c
@@ -0,0 +1,906 @@
+/*
+ * fs/tomoyo_network.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+
+/**
+ * audit_network_log - Audit network log.
+ *
+ * @is_ipv6:    True if @address is an IPv6 address.
+ * @operation:  The name of operation.
+ * @address:    An IPv4 or IPv6 address.
+ * @port:       Port number.
+ * @is_granted: True if this is a granted log.
+ * @profile:    Profile number used.
+ * @mode:       Access control mode used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_network_log(const bool is_ipv6, const char *operation,
+			     const char *address, const u16 port,
+			     const bool is_granted,
+			     const u8 profile, const u8 mode)
+{
+	char *buf;
+	int len = 256;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, KEYWORD_ALLOW_NETWORK "%s %s %u\n",
+		 operation, address, port);
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/**
+ * save_ipv6_address - Keep the given IPv6 address on the RAM.
+ *
+ * @addr: Pointer to "struct in6_addr".
+ *
+ * Returns pointer to "struct in6_addr" on success, NULL otherwise.
+ *
+ * The RAM is shared, so NEVER try to modify or kfree() the returned address.
+ */
+static const struct in6_addr *save_ipv6_address(const struct in6_addr *addr)
+{
+	static const u8 block_size = 16;
+	struct addr_list {
+		struct in6_addr addr[block_size];
+		struct list1_head list;
+		u32 in_use_count;
+	};
+	static LIST1_HEAD(address_list);
+	struct addr_list *ptr;
+	static DEFINE_MUTEX(lock);
+	u8 i = block_size;
+	if (!addr)
+		return NULL;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &address_list, list) {
+		for (i = 0; i < ptr->in_use_count; i++) {
+			if (!memcmp(&ptr->addr[i], addr, sizeof(*addr)))
+				goto ok;
+		}
+		if (i < block_size)
+			break;
+	}
+	if (i == block_size) {
+		ptr = ccs_alloc_element(sizeof(*ptr));
+		if (!ptr)
+			goto ok;
+		list1_add_tail_mb(&ptr->list, &address_list);
+		i = 0;
+	}
+	ptr->addr[ptr->in_use_count++] = *addr;
+ ok:
+	mutex_unlock(&lock);
+	return ptr ? &ptr->addr[i] : NULL;
+}
+
+/* The list for "struct address_group_entry". */
+static LIST1_HEAD(address_group_list);
+
+/**
+ * update_address_group_entry - Update "struct address_group_entry" list.
+ *
+ * @group_name:  The name of address group.
+ * @is_ipv6:     True if @min_address and @max_address are IPv6 addresses.
+ * @min_address: Start of IPv4 or IPv6 address range.
+ * @max_address: End of IPv4 or IPv6 address range.
+ * @is_delete:   True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_address_group_entry(const char *group_name,
+				      const bool is_ipv6,
+				      const u16 *min_address,
+				      const u16 *max_address,
+				      const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	struct address_group_entry *new_group;
+	struct address_group_entry *group;
+	struct address_group_member *new_member;
+	struct address_group_member *member;
+	const struct path_info *saved_group_name;
+	const struct in6_addr *saved_min_address = NULL;
+	const struct in6_addr *saved_max_address = NULL;
+	int error = -ENOMEM;
+	bool found = false;
+	if (!ccs_is_correct_path(group_name, 0, 0, 0, __func__) ||
+	    !group_name[0])
+		return -EINVAL;
+	saved_group_name = ccs_save_name(group_name);
+	if (!saved_group_name)
+		return -ENOMEM;
+	if (!is_ipv6)
+		goto not_ipv6;
+	saved_min_address
+		= save_ipv6_address((struct in6_addr *) min_address);
+	saved_max_address
+		= save_ipv6_address((struct in6_addr *) max_address);
+	if (!saved_min_address || !saved_max_address)
+		return -ENOMEM;
+ not_ipv6:
+	mutex_lock(&lock);
+	list1_for_each_entry(group, &address_group_list, list) {
+		if (saved_group_name != group->group_name)
+			continue;
+		list1_for_each_entry(member, &group->address_group_member_list,
+				     list) {
+			if (member->is_ipv6 != is_ipv6)
+				continue;
+			if (is_ipv6) {
+				if (member->min.ipv6 != saved_min_address ||
+				    member->max.ipv6 != saved_max_address)
+					continue;
+			} else {
+				if (member->min.ipv4 != *(u32 *) min_address ||
+				    member->max.ipv4 != *(u32 *) max_address)
+					continue;
+			}
+			member->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+		found = true;
+		break;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	if (!found) {
+		new_group = ccs_alloc_element(sizeof(*new_group));
+		if (!new_group)
+			goto out;
+		INIT_LIST1_HEAD(&new_group->address_group_member_list);
+		new_group->group_name = saved_group_name;
+		list1_add_tail_mb(&new_group->list, &address_group_list);
+		group = new_group;
+	}
+	new_member = ccs_alloc_element(sizeof(*new_member));
+	if (!new_member)
+		goto out;
+	new_member->is_ipv6 = is_ipv6;
+	if (is_ipv6) {
+		new_member->min.ipv6 = saved_min_address;
+		new_member->max.ipv6 = saved_max_address;
+	} else {
+		new_member->min.ipv4 = *(u32 *) min_address;
+		new_member->max.ipv4 = *(u32 *) max_address;
+	}
+	list1_add_tail_mb(&new_member->list, &group->address_group_member_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_write_address_group_policy - Write "struct address_group_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_address_group_policy(char *data, const bool is_delete)
+{
+	u8 count;
+	bool is_ipv6;
+	u16 min_address[8];
+	u16 max_address[8];
+	char *cp = strchr(data, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	count = sscanf(cp, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx"
+		       "-%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
+		       &min_address[0], &min_address[1],
+		       &min_address[2], &min_address[3],
+		       &min_address[4], &min_address[5],
+		       &min_address[6], &min_address[7],
+		       &max_address[0], &max_address[1],
+		       &max_address[2], &max_address[3],
+		       &max_address[4], &max_address[5],
+		       &max_address[6], &max_address[7]);
+	if (count == 8 || count == 16) {
+		u8 i;
+		for (i = 0; i < 8; i++) {
+			min_address[i] = htons(min_address[i]);
+			max_address[i] = htons(max_address[i]);
+		}
+		if (count == 8)
+			memmove(max_address, min_address, sizeof(min_address));
+		is_ipv6 = true;
+		goto ok;
+	}
+	count = sscanf(cp, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu",
+		       &min_address[0], &min_address[1],
+		       &min_address[2], &min_address[3],
+		       &max_address[0], &max_address[1],
+		       &max_address[2], &max_address[3]);
+	if (count == 4 || count == 8) {
+		u32 ip = ((((u8) min_address[0]) << 24)
+			  + (((u8) min_address[1]) << 16)
+			  + (((u8) min_address[2]) << 8)
+			  + (u8) min_address[3]);
+		*(u32 *) min_address = ip;
+		if (count == 8)
+			ip = ((((u8) max_address[0]) << 24)
+			      + (((u8) max_address[1]) << 16)
+			      + (((u8) max_address[2]) << 8)
+			      + (u8) max_address[3]);
+		*(u32 *) max_address = ip;
+		is_ipv6 = false;
+		goto ok;
+	}
+	return -EINVAL;
+ ok:
+	return update_address_group_entry(data, is_ipv6,
+					  min_address, max_address, is_delete);
+}
+
+/**
+ * find_or_assign_new_address_group - Create address group.
+ *
+ * @group_name: The name of address group.
+ *
+ * Returns pointer to "struct address_group_entry" on success, NULL otherwise.
+ */
+static struct address_group_entry *
+find_or_assign_new_address_group(const char *group_name)
+{
+	u8 i;
+	struct address_group_entry *group;
+	for (i = 0; i <= 1; i++) {
+		list1_for_each_entry(group, &address_group_list, list) {
+			if (!strcmp(group_name, group->group_name->name))
+				return group;
+		}
+		if (!i) {
+			const u16 dummy[2] = { 0, 0 };
+			update_address_group_entry(group_name, false,
+						   dummy, dummy, false);
+			update_address_group_entry(group_name, false,
+						   dummy, dummy, true);
+		}
+	}
+	return NULL;
+}
+
+/**
+ * address_matches_to_group - Check whether the given address matches members of the given address group.
+ *
+ * @is_ipv6: True if @address is an IPv6 address.
+ * @address: An IPv4 or IPv6 address.
+ * @group:   Pointer to "struct address_group_entry".
+ *
+ * Returns true if @address matches addresses in @group group, false otherwise.
+ */
+static bool address_matches_to_group(const bool is_ipv6, const u32 *address,
+				     const struct address_group_entry *group)
+{
+	struct address_group_member *member;
+	const u32 ip = ntohl(*address);
+	list1_for_each_entry(member, &group->address_group_member_list, list) {
+		if (member->is_deleted)
+			continue;
+		if (member->is_ipv6) {
+			if (is_ipv6 &&
+			    memcmp(member->min.ipv6, address, 16) <= 0 &&
+			    memcmp(address, member->max.ipv6, 16) <= 0)
+				return true;
+		} else {
+			if (!is_ipv6 &&
+			    member->min.ipv4 <= ip && ip <= member->max.ipv4)
+				return true;
+		}
+	}
+	return false;
+}
+
+/**
+ * ccs_read_address_group_policy - Read "struct address_group_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_address_group_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *gpos;
+	struct list1_head *mpos;
+	list1_for_each_cookie(gpos, head->read_var1, &address_group_list) {
+		struct address_group_entry *group;
+		group = list1_entry(gpos, struct address_group_entry, list);
+		list1_for_each_cookie(mpos, head->read_var2,
+				      &group->address_group_member_list) {
+			char buf[128];
+			struct address_group_member *member;
+			member = list1_entry(mpos, struct address_group_member,
+					     list);
+			if (member->is_deleted)
+				continue;
+			if (member->is_ipv6) {
+				const struct in6_addr *min_address
+					= member->min.ipv6;
+				const struct in6_addr *max_address
+					= member->max.ipv6;
+				ccs_print_ipv6(buf, sizeof(buf), min_address);
+				if (min_address != max_address) {
+					int len;
+					char *cp = strchr(buf, '\0');
+					*cp++ = '-';
+					len = strlen(buf);
+					ccs_print_ipv6(cp, sizeof(buf) - len,
+						       max_address);
+				}
+			} else {
+				const u32 min_address = member->min.ipv4;
+				const u32 max_address = member->max.ipv4;
+				memset(buf, 0, sizeof(buf));
+				snprintf(buf, sizeof(buf) - 1, "%u.%u.%u.%u",
+					 HIPQUAD(min_address));
+				if (min_address != max_address) {
+					const int len = strlen(buf);
+					snprintf(buf + len,
+						 sizeof(buf) - 1 - len,
+						 "-%u.%u.%u.%u",
+						 HIPQUAD(max_address));
+				}
+			}
+			if (!ccs_io_printf(head, KEYWORD_ADDRESS_GROUP
+					   "%s %s\n", group->group_name->name,
+					   buf))
+				goto out;
+		}
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * ccs_print_ipv6 - Print an IPv6 address.
+ *
+ * @buffer:     Buffer to write to.
+ * @buffer_len: Size of @buffer.
+ * @ip:         Pointer to "struct in6_addr".
+ *
+ * Returns nothing.
+ */
+void ccs_print_ipv6(char *buffer, const int buffer_len,
+		    const struct in6_addr *ip)
+{
+	memset(buffer, 0, buffer_len);
+	snprintf(buffer, buffer_len - 1, "%x:%x:%x:%x:%x:%x:%x:%x", NIP6(*ip));
+}
+
+/**
+ * ccs_net2keyword - Convert network operation index to network operation name.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of operation.
+ */
+const char *ccs_net2keyword(const u8 operation)
+{
+	const char *keyword = "unknown";
+	switch (operation) {
+	case NETWORK_ACL_UDP_BIND:
+		keyword = "UDP bind";
+		break;
+	case NETWORK_ACL_UDP_CONNECT:
+		keyword = "UDP connect";
+		break;
+	case NETWORK_ACL_TCP_BIND:
+		keyword = "TCP bind";
+		break;
+	case NETWORK_ACL_TCP_LISTEN:
+		keyword = "TCP listen";
+		break;
+	case NETWORK_ACL_TCP_CONNECT:
+		keyword = "TCP connect";
+		break;
+	case NETWORK_ACL_TCP_ACCEPT:
+		keyword = "TCP accept";
+		break;
+	case NETWORK_ACL_RAW_BIND:
+		keyword = "RAW bind";
+		break;
+	case NETWORK_ACL_RAW_CONNECT:
+		keyword = "RAW connect";
+		break;
+	}
+	return keyword;
+}
+
+/**
+ * update_network_entry - Update "struct ip_network_acl_record" list.
+ *
+ * @operation:   Type of operation.
+ * @record_type: Type of address.
+ * @group:       Pointer to "struct address_group_entry". May be NULL.
+ * @min_address: Start of IPv4 or IPv6 address range.
+ * @max_address: End of IPv4 or IPv6 address range.
+ * @min_port:    Start of port number range.
+ * @max_port:    End of port number range.
+ * @domain:      Pointer to "struct domain_info".
+ * @condition:   Pointer to "struct condition_list". May be NULL.
+ * @is_delete:   True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_network_entry(const u8 operation, const u8 record_type,
+				const struct address_group_entry *group,
+				const u32 *min_address, const u32 *max_address,
+				const u16 min_port, const u16 max_port,
+				struct domain_info *domain,
+				const struct condition_list *condition,
+				const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct ip_network_acl_record *acl;
+	int error = -ENOMEM;
+	/* using host byte order to allow u32 comparison than memcmp().*/
+	const u32 min_ip = ntohl(*min_address);
+	const u32 max_ip = ntohl(*max_address);
+	const struct in6_addr *saved_min_address = NULL;
+	const struct in6_addr *saved_max_address = NULL;
+	if (!domain)
+		return -EINVAL;
+	if (record_type != IP_RECORD_TYPE_IPv6)
+		goto not_ipv6;
+	saved_min_address = save_ipv6_address((struct in6_addr *) min_address);
+	saved_max_address = save_ipv6_address((struct in6_addr *) max_address);
+	if (!saved_min_address || !saved_max_address)
+		return -ENOMEM;
+ not_ipv6:
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_IP_NETWORK_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct ip_network_acl_record, head);
+		if (acl->operation_type != operation ||
+		    acl->record_type != record_type ||
+		    acl->min_port != min_port || max_port != acl->max_port)
+			continue;
+		if (record_type == IP_RECORD_TYPE_ADDRESS_GROUP) {
+			if (acl->u.group != group)
+				continue;
+		} else if (record_type == IP_RECORD_TYPE_IPv4) {
+			if (acl->u.ipv4.min != min_ip ||
+			    max_ip != acl->u.ipv4.max)
+				continue;
+		} else if (record_type == IP_RECORD_TYPE_IPv6) {
+			if (acl->u.ipv6.min != saved_min_address ||
+			    saved_max_address != acl->u.ipv6.max)
+				continue;
+		}
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_IP_NETWORK_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->operation_type = operation;
+	acl->record_type = record_type;
+	if (record_type == IP_RECORD_TYPE_ADDRESS_GROUP) {
+		acl->u.group = group;
+	} else if (record_type == IP_RECORD_TYPE_IPv4) {
+		acl->u.ipv4.min = min_ip;
+		acl->u.ipv4.max = max_ip;
+	} else {
+		acl->u.ipv6.min = saved_min_address;
+		acl->u.ipv6.max = saved_max_address;
+	}
+	acl->min_port = min_port;
+	acl->max_port = max_port;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_IP_NETWORK_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct ip_network_acl_record, head);
+		if (acl->operation_type != operation ||
+		    acl->record_type != record_type ||
+		    acl->min_port != min_port || max_port != acl->max_port)
+			continue;
+		if (record_type == IP_RECORD_TYPE_ADDRESS_GROUP) {
+			if (acl->u.group != group)
+				continue;
+		} else if (record_type == IP_RECORD_TYPE_IPv4) {
+			if (acl->u.ipv4.min != min_ip ||
+			    max_ip != acl->u.ipv4.max)
+				continue;
+		} else if (record_type == IP_RECORD_TYPE_IPv6) {
+			if (acl->u.ipv6.min != saved_min_address ||
+			    saved_max_address != acl->u.ipv6.max)
+				continue;
+		}
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * check_network_entry - Check permission for network operation.
+ *
+ * @is_ipv6:   True if @address is an IPv6 address.
+ * @operation: Type of operation.
+ * @address:   An IPv4 or IPv6 address.
+ * @port:      Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_network_entry(const bool is_ipv6, const u8 operation,
+			       const u32 *address, const u16 port)
+{
+	struct domain_info * const domain = current->domain_info;
+	struct acl_info *ptr;
+	const char *keyword = ccs_net2keyword(operation);
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_NETWORK);
+	const bool is_enforce = (mode == 3);
+	/* using host byte order to allow u32 comparison than memcmp().*/
+	const u32 ip = ntohl(*address);
+	bool found = false;
+	char buf[64];
+	if (!mode)
+		return 0;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct ip_network_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_IP_NETWORK_ACL)
+			continue;
+		acl = container_of(ptr, struct ip_network_acl_record, head);
+		if (acl->operation_type != operation || port < acl->min_port ||
+		    acl->max_port < port || !ccs_check_condition(ptr, NULL))
+			continue;
+		if (acl->record_type == IP_RECORD_TYPE_ADDRESS_GROUP) {
+			if (!address_matches_to_group(is_ipv6, address,
+						      acl->u.group))
+				continue;
+		} else if (acl->record_type == IP_RECORD_TYPE_IPv4) {
+			if (is_ipv6 ||
+			    ip < acl->u.ipv4.min || acl->u.ipv4.max < ip)
+				continue;
+		} else {
+			if (!is_ipv6 ||
+			    memcmp(acl->u.ipv6.min, address, 16) > 0 ||
+			    memcmp(address, acl->u.ipv6.max, 16) > 0)
+				continue;
+		}
+		ccs_update_condition(ptr);
+		found = true;
+		break;
+	}
+	memset(buf, 0, sizeof(buf));
+	if (is_ipv6)
+		ccs_print_ipv6(buf, sizeof(buf),
+			       (const struct in6_addr *) address);
+	else
+		snprintf(buf, sizeof(buf) - 1, "%u.%u.%u.%u", HIPQUAD(ip));
+	audit_network_log(is_ipv6, keyword, buf, port, found, profile, mode);
+	if (found)
+		return 0;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: %s to %s %u denied for %s\n",
+		       ccs_get_msg(is_enforce), keyword, buf, port,
+		       ccs_get_last_name(domain));
+	if (is_enforce)
+		return ccs_check_supervisor("%s\n" KEYWORD_ALLOW_NETWORK "%s "
+					    "%s %u\n", domain->domainname->name,
+					    keyword, buf, port);
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_network_entry(operation, is_ipv6 ?
+				     IP_RECORD_TYPE_IPv6 : IP_RECORD_TYPE_IPv4,
+				     NULL, address, address, port, port, domain,
+				     NULL, 0);
+	return 0;
+}
+
+/**
+ * ccs_write_network_policy - Write "struct ip_network_acl_record" list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_network_policy(char *data, struct domain_info *domain,
+			     const struct condition_list *condition,
+			     const bool is_delete)
+{
+	u8 sock_type;
+	u8 operation;
+	u8 record_type;
+	u16 min_address[8];
+	u16 max_address[8];
+	struct address_group_entry *group = NULL;
+	u16 min_port;
+	u16 max_port;
+	u8 count;
+	char *cp1 = strchr(data, ' ');
+	char *cp2;
+	if (!cp1)
+		goto out;
+	cp1++;
+	if (!strncmp(data, "TCP ", 4))
+		sock_type = SOCK_STREAM;
+	else if (!strncmp(data, "UDP ", 4))
+		sock_type = SOCK_DGRAM;
+	else if (!strncmp(data, "RAW ", 4))
+		sock_type = SOCK_RAW;
+	else
+		goto out;
+	cp2 = strchr(cp1, ' ');
+	if (!cp2)
+		goto out;
+	cp2++;
+	if (!strncmp(cp1, "bind ", 5))
+		switch (sock_type) {
+		case SOCK_STREAM:
+			operation = NETWORK_ACL_TCP_BIND;
+			break;
+		case SOCK_DGRAM:
+			operation = NETWORK_ACL_UDP_BIND;
+			break;
+		default:
+			operation = NETWORK_ACL_RAW_BIND;
+		}
+	else if (!strncmp(cp1, "connect ", 8))
+		switch (sock_type) {
+		case SOCK_STREAM:
+			operation = NETWORK_ACL_TCP_CONNECT;
+			break;
+		case SOCK_DGRAM:
+			operation = NETWORK_ACL_UDP_CONNECT;
+			break;
+		default:
+			operation = NETWORK_ACL_RAW_CONNECT;
+		}
+	else if (sock_type == SOCK_STREAM && !strncmp(cp1, "listen ", 7))
+		operation = NETWORK_ACL_TCP_LISTEN;
+	else if (sock_type == SOCK_STREAM && !strncmp(cp1, "accept ", 7))
+		operation = NETWORK_ACL_TCP_ACCEPT;
+	else
+		goto out;
+	cp1 = strchr(cp2, ' ');
+	if (!cp1)
+		goto out;
+	*cp1++ = '\0';
+	count = sscanf(cp2, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx"
+		       "-%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
+		       &min_address[0], &min_address[1],
+		       &min_address[2], &min_address[3],
+		       &min_address[4], &min_address[5],
+		       &min_address[6], &min_address[7],
+		       &max_address[0], &max_address[1],
+		       &max_address[2], &max_address[3],
+		       &max_address[4], &max_address[5],
+		       &max_address[6], &max_address[7]);
+	if (count == 8 || count == 16) {
+		u8 i;
+		for (i = 0; i < 8; i++) {
+			min_address[i] = htons(min_address[i]);
+			max_address[i] = htons(max_address[i]);
+		}
+		if (count == 8)
+			memmove(max_address, min_address, sizeof(min_address));
+		record_type = IP_RECORD_TYPE_IPv6;
+		goto ok;
+	}
+	count = sscanf(cp2, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu",
+		       &min_address[0], &min_address[1],
+		       &min_address[2], &min_address[3],
+		       &max_address[0], &max_address[1],
+		       &max_address[2], &max_address[3]);
+	if (count == 4 || count == 8) {
+		u32 ip = htonl((((u8) min_address[0]) << 24)
+			       + (((u8) min_address[1]) << 16)
+			       + (((u8) min_address[2]) << 8)
+			       + (u8) min_address[3]);
+		*(u32 *) min_address = ip;
+		if (count == 8)
+			ip = htonl((((u8) max_address[0]) << 24)
+				   + (((u8) max_address[1]) << 16)
+				   + (((u8) max_address[2]) << 8)
+				   + (u8) max_address[3]);
+		*(u32 *) max_address = ip;
+		record_type = IP_RECORD_TYPE_IPv4;
+		goto ok;
+	}
+	if (*cp2 == '@') {
+		group = find_or_assign_new_address_group(cp2 + 1);
+		if (!group)
+			return -ENOMEM;
+		record_type = IP_RECORD_TYPE_ADDRESS_GROUP;
+		goto ok;
+	}
+ out:
+	return -EINVAL;
+ ok:
+	if (strchr(cp1, ' '))
+		goto out;
+	count = sscanf(cp1, "%hu-%hu", &min_port, &max_port);
+	if (count != 1 && count != 2)
+		goto out;
+	if (count == 1)
+		max_port = min_port;
+	return update_network_entry(operation, record_type, group,
+				    (u32 *) min_address, (u32 *) max_address,
+				    min_port, max_port, domain, condition,
+				    is_delete);
+}
+
+/**
+ * ccs_check_network_listen_acl - Check permission for listen() operation.
+ *
+ * @is_ipv6: True if @address is an IPv6 address.
+ * @address: An IPv4 or IPv6 address.
+ * @port:    Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_listen_acl(const bool is_ipv6, const u8 *address,
+				 const u16 port)
+{
+	return check_network_entry(is_ipv6, NETWORK_ACL_TCP_LISTEN,
+				   (const u32 *) address, ntohs(port));
+}
+
+/**
+ * ccs_check_network_connect_acl - Check permission for connect() operation.
+ *
+ * @is_ipv6:   True if @address is an IPv6 address.
+ * @sock_type: Type of socket. (TCP or UDP or RAW)
+ * @address:   An IPv4 or IPv6 address.
+ * @port:      Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_connect_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port)
+{
+	u8 operation;
+	switch (sock_type) {
+	case SOCK_STREAM:
+		operation = NETWORK_ACL_TCP_CONNECT;
+		break;
+	case SOCK_DGRAM:
+		operation = NETWORK_ACL_UDP_CONNECT;
+		break;
+	default:
+		operation = NETWORK_ACL_RAW_CONNECT;
+	}
+	return check_network_entry(is_ipv6, operation, (const u32 *) address,
+				   ntohs(port));
+}
+
+/**
+ * ccs_check_network_bind_acl - Check permission for bind() operation.
+ *
+ * @is_ipv6:   True if @address is an IPv6 address.
+ * @sock_type: Type of socket. (TCP or UDP or RAW)
+ * @address:   An IPv4 or IPv6 address.
+ * @port:      Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_bind_acl(const bool is_ipv6, const int sock_type,
+			       const u8 *address, const u16 port)
+{
+	u8 operation;
+	switch (sock_type) {
+	case SOCK_STREAM:
+		operation = NETWORK_ACL_TCP_BIND;
+		break;
+	case SOCK_DGRAM:
+		operation = NETWORK_ACL_UDP_BIND;
+		break;
+	default:
+		operation = NETWORK_ACL_RAW_BIND;
+	}
+	return check_network_entry(is_ipv6, operation, (const u32 *) address,
+				   ntohs(port));
+}
+
+/**
+ * ccs_check_network_accept_acl - Check permission for accept() operation.
+ *
+ * @is_ipv6: True if @address is an IPv6 address.
+ * @address: An IPv4 or IPv6 address.
+ * @port:    Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_accept_acl(const bool is_ipv6, const u8 *address,
+				 const u16 port)
+{
+	int retval;
+	current->tomoyo_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+	retval = check_network_entry(is_ipv6, NETWORK_ACL_TCP_ACCEPT,
+				     (const u32 *) address, ntohs(port));
+	current->tomoyo_flags &= ~CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+	return retval;
+}
+
+/**
+ * ccs_check_network_sendmsg_acl - Check permission for sendmsg() operation.
+ *
+ * @is_ipv6:   True if @address is an IPv6 address.
+ * @sock_type: Type of socket. (UDP or RAW)
+ * @address:   An IPv4 or IPv6 address.
+ * @port:      Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_sendmsg_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port)
+{
+	u8 operation;
+	if (sock_type == SOCK_DGRAM)
+		operation = NETWORK_ACL_UDP_CONNECT;
+	else
+		operation = NETWORK_ACL_RAW_CONNECT;
+	return check_network_entry(is_ipv6, operation, (const u32 *) address,
+				   ntohs(port));
+}
+
+/**
+ * ccs_check_network_recvmsg_acl - Check permission for recvmsg() operation.
+ *
+ * @is_ipv6:   True if @address is an IPv6 address.
+ * @sock_type: Type of socket. (UDP or RAW)
+ * @address:   An IPv4 or IPv6 address.
+ * @port:      Port number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_network_recvmsg_acl(const bool is_ipv6, const int sock_type,
+				  const u8 *address, const u16 port)
+{
+	int retval;
+	const u8 operation
+		= (sock_type == SOCK_DGRAM) ?
+		NETWORK_ACL_UDP_CONNECT : NETWORK_ACL_RAW_CONNECT;
+	current->tomoyo_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+	retval = check_network_entry(is_ipv6, operation, (const u32 *) address,
+				     ntohs(port));
+	current->tomoyo_flags &= ~CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+	return retval;
+}

-- 

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

* [TOMOYO #7 21/30] Capability restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (19 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 20/30] Socket operation restriction part Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 22/30] Conditional ACL support functions Tetsuo Handa
                   ` (9 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-capability-restriction-part.patch --]
[-- Type: text/plain, Size: 8674 bytes --]

TOMOYO Linux uses per-a-domain capability, an approach that associate
capabilities with each domain, and assign a domain for each process.
The advantages of this approach are that

(1) Minimum capabilities required for a process is directly given
    compared to setting/clearing/inheriting bit fields of a process's
    capability value.

(2) It can support as many types of capabilities as you want.
    Regarding per-a-task capability, you need to allocate memory for
    individual "task_struct". Thus, the number of supportable capabilities
    is bound by the width of variable in bits (i.e. 32 or 64).
    Regarding per-a-domain capability, you need to allocate memory for
    individual domains. But you can allocate memory dynamically.
    Thus, the number of supportable capabilities is not bounded to 32 or 64.

I can support up to 65536 types of capability,
but currently there are not so many capability hooks inserted.
Adding more capability hooks is a future work.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_capability.c |  223 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 223 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_capability.c
@@ -0,0 +1,223 @@
+/*
+ * fs/tomoyo_capability.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+
+/**
+ * cap_operation2name - Convert capability operation to capability message.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of capability.
+ */
+static const char *cap_operation2name(const u8 operation)
+{
+	static const char *capability_name[TOMOYO_MAX_CAPABILITY_INDEX] = {
+		[TOMOYO_INET_STREAM_SOCKET_CREATE]  =
+		"socket(PF_INET, SOCK_STREAM)",
+		[TOMOYO_INET_STREAM_SOCKET_LISTEN]  =
+		"listen(PF_INET, SOCK_STREAM)",
+		[TOMOYO_INET_STREAM_SOCKET_CONNECT] =
+		"connect(PF_INET, SOCK_STREAM)",
+		[TOMOYO_USE_INET_DGRAM_SOCKET]      =
+		"socket(PF_INET, SOCK_DGRAM)",
+		[TOMOYO_USE_INET_RAW_SOCKET]        =
+		"socket(PF_INET, SOCK_RAW)",
+		[TOMOYO_USE_ROUTE_SOCKET]           = "socket(PF_ROUTE)",
+		[TOMOYO_USE_PACKET_SOCKET]          = "socket(PF_PACKET)",
+		[TOMOYO_SYS_MOUNT]                  = "sys_mount()",
+		[TOMOYO_SYS_UMOUNT]                 = "sys_umount()",
+		[TOMOYO_SYS_REBOOT]                 = "sys_reboot()",
+		[TOMOYO_SYS_CHROOT]                 = "sys_chroot()",
+		[TOMOYO_SYS_KILL]                   = "sys_kill()",
+		[TOMOYO_SYS_VHANGUP]                = "sys_vhangup()",
+		[TOMOYO_SYS_SETTIME]                = "sys_settimeofday()",
+		[TOMOYO_SYS_NICE]                   = "sys_nice()",
+		[TOMOYO_SYS_SETHOSTNAME]            = "sys_sethostname()",
+		[TOMOYO_USE_KERNEL_MODULE]          = "kernel_module",
+		[TOMOYO_CREATE_FIFO]                = "mknod(FIFO)",
+		[TOMOYO_CREATE_BLOCK_DEV]           = "mknod(BDEV)",
+		[TOMOYO_CREATE_CHAR_DEV]            = "mknod(CDEV)",
+		[TOMOYO_CREATE_UNIX_SOCKET]         = "mknod(SOCKET)",
+		[TOMOYO_SYS_LINK]                   = "sys_link()",
+		[TOMOYO_SYS_SYMLINK]                = "sys_symlink()",
+		[TOMOYO_SYS_RENAME]                 = "sys_rename()",
+		[TOMOYO_SYS_UNLINK]                 = "sys_unlink()",
+		[TOMOYO_SYS_CHMOD]                  = "sys_chmod()",
+		[TOMOYO_SYS_CHOWN]                  = "sys_chown()",
+		[TOMOYO_SYS_IOCTL]                  = "sys_ioctl()",
+		[TOMOYO_SYS_KEXEC_LOAD]             = "sys_kexec_load()",
+		[TOMOYO_SYS_PIVOT_ROOT]             = "sys_pivot_root()",
+		[TOMOYO_SYS_PTRACE]                 = "sys_ptrace()",
+	};
+	if (operation < TOMOYO_MAX_CAPABILITY_INDEX)
+		return capability_name[operation];
+	return NULL;
+}
+
+/**
+ * audit_capability_log - Audit capability log.
+ *
+ * @operation:  Type of operation.
+ * @is_granted: True if this is a granted log.
+ * @profile:    Profile number used.
+ * @mode:       Access control mode used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_capability_log(const u8 operation, const bool is_granted,
+				const u8 profile, const u8 mode)
+{
+	char *buf;
+	int len = 64;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, KEYWORD_ALLOW_CAPABILITY "%s\n",
+		 ccs_cap2keyword(operation));
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/**
+ * update_capability_acl - Update "struct capability_acl_record" list.
+ *
+ * @operation: Type of operation.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_capability_acl(const u8 operation, struct domain_info *domain,
+				 const struct condition_list *condition,
+				 const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct capability_acl_record *acl;
+	int error = -ENOMEM;
+	if (!domain)
+		return -EINVAL;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_CAPABILITY_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct capability_acl_record, head);
+		if (acl->operation != operation)
+			continue;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_CAPABILITY_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->operation = operation;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_CAPABILITY_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct capability_acl_record, head);
+		if (acl->operation != operation)
+			continue;
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * ccs_capable - Check permission for capability.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_capable(const u8 operation)
+{
+	struct domain_info * const domain = current->domain_info;
+	struct acl_info *ptr;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_capability_flags(operation);
+	const bool is_enforce = (mode == 3);
+	bool found = false;
+	if (!mode)
+		return true;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct capability_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_CAPABILITY_ACL)
+			continue;
+		acl = container_of(ptr, struct capability_acl_record, head);
+		if (acl->operation != operation ||
+		    !ccs_check_condition(ptr, NULL))
+			continue;
+		ccs_update_condition(ptr);
+		found = true;
+		break;
+	}
+	audit_capability_log(operation, found, profile, mode);
+	if (found)
+		return true;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: %s denied for %s\n",
+		       ccs_get_msg(is_enforce), cap_operation2name(operation),
+		       ccs_get_last_name(domain));
+	if (is_enforce)
+		return !ccs_check_supervisor("%s\n"
+					    KEYWORD_ALLOW_CAPABILITY "%s\n",
+					    domain->domainname->name,
+					    ccs_cap2keyword(operation));
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_capability_acl(operation, domain, NULL, false);
+	return true;
+}
+EXPORT_SYMBOL(ccs_capable); /* for net/unix/af_unix.c */
+
+/**
+ * ccs_write_capability_policy - Write "struct capability_acl_record" list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_capability_policy(char *data, struct domain_info *domain,
+				const struct condition_list *condition,
+				const bool is_delete)
+{
+	u8 capability;
+	for (capability = 0; capability < TOMOYO_MAX_CAPABILITY_INDEX;
+	     capability++) {
+		if (strcmp(data, ccs_cap2keyword(capability)))
+			continue;
+		return update_capability_acl(capability, domain, condition,
+					     is_delete);
+	}
+	return -EINVAL;
+}

-- 

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

* [TOMOYO #7 22/30] Conditional ACL support functions.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (20 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 21/30] Capability " Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 23/30] argvrestriction part Tetsuo Handa
                   ` (8 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-conditional-acl-part.patch --]
[-- Type: text/plain, Size: 33966 bytes --]

TOMOYO supports conditional permissions based on
process's UID,GID etc. and/or requested pathname's UID/GID.
Regarding execve(), you can use argv[] and envp[] conditions.


Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_cond.c | 1380 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1380 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_cond.c
@@ -0,0 +1,1380 @@
+/*
+ * fs/tomoyo_cond.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/realpath.h>
+
+#include <linux/tomoyo.h>
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+
+/* Structure for argv[]. */
+struct argv_entry {
+	unsigned int index;
+	const struct path_info *value;
+	bool is_not;
+};
+
+/* Structure for envp[]. */
+struct envp_entry {
+	const struct path_info *name;
+	const struct path_info *value;
+	bool is_not;
+};
+
+/**
+ * check_argv - Check argv[] in "struct linux_binbrm".
+ *
+ * @index:   Index number of @arg_ptr.
+ * @arg_ptr: Contents of argv[@index].
+ * @argc:    Length of @argv.
+ * @argv:    Pointer to "struct argv_entry".
+ * @checked: Set to true if @argv[@index] was found.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool check_argv(const unsigned int index, const char *arg_ptr,
+		       const int argc, const struct argv_entry *argv,
+		       u8 *checked)
+{
+	int i;
+	struct path_info arg;
+	arg.name = arg_ptr;
+	for (i = 0; i < argc; argv++, checked++, i++) {
+		bool result;
+		if (index != argv->index)
+			continue;
+		*checked = 1;
+		ccs_fill_path_info(&arg);
+		result = ccs_path_matches_pattern(&arg, argv->value);
+		if (argv->is_not)
+			result = !result;
+		if (!result)
+			return false;
+	}
+	return true;
+}
+
+/**
+ * check_envp - Check envp[] in "struct linux_binbrm".
+ *
+ * @env_name:  The name of environment variable.
+ * @env_value: The value of environment variable.
+ * @envc:      Length of @envp.
+ * @envp:      Pointer to "struct envp_entry".
+ * @checked:   Set to true if @envp[@env_name] was found.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool check_envp(const char *env_name, const char *env_value,
+		       const int envc, const struct envp_entry *envp,
+		       u8 *checked)
+{
+	int i;
+	struct path_info name;
+	struct path_info value;
+	name.name = env_name;
+	ccs_fill_path_info(&name);
+	value.name = env_value;
+	ccs_fill_path_info(&value);
+	for (i = 0; i < envc; envp++, checked++, i++) {
+		bool result;
+		if (!ccs_path_matches_pattern(&name, envp->name))
+			continue;
+		*checked = 1;
+		if (envp->value) {
+			result = ccs_path_matches_pattern(&value, envp->value);
+			if (envp->is_not)
+				result = !result;
+		} else {
+			result = true;
+			if (!envp->is_not)
+				result = !result;
+		}
+		if (!result)
+			return false;
+	}
+	return true;
+}
+
+/**
+ * scan_bprm - Scan "struct linux_binprm".
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @argc: Length of @argc.
+ * @argv: Pointer to "struct argv_entry".
+ * @envc: Length of @envp.
+ * @envp: Poiner to "struct envp_entry".
+ * @tmp:  Buffer for temporal use.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool scan_bprm(const struct linux_binprm *bprm,
+		      const u16 argc, const struct argv_entry *argv,
+		      const u16 envc, const struct envp_entry *envp,
+		      struct ccs_page_buffer *tmp)
+{
+	/*
+	  if exec.argc=3
+	  if (argc == 3)
+	  if exec.argv[1]="-c"
+	  if (argc >= 2 && !strcmp(argv[1], "-c"))
+	  if exec.argv[1]!="-c"
+	  if (argc < 2 || strcmp(argv[1], "-c"))
+	  if exec.envc=10-20
+	  if (envc >= 10 && envc <= 20)
+	  if exec.envc!=10-20
+	  if (envc < 10 || envc > 20)
+	  if exec.envp["HOME"]!=NULL
+	  if (getenv("HOME"))
+	  if exec.envp["HOME"]=NULL
+	  if (!getenv("HOME"))
+	  if exec.envp["HOME"]="/"
+	  if (getenv("HOME") && !strcmp(getenv("HOME"), "/"))
+	  if exec.envp["HOME"]!="/"
+	  if (!getenv("HOME") || strcmp(getenv("HOME", "/"))
+	*/
+	char *arg_ptr = tmp->buffer;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+	int argv_count = bprm->argc;
+	int envp_count = bprm->envc;
+	bool result = true;
+	u8 local_checked[32];
+	u8 *checked;
+	if (argc + envc <= sizeof(local_checked)) {
+		checked = local_checked;
+		memset(local_checked, 0, sizeof(local_checked));
+	} else {
+		checked = ccs_alloc(argc + envc);
+		if (!checked)
+			return false;
+	}
+	while (argv_count || envp_count) {
+		struct page *page;
+		const char *kaddr;
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page,
+				   NULL) <= 0) {
+			printk(KERN_DEBUG "get_user_pages() failed\n");
+			result = false;
+			goto out;
+		}
+		pos += PAGE_SIZE - offset;
+#else
+		page = bprm->page[i];
+#endif
+		/* Map. */
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			printk(KERN_DEBUG "kmap() failed\n");
+			result = false;
+			goto out;
+		}
+		while (offset < PAGE_SIZE) {
+			/* Read. */
+			struct path_info arg;
+			const unsigned char c = kaddr[offset++];
+			arg.name = arg_ptr;
+			if (c && arg_len < CCS_MAX_PATHNAME_LEN - 10) {
+				if (c == '\\') {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = '\\';
+				} else if (c > ' ' && c < 127) {
+					arg_ptr[arg_len++] = c;
+				} else {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = (c >> 6) + '0';
+					arg_ptr[arg_len++] =
+						((c >> 3) & 7) + '0';
+					arg_ptr[arg_len++] = (c & 7) + '0';
+				}
+			} else {
+				arg_ptr[arg_len] = '\0';
+			}
+			if (c)
+				continue;
+			/* Check. */
+			if (argv_count) {
+				if (!check_argv(bprm->argc - argv_count,
+						arg_ptr, argc, argv,
+						checked)) {
+					result = false;
+					break;
+				}
+				argv_count--;
+			} else if (envp_count) {
+				char *cp = strchr(arg_ptr, '=');
+				if (cp) {
+					*cp = '\0';
+					if (!check_envp(arg_ptr, cp + 1,
+							envc, envp,
+							checked + argc)) {
+						result = false;
+						break;
+					}
+				}
+				envp_count--;
+			} else {
+				break;
+			}
+			arg_len = 0;
+		}
+		/* Unmap. */
+		kunmap(page);
+#ifdef CONFIG_MMU
+		put_page(page);
+#endif
+		i++;
+		offset = 0;
+		if (!result)
+			break;
+	}
+ out:
+	if (result) {
+		/* Check not-yet-checked entries. */
+		for (i = 0; i < argc; i++) {
+			if (checked[i])
+				continue;
+			/*
+			 * Return true only if all unchecked indexes in
+			 * bprm->argv[] are not matched.
+			 */
+			if (argv[i].is_not)
+				continue;
+			result = false;
+			break;
+		}
+		for (i = 0; i < envc; envp++, i++) {
+			if (checked[argc + i])
+				continue;
+			/*
+			 * Return true only if all unchecked environ variables
+			 * in bprm->envp[] are either undefined or not matched.
+			 */
+			if ((!envp->value && !envp->is_not) ||
+			    (envp->value && envp->is_not))
+				continue;
+			result = false;
+			break;
+		}
+	}
+	if (checked != local_checked)
+		ccs_free(checked);
+	return result;
+}
+
+/* Value type definition. */
+#define VALUE_TYPE_DECIMAL     1
+#define VALUE_TYPE_OCTAL       2
+#define VALUE_TYPE_HEXADECIMAL 3
+
+/**
+ * parse_ulong - Parse an "unsigned long" value.
+ *
+ * @result: Pointer to "unsigned long".
+ * @str:    Pointer to string to parse.
+ *
+ * Returns value type on success, 0 otherwise.
+ *
+ * The @src is updated to point the first character after the value
+ * on success.
+ */
+static u8 parse_ulong(unsigned long *result, char **str)
+{
+	const char *cp = *str;
+	char *ep;
+	int base = 10;
+	if (*cp == '0') {
+		char c = *(cp + 1);
+		if (c == 'x' || c == 'X') {
+			base = 16;
+			cp += 2;
+		} else if (c >= '0' && c <= '7') {
+			base = 8;
+			cp++;
+		}
+	}
+	*result = simple_strtoul(cp, &ep, base);
+	if (cp == ep)
+		return 0;
+	*str = ep;
+	switch (base) {
+	case 16:
+		return VALUE_TYPE_HEXADECIMAL;
+	case 8:
+		return VALUE_TYPE_OCTAL;
+	default:
+		return VALUE_TYPE_DECIMAL;
+	}
+}
+
+
+/**
+ * print_ulong - Print an "unsigned long" value.
+ *
+ * @buffer:     Pointer to buffer.
+ * @buffer_len: Size of @buffer.
+ * @value:      An "unsigned long" value.
+ * @type:       Type of @value.
+ *
+ * Returns nothing.
+ */
+static void print_ulong(char *buffer, const int buffer_len,
+			const unsigned long value, const int type)
+{
+	if (type == VALUE_TYPE_DECIMAL)
+		snprintf(buffer, buffer_len, "%lu", value);
+	else if (type == VALUE_TYPE_OCTAL)
+		snprintf(buffer, buffer_len, "0%lo", value);
+	else
+		snprintf(buffer, buffer_len, "0x%lX", value);
+}
+
+/* Structure for " if " and "; set" part. */
+struct condition_list {
+	struct list1_head list;
+	u16 condc;
+	u16 argc;
+	u16 envc;
+	u8 post_state[4];
+	/* "unsigned long condition[condc]" follows here. */
+	/* "struct argv_entry argv[argc]" follows here. */
+	/* "struct envp_entry envp[envc]" follows here. */
+};
+
+/**
+ * parse_argv - Parse an argv[] condition part.
+ *
+ * @start: String to parse.
+ * @argv:  Pointer to "struct argv_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool parse_argv(char *start, struct argv_entry *argv)
+{
+	unsigned long index;
+	const struct path_info *value;
+	bool is_not;
+	char c;
+	char *cp;
+	start += 10;
+	if (parse_ulong(&index, &start) != VALUE_TYPE_DECIMAL)
+		goto out;
+	if (*start++ != ']')
+		goto out;
+	c = *start++;
+	if (c == '=')
+		is_not = false;
+	else if (c == '!' && *start++ == '=')
+		is_not = true;
+	else
+		goto out;
+	if (*start++ != '"')
+		goto out;
+	cp = strchr(start, '\0') - 1;
+	if (cp < start || *cp != '"')
+		goto out;
+	*cp = '\0';
+	if (!ccs_is_correct_path(start, 0, 0, 0, __func__))
+		goto out;
+	value = ccs_save_name(start);
+	if (!value)
+		goto out;
+	argv->index = index;
+	argv->is_not = is_not;
+	argv->value = value;
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * parse_envp - Parse an envp[] condition part.
+ *
+ * @start: String to parse.
+ * @envp:  Pointer to "struct envp_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool parse_envp(char *start, struct envp_entry *envp)
+{
+	const struct path_info *name;
+	const struct path_info *value;
+	bool is_not;
+	char *cp;
+	start += 11;
+	cp = start;
+	/*
+	 * Since environment variable names don't
+	 * contain '=', I can treat '"]=' and '"]!='
+	 * sequences as delimiters.
+	 */
+	while (1) {
+		if (!strncmp(start, "\"]=", 3)) {
+			is_not = false;
+			*start = '\0';
+			start += 3;
+			break;
+		} else if (!strncmp(start, "\"]!=", 4)) {
+			is_not = true;
+			*start = '\0';
+			start += 4;
+			break;
+		} else if (!*start++) {
+			goto out;
+		}
+	}
+	if (!*cp || !ccs_is_correct_path(cp, 0, 0, 0, __func__))
+		goto out;
+	name = ccs_save_name(cp);
+	if (!name)
+		goto out;
+	if (!strcmp(start, "NULL")) {
+		value = NULL;
+	} else {
+		if (*start++ != '"')
+			goto out;
+		cp = strchr(start, '\0') - 1;
+		if (cp < start || *cp != '"')
+			goto out;
+		*cp = '\0';
+		if (!ccs_is_correct_path(start, 0, 0, 0, __func__))
+			goto out;
+		value = ccs_save_name(start);
+		if (!value)
+			goto out;
+	}
+	envp->name = name;
+	envp->is_not = is_not;
+	envp->value = value;
+	return true;
+ out:
+	return false;
+}
+
+/* The list for "struct condition_list". */
+static LIST1_HEAD(condition_list);
+
+#define TASK_UID          0
+#define TASK_EUID         1
+#define TASK_SUID         2
+#define TASK_FSUID        3
+#define TASK_GID          4
+#define TASK_EGID         5
+#define TASK_SGID         6
+#define TASK_FSGID        7
+#define TASK_PID          8
+#define TASK_PPID         9
+#define PATH1_UID        10
+#define PATH1_GID        11
+#define PATH1_INO        12
+#define PATH1_PARENT_UID 13
+#define PATH1_PARENT_GID 14
+#define PATH1_PARENT_INO 15
+#define PATH2_PARENT_UID 16
+#define PATH2_PARENT_GID 17
+#define PATH2_PARENT_INO 18
+#define EXEC_ARGC        19
+#define EXEC_ENVC        20
+#define TASK_STATE_0     21
+#define TASK_STATE_1     22
+#define TASK_STATE_2     23
+#define MAX_KEYWORD      24
+
+static struct {
+	const char *keyword;
+	const int keyword_len; /* strlen(keyword) */
+} condition_control_keyword[MAX_KEYWORD] = {
+	[TASK_UID]         = { "task.uid",           8 },
+	[TASK_EUID]        = { "task.euid",          9 },
+	[TASK_SUID]        = { "task.suid",          9 },
+	[TASK_FSUID]       = { "task.fsuid",        10 },
+	[TASK_GID]         = { "task.gid",           8 },
+	[TASK_EGID]        = { "task.egid",          9 },
+	[TASK_SGID]        = { "task.sgid",          9 },
+	[TASK_FSGID]       = { "task.fsgid",        10 },
+	[TASK_PID]         = { "task.pid",           8 },
+	[TASK_PPID]        = { "task.ppid",          9 },
+	[PATH1_UID]        = { "path1.uid",          9 },
+	[PATH1_GID]        = { "path1.gid",          9 },
+	[PATH1_INO]        = { "path1.ino",          9 },
+	[PATH1_PARENT_UID] = { "path1.parent.uid",  16 },
+	[PATH1_PARENT_GID] = { "path1.parent.gid",  16 },
+	[PATH1_PARENT_INO] = { "path1.parent.ino",  16 },
+	[PATH2_PARENT_UID] = { "path2.parent.uid",  16 },
+	[PATH2_PARENT_GID] = { "path2.parent.gid",  16 },
+	[PATH2_PARENT_INO] = { "path2.parent.ino",  16 },
+	[EXEC_ARGC]        = { "exec.argc",          9 },
+	[EXEC_ENVC]        = { "exec.envc",          9 },
+	[TASK_STATE_0]     = { "task.state[0]",     13 },
+	[TASK_STATE_1]     = { "task.state[1]",     13 },
+	[TASK_STATE_2]     = { "task.state[2]",     13 },
+};
+
+/**
+ * parse_post_condition - Parse post-condition part.
+ *
+ * @condition:  String to parse.
+ * @post_state: Buffer to store post-condition part.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool parse_post_condition(char * const condition, u8 post_state[4])
+{
+	char *start = strstr(condition, "; set ");
+	if (!start)
+		return true;
+	*start = '\0';
+	start += 6;
+	while (1) {
+		int i;
+		unsigned long value;
+		while (*start == ' ')
+			start++;
+		if (!*start)
+			break;
+		if (!strncmp(start, "task.state[0]=", 14))
+			i = 0;
+		else if (!strncmp(start, "task.state[1]=", 14))
+			i = 1;
+		else if (!strncmp(start, "task.state[2]=", 14))
+			i = 2;
+		else
+			goto out;
+		start += 14;
+		if (post_state[3] & (1 << i))
+			goto out;
+		post_state[3] |= 1 << i;
+		if (!parse_ulong(&value, &start) || value > 255)
+			goto out;
+		post_state[i] = (u8) value;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * find_same_condition - Search for same condition list.
+ *
+ * @new_ptr: Pointer to "struct condition_list".
+ * @size:    Size of @new_ptr.
+ *
+ * Returns existing pointer to "struct condition_list" if the same entry was
+ * found, NULL if memory allocation failed, @new_ptr otherwise.
+ */
+static struct condition_list *
+find_same_condition(struct condition_list *new_ptr, const u32 size)
+{
+	static DEFINE_MUTEX(lock);
+	struct condition_list *ptr;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &condition_list, list) {
+		/* Don't compare if size differs. */
+		if (ptr->condc != new_ptr->condc ||
+		    ptr->argc != new_ptr->argc ||
+		    ptr->envc != new_ptr->envc)
+			continue;
+		/*
+		 * Compare ptr and new_ptr
+		 * except ptr->list and new_ptr->list.
+		 */
+		if (memcmp(((u8 *) ptr) + sizeof(ptr->list),
+			   ((u8 *) new_ptr) + sizeof(new_ptr->list),
+			   size - sizeof(ptr->list)))
+			continue;
+		/* Same entry found. Share this entry. */
+		ccs_free(new_ptr);
+		new_ptr = ptr;
+		goto ok;
+	}
+	/* Same entry not found. Save this entry. */
+	ptr = ccs_alloc_element(size);
+	if (ptr) {
+		memmove(ptr, new_ptr, size);
+		/* Append to chain. */
+		list1_add_tail_mb(&ptr->list, &condition_list);
+	}
+	ccs_free(new_ptr);
+	new_ptr = ptr;
+ ok:
+	mutex_unlock(&lock);
+	return new_ptr;
+}
+
+/**
+ * ccs_find_or_assign_new_condition - Parse condition part.
+ *
+ * @condition: Pointer to string to parse.
+ *
+ * Returns pointer to "struct condition_list" on success, NULL otherwise.
+ */
+const struct condition_list *
+ccs_find_or_assign_new_condition(char * const condition)
+{
+	char *start = condition;
+	struct condition_list *new_ptr = NULL;
+	unsigned long *ptr;
+	struct argv_entry *argv;
+	struct envp_entry *envp;
+	u32 size;
+	u8 left, right, i;
+	unsigned long left_min = 0;
+	unsigned long left_max = 0;
+	unsigned long right_min = 0;
+	unsigned long right_max = 0;
+	u16 condc = 0;
+	u16 argc = 0;
+	u16 envc = 0;
+	u8 post_state[4] = { 0, 0, 0, 0 };
+	if (!parse_post_condition(start, post_state))
+		goto out;
+	start = condition;
+	if (!strncmp(start, "if ", 3))
+		start += 3;
+	else if (*start)
+		return NULL;
+	while (1) {
+		while (*start == ' ')
+			start++;
+		if (!*start)
+			break;
+		if (!strncmp(start, "exec.argv[", 10)) {
+			argc++;
+			start = strchr(start + 10, ' ');
+			if (!start)
+				break;
+			continue;
+		} else if (!strncmp(start, "exec.envp[\"", 11)) {
+			envc++;
+			start = strchr(start + 11, ' ');
+			if (!start)
+				break;
+			continue;
+		}
+		for (left = 0; left < MAX_KEYWORD; left++) {
+			const int len =
+				condition_control_keyword[left].keyword_len;
+			if (strncmp(start,
+				    condition_control_keyword[left].keyword,
+				    len))
+				continue;
+			start += len;
+			break;
+		}
+		if (left < MAX_KEYWORD)
+			goto check_operator_1;
+		if (!parse_ulong(&left_min, &start))
+			goto out;
+		condc++; /* body */
+		if (*start != '-')
+			goto check_operator_1;
+		start++;
+		if (!parse_ulong(&left_max, &start) || left_min > left_max)
+			goto out;
+		condc++; /* body */
+ check_operator_1:
+		if (strncmp(start, "!=", 2) == 0)
+			start += 2;
+		else if (*start == '=')
+			start++;
+		else
+			goto out;
+		condc++; /* header */
+		for (right = 0; right < MAX_KEYWORD; right++) {
+			const int len =
+				condition_control_keyword[right].keyword_len;
+			if (strncmp(start,
+				    condition_control_keyword[right].keyword,
+				    len))
+				continue;
+			start += len;
+			break;
+		}
+		if (right < MAX_KEYWORD)
+			continue;
+		if (!parse_ulong(&right_min, &start))
+			goto out;
+		condc++; /* body */
+		if (*start != '-')
+			continue;
+		start++;
+		if (!parse_ulong(&right_max, &start) || right_min > right_max)
+			goto out;
+		condc++; /* body */
+	}
+	size = sizeof(*new_ptr)
+		+ condc * sizeof(unsigned long)
+		+ argc * sizeof(struct argv_entry)
+		+ envc * sizeof(struct envp_entry);
+	new_ptr = ccs_alloc(size);
+	if (!new_ptr)
+		return NULL;
+	for (i = 0; i < 4; i++)
+		new_ptr->post_state[i] = post_state[i];
+	new_ptr->condc = condc;
+	new_ptr->argc = argc;
+	new_ptr->envc = envc;
+	ptr = (unsigned long *) (new_ptr + 1);
+	argv = (struct argv_entry *) (ptr + condc);
+	envp = (struct envp_entry *) (argv + argc);
+	start = condition;
+	if (!strncmp(start, "if ", 3))
+		start += 3;
+	else if (*start)
+		goto out;
+	while (1) {
+		u8 match = 0;
+		u8 left_1_type = 0;
+		u8 left_2_type = 0;
+		u8 right_1_type = 0;
+		u8 right_2_type = 0;
+		while (*start == ' ')
+			start++;
+		if (!*start)
+			break;
+		if (!strncmp(start, "exec.argv[", 10)) {
+			char *cp = strchr(start + 10, ' ');
+			if (cp)
+				*cp = '\0';
+			if (!parse_argv(start, argv))
+				goto out;
+			argv++;
+			argc--;
+			if (cp)
+				*cp = ' ';
+			else
+				break;
+			start = cp;
+			continue;
+		} else if (!strncmp(start, "exec.envp[\"", 11)) {
+			char *cp = strchr(start + 11, ' ');
+			if (cp)
+				*cp = '\0';
+			if (!parse_envp(start, envp))
+				goto out;
+			envp++;
+			envc--;
+			if (cp)
+				*cp = ' ';
+			else
+				break;
+			start = cp;
+			continue;
+		}
+		for (left = 0; left < MAX_KEYWORD; left++) {
+			const int len =
+				condition_control_keyword[left].keyword_len;
+			if (strncmp(start,
+				    condition_control_keyword[left].keyword,
+				    len))
+				continue;
+			start += len;
+			break;
+		}
+		if (left < MAX_KEYWORD)
+			goto check_operator_2;
+		left_1_type = parse_ulong(&left_min, &start);
+		condc--; /* body */
+		if (*start != '-')
+			goto check_operator_2;
+		start++;
+		left_2_type = parse_ulong(&left_max, &start);
+		condc--; /* body */
+		left++;
+ check_operator_2:
+		if (!strncmp(start, "!=", 2)) {
+			start += 2;
+		} else if (*start == '=') {
+			match |= 1;
+			start++;
+		} else {
+			break; /* This shouldn't happen. */
+		}
+		condc--; /* header */
+		for (right = 0; right < MAX_KEYWORD; right++) {
+			const int len =
+				condition_control_keyword[right].keyword_len;
+			if (strncmp(start,
+				    condition_control_keyword[right].keyword,
+				    len))
+				continue;
+			start += len;
+			break;
+		}
+		if (right < MAX_KEYWORD)
+			goto store_value;
+		right_1_type = parse_ulong(&right_min, &start);
+		condc--; /* body */
+		if (*start != '-')
+			goto store_value;
+		start++;
+		right_2_type = parse_ulong(&right_max, &start);
+		condc--; /* body */
+		right++;
+ store_value:
+		*ptr = (((u32) match) << 16) |
+			(((u32) left_1_type) << 18) |
+			(((u32) left_2_type) << 20) |
+			(((u32) right_1_type) << 22) |
+			(((u32) right_2_type) << 24) |
+			(((u32) left) << 8) |
+			((u32) right);
+		ptr++;
+		if (left >= MAX_KEYWORD) {
+			*ptr = left_min;
+			ptr++;
+		}
+		if (left == MAX_KEYWORD + 1) {
+			*ptr = left_max;
+			ptr++;
+		}
+		if (right >= MAX_KEYWORD) {
+			*ptr = right_min;
+			ptr++;
+		}
+		if (right == MAX_KEYWORD + 1) {
+			*ptr = right_max;
+			ptr++;
+		}
+	}
+	/*
+	  printk(KERN_DEBUG "argc=%u envc=%u condc=%u\n", argc, envc, condc);
+	*/
+	BUG_ON(argc);
+	BUG_ON(envc);
+	BUG_ON(condc);
+	return find_same_condition(new_ptr, size);
+ out:
+	ccs_free(new_ptr);
+	return NULL;
+}
+
+/**
+ * get_attributes - Revalidate "struct inode".
+ *
+ * @obj: Pointer to "struct obj_info".
+ *
+ * Returns nothing.
+ */
+static void get_attributes(struct obj_info *obj)
+{
+	struct vfsmount *mnt;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct kstat stat;
+
+	mnt = obj->path1_vfsmnt;
+	dentry = obj->path1_dentry;
+	inode = dentry->d_inode;
+	if (inode) {
+		if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+			/* Nothing to do. */
+		} else {
+			obj->path1_stat.uid = stat.uid;
+			obj->path1_stat.gid = stat.gid;
+			obj->path1_stat.ino = stat.ino;
+			obj->path1_valid = true;
+		}
+	}
+
+	dentry = dget_parent(obj->path1_dentry);
+	inode = dentry->d_inode;
+	if (inode) {
+		if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+			/* Nothing to do. */
+		} else {
+			obj->path1_parent_stat.uid = stat.uid;
+			obj->path1_parent_stat.gid = stat.gid;
+			obj->path1_parent_stat.ino = stat.ino;
+			obj->path1_parent_valid = true;
+		}
+	}
+	dput(dentry);
+
+	mnt = obj->path2_vfsmnt;
+	if (mnt) {
+		dentry = dget_parent(obj->path2_dentry);
+		inode = dentry->d_inode;
+		if (inode) {
+			if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+				/* Nothing to do. */
+			} else {
+				obj->path2_parent_stat.uid = stat.uid;
+				obj->path2_parent_stat.gid = stat.gid;
+				obj->path2_parent_stat.ino = stat.ino;
+				obj->path2_parent_valid = true;
+			}
+		}
+		dput(dentry);
+	}
+}
+
+/**
+ * ccs_check_condition - Check condition part.
+ *
+ * @acl: Pointer to "struct acl_info".
+ * @obj: Pointer to "struct obj_info". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_check_condition(const struct acl_info *acl, struct obj_info *obj)
+{
+	const struct task_struct *task = current;
+	u32 i;
+	unsigned long left_min = 0;
+	unsigned long left_max = 0;
+	unsigned long right_min = 0;
+	unsigned long right_max = 0;
+	const unsigned long *ptr;
+	const struct argv_entry *argv;
+	const struct envp_entry *envp;
+	u16 condc;
+	u16 argc;
+	u16 envc;
+	const struct condition_list *cond = ccs_get_condition_part(acl);
+	const struct linux_binprm *bprm;
+	if (!cond)
+		return true;
+	condc = cond->condc;
+	argc = cond->argc;
+	envc = cond->envc;
+	bprm = obj ? obj->bprm : NULL;
+	if (!bprm && (argc || envc))
+		return false;
+	ptr = (unsigned long *) (cond + 1);
+	argv = (const struct argv_entry *) (ptr + condc);
+	envp = (const struct envp_entry *) (argv + argc);
+	for (i = 0; i < condc; i++) {
+		const u32 header = *ptr;
+		const bool match = (header >> 16) & 1;
+		const u8 left = header >> 8;
+		const u8 right = header;
+		ptr++;
+		if ((left >= PATH1_UID && left < MAX_KEYWORD) ||
+		    (right >= PATH1_UID && right < MAX_KEYWORD)) {
+			if (!obj)
+				goto out;
+			if (!obj->validate_done) {
+				get_attributes(obj);
+				obj->validate_done = true;
+			}
+		}
+		switch (left) {
+		case TASK_UID:
+			left_max = task->uid;
+			break;
+		case TASK_EUID:
+			left_max = task->euid;
+			break;
+		case TASK_SUID:
+			left_max = task->suid;
+			break;
+		case TASK_FSUID:
+			left_max = task->fsuid;
+			break;
+		case TASK_GID:
+			left_max = task->gid;
+			break;
+		case TASK_EGID:
+			left_max = task->egid;
+			break;
+		case TASK_SGID:
+			left_max = task->sgid;
+			break;
+		case TASK_FSGID:
+			left_max = task->fsgid;
+			break;
+		case TASK_PID:
+			left_max = task->pid;
+			break;
+		case TASK_PPID:
+			left_max = sys_getppid();
+			break;
+		case PATH1_UID:
+			if (!obj->path1_valid)
+				goto out;
+			left_max = obj->path1_stat.uid;
+			break;
+		case PATH1_GID:
+			if (!obj->path1_valid)
+				goto out;
+			left_max = obj->path1_stat.gid;
+			break;
+		case PATH1_INO:
+			if (!obj->path1_valid)
+				goto out;
+			left_max = obj->path1_stat.ino;
+			break;
+		case PATH1_PARENT_UID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_max = obj->path1_parent_stat.uid;
+			break;
+		case PATH1_PARENT_GID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_max = obj->path1_parent_stat.gid;
+			break;
+		case PATH1_PARENT_INO:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_max = obj->path1_parent_stat.ino;
+			break;
+		case PATH2_PARENT_UID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_max = obj->path2_parent_stat.uid;
+			break;
+		case PATH2_PARENT_GID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_max = obj->path2_parent_stat.gid;
+			break;
+		case PATH2_PARENT_INO:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_max = obj->path2_parent_stat.ino;
+			break;
+		case EXEC_ARGC:
+			if (!bprm)
+				goto out;
+			left_max = bprm->argc;
+			i++;
+			break;
+		case EXEC_ENVC:
+			if (!bprm)
+				goto out;
+			left_max = bprm->envc;
+			i++;
+			break;
+		case TASK_STATE_0:
+			left_max = (u8) (task->tomoyo_flags >> 24);
+			break;
+		case TASK_STATE_1:
+			left_max = (u8) (task->tomoyo_flags >> 16);
+			break;
+		case TASK_STATE_2:
+			left_max = (u8) (task->tomoyo_flags >> 8);
+			break;
+		case MAX_KEYWORD:
+			left_max = *ptr;
+			ptr++;
+			i++;
+			break;
+		case MAX_KEYWORD + 1:
+			left_min = *ptr;
+			ptr++;
+			left_max = *ptr;
+			ptr++;
+			i += 2;
+			break;
+		}
+		if (left != MAX_KEYWORD + 1)
+			left_min = left_max;
+		switch (right) {
+		case TASK_UID:
+			right_max = task->uid;
+			break;
+		case TASK_EUID:
+			right_max = task->euid;
+			break;
+		case TASK_SUID:
+			right_max = task->suid;
+			break;
+		case TASK_FSUID:
+			right_max = task->fsuid;
+			break;
+		case TASK_GID:
+			right_max = task->gid;
+			break;
+		case TASK_EGID:
+			right_max = task->egid;
+			break;
+		case TASK_SGID:
+			right_max = task->sgid;
+			break;
+		case TASK_FSGID:
+			right_max = task->fsgid;
+			break;
+		case TASK_PID:
+			right_max = task->pid;
+			break;
+		case TASK_PPID:
+			right_max = sys_getppid();
+			break;
+		case PATH1_UID:
+			if (!obj->path1_valid)
+				goto out;
+			right_max = obj->path1_stat.uid;
+			break;
+		case PATH1_GID:
+			if (!obj->path1_valid)
+				goto out;
+			right_max = obj->path1_stat.gid;
+			break;
+		case PATH1_INO:
+			if (!obj->path1_valid)
+				goto out;
+			right_max = obj->path1_stat.ino;
+			break;
+		case PATH1_PARENT_UID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_max = obj->path1_parent_stat.uid;
+			break;
+		case PATH1_PARENT_GID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_max = obj->path1_parent_stat.gid;
+			break;
+		case PATH1_PARENT_INO:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_max = obj->path1_parent_stat.ino;
+			break;
+		case PATH2_PARENT_UID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_max = obj->path2_parent_stat.uid;
+			break;
+		case PATH2_PARENT_GID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_max = obj->path2_parent_stat.gid;
+			break;
+		case PATH2_PARENT_INO:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_max = obj->path2_parent_stat.ino;
+			break;
+		case EXEC_ARGC:
+			if (!bprm)
+				goto out;
+			right_max = bprm->argc;
+			i++;
+			break;
+		case EXEC_ENVC:
+			if (!bprm)
+				goto out;
+			right_max = bprm->envc;
+			i++;
+			break;
+		case TASK_STATE_0:
+			right_max = (u8) (task->tomoyo_flags >> 24);
+			break;
+		case TASK_STATE_1:
+			right_max = (u8) (task->tomoyo_flags >> 16);
+			break;
+		case TASK_STATE_2:
+			right_max = (u8) (task->tomoyo_flags >> 8);
+			break;
+		case MAX_KEYWORD:
+			right_max = *ptr;
+			ptr++;
+			i++;
+			break;
+		case MAX_KEYWORD + 1:
+			right_min = *ptr;
+			ptr++;
+			right_max = *ptr;
+			ptr++;
+			i += 2;
+			break;
+		}
+		if (right != MAX_KEYWORD + 1)
+			right_min = right_max;
+		if (match) {
+			if (left_min <= right_max && left_max >= right_min)
+				continue;
+		} else {
+			if (left_min > right_max || left_max < right_min)
+				continue;
+		}
+ out:
+		return false;
+	}
+	if (bprm && (argc || envc))
+		return scan_bprm(bprm, argc, argv, envc, envp, obj->tmp);
+	return true;
+}
+
+/**
+ * ccs_update_condition - Update the current process's condition.
+ *
+ * @acl: Pointer to "struct acl_info".
+ *
+ * Returns nothing.
+ */
+void ccs_update_condition(const struct acl_info *acl)
+{
+	/*
+	 * Don't change the lowest byte because it is reserved for
+	 * TOMOYO_CHECK_READ_FOR_OPEN_EXEC / CCS_DONT_SLEEP_ON_ENFORCE_ERROR /
+	 * TOMOYO_TASK_IS_EXECUTE_HANDLER.
+	 */
+	const struct condition_list *ptr = ccs_get_condition_part(acl);
+	struct task_struct *task;
+	u32 tomoyo_flags = current->tomoyo_flags;
+	const u8 flags = ptr ? ptr->post_state[3] : 0;
+	if (!flags)
+		return;
+	task = current;
+	tomoyo_flags = task->tomoyo_flags;
+	if (flags & 1) {
+		tomoyo_flags &= ~0xFF000000;
+		tomoyo_flags |= ptr->post_state[0] << 24;
+	}
+	if (flags & 2) {
+		tomoyo_flags &= ~0x00FF0000;
+		tomoyo_flags |= ptr->post_state[1] << 16;
+	}
+	if (flags & 4) {
+		tomoyo_flags &= ~0x0000FF00;
+		tomoyo_flags |= ptr->post_state[2] << 8;
+	}
+	task->tomoyo_flags = tomoyo_flags;
+}
+
+/**
+ * ccs_print_condition - Print condition part.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ * @cond: Pointer to "struct condition_list". May be NULL.
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_print_condition(struct ccs_io_buffer *head,
+			 const struct condition_list *cond)
+{
+	const unsigned long *ptr;
+	const struct argv_entry *argv;
+	const struct envp_entry *envp;
+	u16 condc;
+	u16 argc;
+	u16 envc;
+	u16 i;
+	u16 j;
+	char buffer[32];
+	if (!cond)
+		goto no_condition;
+	condc = cond->condc;
+	argc = cond->argc;
+	envc = cond->envc;
+	ptr = (const unsigned long *) (cond + 1);
+	argv = (const struct argv_entry *) (ptr + condc);
+	envp = (const struct envp_entry *) (argv + argc);
+	memset(buffer, 0, sizeof(buffer));
+	for (i = 0; i < condc; i++) {
+		const u32 header = *ptr;
+		const u8 match = (header >> 16) & 1;
+		const u8 left_1_type = (header >> 18) & 3;
+		const u8 left_2_type = (header >> 20) & 3;
+		const u8 right_1_type = (header >> 22) & 3;
+		const u8 right_2_type = (header >> 24) & 3;
+		const u8 left = header >> 8;
+		const u8 right = header;
+		ptr++;
+		if (!ccs_io_printf(head, "%s", i ? " " : " if "))
+			goto out;
+		if (left < MAX_KEYWORD) {
+			const char *keyword
+				= condition_control_keyword[left].keyword;
+			if (!ccs_io_printf(head, "%s", keyword))
+				goto out;
+			goto print_operator;
+		}
+		print_ulong(buffer, sizeof(buffer) - 1, *ptr, left_1_type);
+		ptr++;
+		if (!ccs_io_printf(head, "%s", buffer))
+			goto out;
+		i++;
+		if (left == MAX_KEYWORD)
+			goto print_operator;
+		print_ulong(buffer, sizeof(buffer) - 1, *ptr, left_2_type);
+		ptr++;
+		if (!ccs_io_printf(head, "-%s", buffer))
+			goto out;
+		i++;
+ print_operator:
+		if (!ccs_io_printf(head, "%s", match ? "=" : "!="))
+			goto out;
+		if (right < MAX_KEYWORD) {
+			const char *keyword
+				= condition_control_keyword[right].keyword;
+			if (!ccs_io_printf(head, "%s", keyword))
+				goto out;
+			continue;
+		}
+		print_ulong(buffer, sizeof(buffer) - 1, *ptr, right_1_type);
+		ptr++;
+		if (!ccs_io_printf(head, "%s", buffer))
+			goto out;
+		i++;
+		if (right == MAX_KEYWORD)
+			continue;
+		print_ulong(buffer, sizeof(buffer) - 1, *ptr, right_2_type);
+		ptr++;
+		if (!ccs_io_printf(head, "-%s", buffer))
+			goto out;
+		i++;
+	}
+
+	if (!argc && !envc)
+		goto post_condition;
+	if (!condc && !ccs_io_printf(head, " if"))
+		goto out;
+	for (i = 0; i < argc; argv++, i++) {
+		const char *op = argv->is_not ? "!=" : "=";
+		if (!ccs_io_printf(head, " exec.argv[%u]%s\"%s\"", argv->index,
+				   op, argv->value->name))
+			goto out;
+	}
+	buffer[1] = '\0';
+	for (i = 0; i < envc; envp++, i++) {
+		const char *op = envp->is_not ? "!=" : "=";
+		const char *value = envp->value ? envp->value->name : NULL;
+		if (!ccs_io_printf(head, " exec.envp[\"%s\"]%s",
+				   envp->name->name, op))
+			goto out;
+		if (value) {
+			if (!ccs_io_printf(head, "\"%s\"", value))
+				goto out;
+		} else {
+			if (!ccs_io_printf(head, "NULL"))
+				goto out;
+		}
+	}
+ post_condition:
+	i = cond->post_state[3];
+	if (!i)
+		goto no_condition;
+	if (!ccs_io_printf(head, " ; set"))
+		goto out;
+	for (j = 0; j < 3; j++) {
+		if (!(i & (1 << j)))
+			continue;
+		if (!ccs_io_printf(head, " task.state[%u]=%u", j,
+				   cond->post_state[j]))
+			goto out;
+	}
+ no_condition:
+	if (ccs_io_printf(head, "\n"))
+		return true;
+ out:
+	return false;
+}

-- 

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

* [TOMOYO #7 23/30] argvrestriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (21 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 22/30] Conditional ACL support functions Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 24/30] File operation restriction part Tetsuo Handa
                   ` (7 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-argv0-restriction-part.patch --]
[-- Type: text/plain, Size: 6817 bytes --]

This file controls argv[0] passed to execve().

Multi call binary programs (e.g. busybox) behave differently depending on
the invocation name (a.k.a. argv[0]).
Thus, to prevent attackers from abusing multi call binary programs,
TOMOYO checks the combination of filename and argv[0] passed to execve().

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_exec.c |  203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 203 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_exec.c
@@ -0,0 +1,203 @@
+/*
+ * fs/tomoyo_exec.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+
+/**
+ * audit_argv0_log - Audit argv[0] log.
+ *
+ * @filename:   The fullpath of program.
+ * @argv0:      The basename of argv[0].
+ * @is_granted: True if this is a granted log.
+ * @profile:    Profile number used.
+ * @mode:       Access control mode used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_argv0_log(const struct path_info *filename, const char *argv0,
+			   const bool is_granted, const u8 profile,
+			   const u8 mode)
+{
+	char *buf;
+	int len;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	len = filename->total_len + strlen(argv0) + 64;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1,
+		 KEYWORD_ALLOW_ARGV0 "%s %s\n", filename->name, argv0);
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/**
+ * update_argv0_entry - Update "struct argv0_acl_record" list.
+ *
+ * @filename:  The fullpath of the program.
+ * @argv0:     The basename of argv[0].
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_argv0_entry(const char *filename, const char *argv0,
+			      struct domain_info *domain,
+			      const struct condition_list *condition,
+			      const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct argv0_acl_record *acl;
+	const struct path_info *saved_filename;
+	const struct path_info *saved_argv0;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(filename, 1, 0, -1, __func__) ||
+	    !ccs_is_correct_path(argv0, -1, 0, -1, __func__) ||
+	    strchr(argv0, '/'))
+		return -EINVAL;
+	saved_filename = ccs_save_name(filename);
+	saved_argv0 = ccs_save_name(argv0);
+	if (!saved_filename || !saved_argv0)
+		return -ENOMEM;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_ARGV0_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct argv0_acl_record, head);
+		if (acl->filename != saved_filename ||
+		    acl->argv0 != saved_argv0)
+			continue;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_ARGV0_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->filename = saved_filename;
+	acl->argv0 = saved_argv0;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_ARGV0_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct argv0_acl_record, head);
+		if (acl->filename != saved_filename ||
+		    acl->argv0 != saved_argv0)
+			continue;
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * check_argv0_acl - Check permission for argv[0].
+ *
+ * @filename: The fullpath of the program.
+ * @argv0:    The basename of argv[0].
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_argv0_acl(const struct path_info *filename, const char *argv0)
+{
+	const struct domain_info *domain = current->domain_info;
+	int error = -EPERM;
+	struct acl_info *ptr;
+	struct path_info argv_0;
+	argv_0.name = argv0;
+	ccs_fill_path_info(&argv_0);
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct argv0_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_ARGV0_ACL)
+			continue;
+		acl = container_of(ptr, struct argv0_acl_record, head);
+		if (!ccs_check_condition(ptr, NULL) ||
+		    !ccs_path_matches_pattern(filename, acl->filename) ||
+		    !ccs_path_matches_pattern(&argv_0, acl->argv0))
+			continue;
+		ccs_update_condition(ptr);
+		error = 0;
+		break;
+	}
+	return error;
+}
+
+/**
+ * ccs_check_argv0_perm - Check permission for argv[0].
+ *
+ * @filename: The fullpath of the program.
+ * @argv0:    The basename of argv[0].
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_argv0_perm(const struct path_info *filename, const char *argv0)
+{
+	int error = 0;
+	struct domain_info * const domain = current->domain_info;
+	const u8 profile = domain->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_ARGV0);
+	const bool is_enforce = (mode == 3);
+	if (!filename || !argv0 || !*argv0)
+		return 0;
+	error = check_argv0_acl(filename, argv0);
+	audit_argv0_log(filename, argv0, !error, profile, mode);
+	if (!error)
+		return 0;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Run %s as %s denied for %s\n",
+		       ccs_get_msg(is_enforce), filename->name, argv0,
+		       ccs_get_last_name(domain));
+	if (is_enforce)
+		return ccs_check_supervisor("%s\n"
+					    KEYWORD_ALLOW_ARGV0 "%s %s\n",
+					    domain->domainname->name,
+					    filename->name, argv0);
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_argv0_entry(filename->name, argv0, domain, NULL, false);
+	return 0;
+}
+
+/**
+ * ccs_write_argv0_policy - Write "struct argv0_acl_record" list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_argv0_policy(char *data, struct domain_info *domain,
+			   const struct condition_list *condition,
+			   const bool is_delete)
+{
+	char *argv0 = strchr(data, ' ');
+	if (!argv0)
+		return -EINVAL;
+	*argv0++ = '\0';
+	return update_argv0_entry(data, argv0, domain, condition, is_delete);
+}

-- 

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

* [TOMOYO #7 24/30] File operation restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (22 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 23/30] argvrestriction part Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 25/30] Signal " Tetsuo Handa
                   ` (6 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-file-restriction-part.patch --]
[-- Type: text/plain, Size: 46173 bytes --]

This file controls file related operations.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_file.c | 1561 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1561 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_file.c
@@ -0,0 +1,1561 @@
+/*
+ * fs/tomoyo_file.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+#include <linux/binfmts.h>
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/* Structure for "allow_read" keyword. */
+struct globally_readable_file_entry {
+	struct list1_head list;
+	const struct path_info *filename;
+	bool is_deleted;
+};
+
+/* Structure for "file_pattern" keyword. */
+struct pattern_entry {
+	struct list1_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/* Structure for "deny_rewrite" keyword. */
+struct no_rewrite_entry {
+	struct list1_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/* Keyword array for single path operations. */
+static const char *sp_keyword[MAX_SINGLE_PATH_OPERATION] = {
+	[TYPE_READ_WRITE_ACL] = "read/write",
+	[TYPE_EXECUTE_ACL]    = "execute",
+	[TYPE_READ_ACL]       = "read",
+	[TYPE_WRITE_ACL]      = "write",
+	[TYPE_CREATE_ACL]     = "create",
+	[TYPE_UNLINK_ACL]     = "unlink",
+	[TYPE_MKDIR_ACL]      = "mkdir",
+	[TYPE_RMDIR_ACL]      = "rmdir",
+	[TYPE_MKFIFO_ACL]     = "mkfifo",
+	[TYPE_MKSOCK_ACL]     = "mksock",
+	[TYPE_MKBLOCK_ACL]    = "mkblock",
+	[TYPE_MKCHAR_ACL]     = "mkchar",
+	[TYPE_TRUNCATE_ACL]   = "truncate",
+	[TYPE_SYMLINK_ACL]    = "symlink",
+	[TYPE_REWRITE_ACL]    = "rewrite",
+};
+
+/* Keyword array for double path operations. */
+static const char *dp_keyword[MAX_DOUBLE_PATH_OPERATION] = {
+	[TYPE_LINK_ACL]    = "link",
+	[TYPE_RENAME_ACL]  = "rename",
+};
+
+/**
+ * ccs_sp2keyword - Get the name of single path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of single path operation.
+ */
+const char *ccs_sp2keyword(const u8 operation)
+{
+	return (operation < MAX_SINGLE_PATH_OPERATION)
+		? sp_keyword[operation] : NULL;
+}
+
+/**
+ * ccs_dp2keyword - Get the name of double path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of double path operation.
+ */
+const char *ccs_dp2keyword(const u8 operation)
+{
+	return (operation < MAX_DOUBLE_PATH_OPERATION)
+		? dp_keyword[operation] : NULL;
+}
+
+/**
+ * strendswith - Check whether the token ends with the given token.
+ *
+ * @name: The token to check.
+ * @tail: The token to find.
+ *
+ * Returns true if @name ends with @tail, false otherwise.
+ */
+static bool strendswith(const char *name, const char *tail)
+{
+	int len;
+	if (!name || !tail)
+		return false;
+	len = strlen(name) - strlen(tail);
+	return len >= 0 && !strcmp(name + len, tail);
+}
+
+/**
+ * ccs_get_path - Get realpath.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @mnt:    Pointer to "struct vfsmount".
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ */
+static struct path_info *ccs_get_path(struct dentry *dentry,
+				      struct vfsmount *mnt)
+{
+	int error;
+	struct path_info_with_data *buf = ccs_alloc(sizeof(*buf));
+	if (!buf)
+		return NULL;
+	/* Preserve one byte for appending "/". */
+	error = ccs_realpath_from_dentry2(dentry, mnt, buf->body,
+					  sizeof(buf->body) - 2);
+	if (!error) {
+		buf->head.name = buf->body;
+		ccs_fill_path_info(&buf->head);
+		return &buf->head;
+	}
+	ccs_free(buf);
+	return NULL;
+}
+
+static int update_double_path_acl(const u8 type, const char *filename1,
+				  const char *filename2,
+				  struct domain_info * const domain,
+				  const struct condition_list *condition,
+				  const bool is_delete);
+static int update_single_path_acl(const u8 type, const char *filename,
+				  struct domain_info * const domain,
+				  const struct condition_list *condition,
+				  const bool is_delete);
+
+/**
+ * audit_file_log - Audit file related request log.
+ *
+ * @operation:  The name of operation.
+ * @filename1:  First pathname.
+ * @filename2:  Second pathname. May be NULL.
+ * @is_granted: True if this is a granted log.
+ * @profile:    Profile number used.
+ * @mode:       Access control mode used.
+ * @bprm:       Pointer to "struct linux_binprm". May be NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_file_log(const char *operation,
+			  const struct path_info *filename1,
+			  const struct path_info *filename2,
+			  const bool is_granted, const u8 profile,
+			  const u8 mode, struct linux_binprm *bprm)
+{
+	char *buf;
+	int len;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	len = strlen(operation) + filename1->total_len + 16;
+	if (filename2)
+		len += filename2->total_len;
+	buf = ccs_init_audit_log(&len, profile, mode, bprm);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, "allow_%s %s %s\n",
+		 operation, filename1->name, filename2 ? filename2->name : "");
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/* The list for "struct globally_readable_file_entry". */
+static LIST1_HEAD(globally_readable_list);
+
+/**
+ * update_globally_readable_entry - Update "struct globally_readable_file_entry" list.
+ *
+ * @filename:  Filename unconditionally permitted to open() for reading.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_globally_readable_entry(const char *filename,
+					  const bool is_delete)
+{
+	struct globally_readable_file_entry *new_entry;
+	struct globally_readable_file_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_filename;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(filename, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	saved_filename = ccs_save_name(filename);
+	if (!saved_filename)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &globally_readable_list, list) {
+		if (ptr->filename != saved_filename)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->filename = saved_filename;
+	list1_add_tail_mb(&new_entry->list, &globally_readable_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading.
+ *
+ * @filename: The filename to check.
+ *
+ * Returns true if any domain can open @filename for reading, false otherwise.
+ */
+static bool is_globally_readable_file(const struct path_info *filename)
+{
+	struct globally_readable_file_entry *ptr;
+	list1_for_each_entry(ptr, &globally_readable_list, list) {
+		if (!ptr->is_deleted && !ccs_pathcmp(filename, ptr->filename))
+			return true;
+	}
+	return false;
+}
+
+/**
+ * ccs_write_globally_readable_policy - Write "struct globally_readable_file_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_globally_readable_policy(char *data, const bool is_delete)
+{
+	return update_globally_readable_entry(data, is_delete);
+}
+
+/**
+ * ccs_read_globally_readable_policy - Read "struct globally_readable_file_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_globally_readable_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &globally_readable_list) {
+		struct globally_readable_file_entry *ptr;
+		ptr = list1_entry(pos, struct globally_readable_file_entry,
+				  list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALLOW_READ "%s\n",
+				   ptr->filename->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/* The list for "struct path_group_entry". */
+static LIST1_HEAD(path_group_list);
+
+/**
+ * update_path_group_entry - Update "struct path_group_entry" list.
+ *
+ * @group_name:  The name of pathname group.
+ * @member_name: The name of group's member.
+ * @is_delete:   True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_path_group_entry(const char *group_name,
+				   const char *member_name,
+				   const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	struct path_group_entry *new_group;
+	struct path_group_entry *group;
+	struct path_group_member *new_member;
+	struct path_group_member *member;
+	const struct path_info *saved_group_name;
+	const struct path_info *saved_member_name;
+	int error = -ENOMEM;
+	bool found = false;
+	if (!ccs_is_correct_path(group_name, 0, 0, 0, __func__) ||
+	    !group_name[0] ||
+	    !ccs_is_correct_path(member_name, 0, 0, 0, __func__) ||
+	    !member_name[0])
+		return -EINVAL;
+	saved_group_name = ccs_save_name(group_name);
+	saved_member_name = ccs_save_name(member_name);
+	if (!saved_group_name || !saved_member_name)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(group, &path_group_list, list) {
+		if (saved_group_name != group->group_name)
+			continue;
+		list1_for_each_entry(member, &group->path_group_member_list,
+				     list) {
+			if (member->member_name != saved_member_name)
+				continue;
+			member->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+		found = true;
+		break;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	if (!found) {
+		new_group = ccs_alloc_element(sizeof(*new_group));
+		if (!new_group)
+			goto out;
+		INIT_LIST1_HEAD(&new_group->path_group_member_list);
+		new_group->group_name = saved_group_name;
+		list1_add_tail_mb(&new_group->list, &path_group_list);
+		group = new_group;
+	}
+	new_member = ccs_alloc_element(sizeof(*new_member));
+	if (!new_member)
+		goto out;
+	new_member->member_name = saved_member_name;
+	list1_add_tail_mb(&new_member->list, &group->path_group_member_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_write_path_group_policy - Write "struct path_group_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, nagative value otherwise.
+ */
+int ccs_write_path_group_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return update_path_group_entry(data, cp, is_delete);
+}
+
+/**
+ * find_or_assign_new_path_group - Create pathname group.
+ *
+ * @group_name: The name of pathname group.
+ *
+ * Returns pointer to "struct path_group_entry" if found, NULL otherwise.
+ */
+static struct path_group_entry *
+find_or_assign_new_path_group(const char *group_name)
+{
+	u8 i;
+	struct path_group_entry *group;
+	for (i = 0; i <= 1; i++) {
+		list1_for_each_entry(group, &path_group_list, list) {
+			if (!strcmp(group_name, group->group_name->name))
+				return group;
+		}
+		if (!i) {
+			update_path_group_entry(group_name, "/", false);
+			update_path_group_entry(group_name, "/", true);
+		}
+	}
+	return NULL;
+}
+
+/**
+ * path_matches_group - Check whether the given pathname matches members of the given pathname group.
+ *
+ * @pathname:        The name of pathname.
+ * @group:           Pointer to "struct path_group_entry".
+ * @may_use_pattern: True if wild card is permitted.
+ *
+ * Returns true if @pathname matches pathnames in @group, false otherwise.
+ */
+static bool path_matches_group(const struct path_info *pathname,
+			       const struct path_group_entry *group,
+			       const bool may_use_pattern)
+{
+	struct path_group_member *member;
+	list1_for_each_entry(member, &group->path_group_member_list, list) {
+		if (member->is_deleted)
+			continue;
+		if (!member->member_name->is_patterned) {
+			if (!ccs_pathcmp(pathname, member->member_name))
+				return true;
+		} else if (may_use_pattern) {
+			if (ccs_path_matches_pattern(pathname,
+						     member->member_name))
+				return true;
+		}
+	}
+	return false;
+}
+
+/**
+ * ccs_read_path_group_policy - Read "struct path_group_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_path_group_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *gpos;
+	struct list1_head *mpos;
+	list1_for_each_cookie(gpos, head->read_var1, &path_group_list) {
+		struct path_group_entry *group;
+		group = list1_entry(gpos, struct path_group_entry, list);
+		list1_for_each_cookie(mpos, head->read_var2,
+				      &group->path_group_member_list) {
+			struct path_group_member *member;
+			member = list1_entry(mpos, struct path_group_member,
+					     list);
+			if (member->is_deleted)
+				continue;
+			if (!ccs_io_printf(head, KEYWORD_PATH_GROUP "%s %s\n",
+					   group->group_name->name,
+					   member->member_name->name))
+				goto out;
+		}
+	}
+	return true;
+ out:
+	return false;
+}
+
+/* The list for "struct pattern_entry". */
+static LIST1_HEAD(pattern_list);
+
+/**
+ * update_file_pattern_entry - Update "struct pattern_entry" list.
+ *
+ * @pattern:   Pathname pattern.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_file_pattern_entry(const char *pattern, const bool is_delete)
+{
+	struct pattern_entry *new_entry;
+	struct pattern_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_pattern;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(pattern, 0, 1, 0, __func__))
+		return -EINVAL;
+	saved_pattern = ccs_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &pattern_list, list) {
+		if (saved_pattern != ptr->pattern)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail_mb(&new_entry->list, &pattern_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * get_file_pattern - Get patterned pathname.
+ *
+ * @filename: The filename to find patterned pathname.
+ *
+ * Returns pointer to pathname pattern if matched, @filename otherwise.
+ */
+static const struct path_info *
+get_file_pattern(const struct path_info *filename)
+{
+	struct pattern_entry *ptr;
+	const struct path_info *pattern = NULL;
+	list1_for_each_entry(ptr, &pattern_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		pattern = ptr->pattern;
+		if (strendswith(pattern->name, "/\\*")) {
+			/* Do nothing. Try to find the better match. */
+		} else {
+			/* This would be the better match. Use this. */
+			break;
+		}
+	}
+	if (pattern)
+		filename = pattern;
+	return filename;
+}
+
+/**
+ * ccs_write_pattern_policy - Write "struct pattern_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_pattern_policy(char *data, const bool is_delete)
+{
+	return update_file_pattern_entry(data, is_delete);
+}
+
+/**
+ * ccs_read_file_pattern - Read "struct pattern_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_file_pattern(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &pattern_list) {
+		struct pattern_entry *ptr;
+		ptr = list1_entry(pos, struct pattern_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_FILE_PATTERN "%s\n",
+				   ptr->pattern->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/* The list for "struct no_rewrite_entry". */
+static LIST1_HEAD(no_rewrite_list);
+
+/**
+ * update_no_rewrite_entry - Update "struct no_rewrite_entry" list.
+ *
+ * @pattern:   Pathname pattern that are not rewritable by default.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_no_rewrite_entry(const char *pattern, const bool is_delete)
+{
+	struct no_rewrite_entry *new_entry, *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_pattern;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(pattern, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_pattern = ccs_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->pattern != saved_pattern)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail_mb(&new_entry->list, &no_rewrite_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited.
+ *
+ * @filename: Filename to check.
+ *
+ * Returns true if @filename is specified by "deny_rewrite" directive,
+ * false otherwise.
+ */
+static bool is_no_rewrite_file(const struct path_info *filename)
+{
+	struct no_rewrite_entry *ptr;
+	list1_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		return true;
+	}
+	return false;
+}
+
+/**
+ * ccs_write_no_rewrite_policy - Write "struct no_rewrite_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_no_rewrite_policy(char *data, const bool is_delete)
+{
+	return update_no_rewrite_entry(data, is_delete);
+}
+
+/**
+ * ccs_read_no_rewrite_policy - Read "struct no_rewrite_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_no_rewrite_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &no_rewrite_list) {
+		struct no_rewrite_entry *ptr;
+		ptr = list1_entry(pos, struct no_rewrite_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_DENY_REWRITE "%s\n",
+				   ptr->pattern->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * update_file_acl - Update file's read/write/execute ACL.
+ *
+ * @filename:  Filename.
+ * @perm:      Permission (between 1 to 7).
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * This is legacy support interface for older policy syntax.
+ * Current policy syntax uses "allow_read/write" instead of "6",
+ * "allow_read" instead of "4", "allow_write" instead of "2",
+ * "allow_execute" instead of "1".
+ */
+static int update_file_acl(const char *filename, u8 perm,
+			   struct domain_info * const domain,
+			   const struct condition_list *condition,
+			   const bool is_delete)
+{
+	if (perm > 7 || !perm) {
+		printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+		       __func__, perm, filename);
+		return -EINVAL;
+	}
+	if (filename[0] != '@' && strendswith(filename, "/"))
+		/*
+		 * Only 'allow_mkdir' and 'allow_rmdir' are valid for
+		 * directory permissions.
+		 */
+		return 0;
+	if (perm & 4)
+		update_single_path_acl(TYPE_READ_ACL, filename, domain,
+				       condition, is_delete);
+	if (perm & 2)
+		update_single_path_acl(TYPE_WRITE_ACL, filename, domain,
+				       condition, is_delete);
+	if (perm & 1)
+		update_single_path_acl(TYPE_EXECUTE_ACL, filename, domain,
+				       condition, is_delete);
+	return 0;
+}
+
+/**
+ * check_single_path_acl2 - Check permission for single path operation.
+ *
+ * @filename:        Filename to check.
+ * @perm:            Permission.
+ * @obj:             Pointer to "struct obj_info".
+ * @may_use_pattern: True if patterned ACL is permitted.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_single_path_acl2(const struct path_info *filename,
+				  const u16 perm, struct obj_info *obj,
+				  const bool may_use_pattern)
+{
+	const struct domain_info *domain = current->domain_info;
+	struct acl_info *ptr;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct single_path_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (!(acl->perm & perm) || !ccs_check_condition(ptr, obj))
+			continue;
+		if (acl->u_is_group) {
+			if (!path_matches_group(filename, acl->u.group,
+						may_use_pattern))
+				continue;
+		} else if (may_use_pattern || !acl->u.filename->is_patterned) {
+			if (!ccs_path_matches_pattern(filename,
+						      acl->u.filename))
+				continue;
+		} else {
+			continue;
+		}
+		ccs_update_condition(ptr);
+		return 0;
+	}
+	return -EPERM;
+}
+
+/**
+ * check_file_acl - Check permission for opening files.
+ *
+ * @filename:  Filename to check.
+ * @operation: Mode ("read" or "write" or "read/write" or "execute").
+ * @obj:       Pointer to "struct obj_info".
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_file_acl(const struct path_info *filename, const u8 operation,
+			  struct obj_info *obj)
+{
+	u16 perm = 0;
+	if (!ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	if (operation == 6)
+		perm = 1 << TYPE_READ_WRITE_ACL;
+	else if (operation == 4)
+		perm = 1 << TYPE_READ_ACL;
+	else if (operation == 2)
+		perm = 1 << TYPE_WRITE_ACL;
+	else if (operation == 1)
+		perm = 1 << TYPE_EXECUTE_ACL;
+	else
+		BUG();
+	return check_single_path_acl2(filename, perm, obj, operation != 1);
+}
+
+/**
+ * check_file_perm2 - Check permission for opening files.
+ *
+ * @filename:  Filename to check.
+ * @perm:      Mode ("read" or "write" or "read/write" or "execute").
+ * @operation: Operation name passed used for verbose mode.
+ * @obj:       Pointer to "struct obj_info". May be NULL.
+ * @profile:   Profile number passed to audit logs.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_file_perm2(const struct path_info *filename, const u8 perm,
+			    const char *operation, struct obj_info *obj,
+			    const u8 profile, const u8 mode)
+{
+	struct domain_info * const domain = current->domain_info;
+	const bool is_enforce = (mode == 3);
+	const char *msg = "<unknown>";
+	int error = 0;
+	if (!filename)
+		return 0;
+	error = check_file_acl(filename, perm, obj);
+	if (error && perm == 4 &&
+	    (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) == 0 &&
+	    is_globally_readable_file(filename))
+		error = 0;
+	if (perm == 6)
+		msg = ccs_sp2keyword(TYPE_READ_WRITE_ACL);
+	else if (perm == 4)
+		msg = ccs_sp2keyword(TYPE_READ_ACL);
+	else if (perm == 2)
+		msg = ccs_sp2keyword(TYPE_WRITE_ACL);
+	else if (perm == 1)
+		msg = ccs_sp2keyword(TYPE_EXECUTE_ACL);
+	else
+		BUG();
+	audit_file_log(msg, filename, NULL, !error, profile, mode,
+		       obj ? obj->bprm : NULL);
+	if (!error)
+		return 0;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied "
+		       "for %s\n", ccs_get_msg(is_enforce), msg, operation,
+		       filename->name, ccs_get_last_name(domain));
+	if (is_enforce)
+		return ccs_check_supervisor("%s\nallow_%s %s\n",
+					    domain->domainname->name,
+					    msg, filename->name);
+	if (mode == 1 && ccs_check_domain_quota(domain)) {
+		/* Don't use patterns for execute permission. */
+		const struct path_info *patterned_file = (perm != 1) ?
+			get_file_pattern(filename) : filename;
+		update_file_acl(patterned_file->name, perm,
+				domain, NULL, false);
+	}
+	return 0;
+}
+
+/**
+ * update_execute_handler - Update "struct execute_handler_record" list.
+ *
+ * @type:      Type of execute handler.
+ * @filename:  Pathname to the execute handler.
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_execute_handler(const u8 type, const char *filename,
+				  struct domain_info * const domain,
+				  const bool is_delete)
+{
+	const struct path_info *saved_filename;
+	struct acl_info *ptr;
+	struct execute_handler_record *acl;
+	int error = -ENOMEM;
+	if (!domain)
+		return -EINVAL;
+	if (!ccs_is_correct_path(filename, 1, -1, -1, __func__))
+		return -EINVAL;
+	saved_filename = ccs_save_name(filename);
+	if (!saved_filename)
+		return -ENOMEM;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != type)
+			continue;
+		/* Condition not supported. */
+		acl = container_of(ptr, struct execute_handler_record, head);
+		if (acl->handler != saved_filename)
+			continue;
+		/* Only one entry can exist in a domain. */
+		list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+			if (ptr->type == type)
+				ptr->type |= ACL_DELETED;
+		}
+		error = ccs_add_domain_acl(NULL, &acl->head);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(type, NULL);
+	if (!acl)
+		goto out;
+	acl->handler = saved_filename;
+	/* Only one entry can exist in a domain. */
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ptr->type == type)
+			ptr->type |= ACL_DELETED;
+	}
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != type)
+			continue;
+		/* Condition not supported. */
+		acl = container_of(ptr, struct execute_handler_record, head);
+		if (acl->handler != saved_filename)
+			continue;
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * ccs_write_file_policy - Update file related list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_file_policy(char *data, struct domain_info *domain,
+			  const struct condition_list *condition,
+			  const bool is_delete)
+{
+	char *filename = strchr(data, ' ');
+	char *filename2;
+	unsigned int perm;
+	u8 type;
+	if (!filename)
+		return -EINVAL;
+	*filename++ = '\0';
+	if (sscanf(data, "%u", &perm) == 1)
+		return update_file_acl(filename, (u8) perm, domain, condition,
+				       is_delete);
+	if (strncmp(data, "allow_", 6)) {
+		u8 type;
+		if (!strcmp(data, KEYWORD_EXECUTE_HANDLER))
+			type = TYPE_EXECUTE_HANDLER;
+		else if (!strcmp(data, KEYWORD_DENIED_EXECUTE_HANDLER))
+			type = TYPE_DENIED_EXECUTE_HANDLER;
+		else
+			goto out;
+		return update_execute_handler(type, filename,
+					      domain, is_delete);
+	}
+	data += 6;
+	for (type = 0; type < MAX_SINGLE_PATH_OPERATION; type++) {
+		if (strcmp(data, sp_keyword[type]))
+			continue;
+		return update_single_path_acl(type, filename, domain, condition,
+					      is_delete);
+	}
+	filename2 = strchr(filename, ' ');
+	if (!filename2)
+		goto out;
+	*filename2++ = '\0';
+	for (type = 0; type < MAX_DOUBLE_PATH_OPERATION; type++) {
+		if (strcmp(data, dp_keyword[type]))
+			continue;
+		return update_double_path_acl(type, filename, filename2, domain,
+					      condition, is_delete);
+	}
+ out:
+	return -EINVAL;
+}
+
+/**
+ * update_single_path_acl - Update "struct single_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename:  Filename.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_single_path_acl(const u8 type, const char *filename,
+				  struct domain_info * const domain,
+				  const struct condition_list *condition,
+				  const bool is_delete)
+{
+	static const u16 rw_mask = (1 << TYPE_READ_ACL) | (1 << TYPE_WRITE_ACL);
+	const struct path_info *saved_filename;
+	struct acl_info *ptr;
+	struct single_path_acl_record *acl;
+	int error = -ENOMEM;
+	bool is_group = false;
+	const u16 perm = 1 << type;
+	if (!domain)
+		return -EINVAL;
+	if (!ccs_is_correct_path(filename, 0, 0, 0, __func__))
+		return -EINVAL;
+	if (filename[0] == '@') {
+		/*
+		 * This cast is OK because I don't dereference
+		 * in this function.
+		 */
+		saved_filename = (struct path_info *)
+			find_or_assign_new_path_group(filename + 1);
+		is_group = true;
+	} else {
+		saved_filename = ccs_save_name(filename);
+	}
+	if (!saved_filename)
+		return -ENOMEM;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (acl->u.filename != saved_filename)
+			continue;
+		/* Special case. Clear all bits if marked as deleted. */
+		if (ptr->type & ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		if ((acl->perm & rw_mask) == rw_mask)
+			acl->perm |= 1 << TYPE_READ_WRITE_ACL;
+		else if (acl->perm & (1 << TYPE_READ_WRITE_ACL))
+			acl->perm |= rw_mask;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_SINGLE_PATH_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->u_is_group = is_group;
+	acl->u.filename = saved_filename;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (acl->u.filename != saved_filename)
+			continue;
+		acl->perm &= ~perm;
+		if ((acl->perm & rw_mask) != rw_mask)
+			acl->perm &= ~(1 << TYPE_READ_WRITE_ACL);
+		else if (!(acl->perm & (1 << TYPE_READ_WRITE_ACL)))
+			acl->perm &= ~rw_mask;
+		error = ccs_del_domain_acl(acl->perm ? NULL : ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * update_double_path_acl - Update "struct double_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename1: First filename.
+ * @filename2: Second filename.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_double_path_acl(const u8 type, const char *filename1,
+				  const char *filename2,
+				  struct domain_info * const domain,
+				  const struct condition_list *condition,
+				  const bool is_delete)
+{
+	const struct path_info *saved_filename1;
+	const struct path_info *saved_filename2;
+	struct acl_info *ptr;
+	struct double_path_acl_record *acl;
+	int error = -ENOMEM;
+	bool is_group1 = false;
+	bool is_group2 = false;
+	const u8 perm = 1 << type;
+	if (!domain)
+		return -EINVAL;
+	if (!ccs_is_correct_path(filename1, 0, 0, 0, __func__) ||
+	    !ccs_is_correct_path(filename2, 0, 0, 0, __func__))
+		return -EINVAL;
+	if (filename1[0] == '@') {
+		/*
+		 * This cast is OK because I don't dereference
+		 * in this function.
+		 */
+		saved_filename1 = (struct path_info *)
+			find_or_assign_new_path_group(filename1 + 1);
+		is_group1 = true;
+	} else {
+		saved_filename1 = ccs_save_name(filename1);
+	}
+	if (filename2[0] == '@') {
+		/*
+		 * This cast is OK because I don't dereference
+		 * in this function.
+		 */
+		saved_filename2 = (struct path_info *)
+			find_or_assign_new_path_group(filename2 + 1);
+		is_group2 = true;
+	} else {
+		saved_filename2 = ccs_save_name(filename2);
+	}
+	if (!saved_filename1 || !saved_filename2)
+		return -ENOMEM;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (acl->u1.filename1 != saved_filename1 ||
+		    acl->u2.filename2 != saved_filename2)
+			continue;
+		/* Special case. Clear all bits if marked as deleted. */
+		if (ptr->type & ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_DOUBLE_PATH_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->u1_is_group = is_group1;
+	acl->u2_is_group = is_group2;
+	acl->u1.filename1 = saved_filename1;
+	acl->u2.filename2 = saved_filename2;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (acl->u1.filename1 != saved_filename1 ||
+		    acl->u2.filename2 != saved_filename2)
+			continue;
+		acl->perm &= ~perm;
+		error = ccs_del_domain_acl(acl->perm ? NULL : ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * check_single_path_acl - Check permission for single path operation.
+ *
+ * @type:     Type of operation.
+ * @filename: Filename to check.
+ * @obj:      Pointer to "struct obj_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_single_path_acl(const u8 type,
+				 const struct path_info *filename,
+				 struct obj_info *obj)
+{
+	if (!ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	return check_single_path_acl2(filename, 1 << type, obj, 1);
+}
+
+/**
+ * check_double_path_acl - Check permission for double path operation.
+ *
+ * @type:      Type of operation.
+ * @filename1: First filename to check.
+ * @filename2: Second filename to check.
+ * @obj:       Pointer to "struct obj_info".
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_double_path_acl(const u8 type,
+				 const struct path_info *filename1,
+				 const struct path_info *filename2,
+				 struct obj_info *obj)
+{
+	const struct domain_info *domain = current->domain_info;
+	struct acl_info *ptr;
+	const u8 perm = 1 << type;
+	if (!ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct double_path_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (!(acl->perm & perm) || !ccs_check_condition(ptr, obj))
+			continue;
+		if (acl->u1_is_group) {
+			if (!path_matches_group(filename1, acl->u1.group1,
+						true))
+				continue;
+		} else {
+			if (!ccs_path_matches_pattern(filename1,
+						      acl->u1.filename1))
+				continue;
+		}
+		if (acl->u2_is_group) {
+			if (!path_matches_group(filename2,
+						acl->u2.group2, true))
+				continue;
+		} else {
+			if (!ccs_path_matches_pattern(filename2,
+						      acl->u2.filename2))
+				continue;
+		}
+		ccs_update_condition(ptr);
+		return 0;
+	}
+	return -EPERM;
+}
+
+/**
+ * check_single_path_permission2 - Check permission for single path operation.
+ *
+ * @operation: Type of operation.
+ * @filename:  Filename to check.
+ * @obj:       Pointer to "struct obj_info".
+ * @profile:   Profile number passed to audit logs.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_single_path_permission2(u8 operation,
+					 const struct path_info *filename,
+					 struct obj_info *obj,
+					 const u8 profile, const u8 mode)
+{
+	const char *msg;
+	int error;
+	struct domain_info * const domain = current->domain_info;
+	const bool is_enforce = (mode == 3);
+	if (!mode)
+		return 0;
+ next:
+	error = check_single_path_acl(operation, filename, obj);
+	msg = ccs_sp2keyword(operation);
+	audit_file_log(msg, filename, NULL, !error, profile, mode, NULL);
+	if (!error)
+		goto ok;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n",
+		       ccs_get_msg(is_enforce), msg, filename->name,
+		       ccs_get_last_name(domain));
+	if (is_enforce)
+		error = ccs_check_supervisor("%s\nallow_%s %s\n",
+					     domain->domainname->name,
+					     msg, filename->name);
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_single_path_acl(operation,
+				       get_file_pattern(filename)->name,
+				       domain, NULL, false);
+	if (!is_enforce)
+		error = 0;
+ ok:
+	/*
+	 * Since "allow_truncate" doesn't imply "allow_rewrite" permission,
+	 * we need to check "allow_rewrite" permission if the filename is
+	 * specified by "deny_rewrite" keyword.
+	 */
+	if (!error && operation == TYPE_TRUNCATE_ACL &&
+	    is_no_rewrite_file(filename)) {
+		operation = TYPE_REWRITE_ACL;
+		goto next;
+	}
+	return error;
+}
+
+/**
+ * ccs_check_file_perm - Check permission for sysctl()'s "read" and "write".
+ *
+ * @filename:  Filename to check.
+ * @perm:      Mode ("read" or "write" or "read/write").
+ * @operation: Always "sysctl".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_file_perm(const char *filename, const u8 perm,
+			const char *operation)
+{
+	struct path_info name;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	if (!mode)
+		return 0;
+	name.name = filename;
+	ccs_fill_path_info(&name);
+	return check_file_perm2(&name, perm, operation, NULL, profile, mode);
+}
+
+/**
+ * ccs_check_exec_perm - Check permission for "execute".
+ *
+ * @filename: Check permission for "execute".
+ * @bprm:     Pointer to "struct linux_binprm".
+ * @tmp:      Buffer for temporal use.
+ *
+ * Returns 0 on success, negativevalue otherwise.
+ */
+int ccs_check_exec_perm(const struct path_info *filename,
+			struct linux_binprm *bprm, struct ccs_page_buffer *tmp)
+{
+	struct obj_info obj;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	if (!mode)
+		return 0;
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = bprm->file->f_dentry;
+	obj.path1_vfsmnt = bprm->file->f_vfsmnt;
+	obj.bprm = bprm;
+	obj.tmp = tmp;
+	return check_file_perm2(filename, 1, "do_execve", &obj, profile, mode);
+}
+
+/**
+ * ccs_check_open_permission - Check permission for "read" and "write".
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @mnt:    Pointer to "struct vfsmount".
+ * @flag:   Flags for open().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_open_permission(struct dentry *dentry, struct vfsmount *mnt,
+			      const int flag)
+{
+	struct obj_info obj;
+	const u8 acc_mode = ACC_MODE(flag);
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	if (!mode)
+		return 0;
+	if (acc_mode == 0)
+		return 0;
+	if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
+		/*
+		 * I don't check directories here because mkdir() and rmdir()
+		 * don't call me.
+		 */
+		return 0;
+	buf = ccs_get_path(dentry, mnt);
+	if (!buf)
+		goto out;
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry;
+	obj.path1_vfsmnt = mnt;
+	error = 0;
+	/*
+	 * If the filename is specified by "deny_rewrite" keyword,
+	 * we need to check "allow_rewrite" permission when the filename is not
+	 * opened for append mode or the filename is truncated at open time.
+	 */
+	if ((acc_mode & MAY_WRITE) &&
+	    ((flag & O_TRUNC) || !(flag & O_APPEND))) {
+		if (is_no_rewrite_file(buf))
+			error = check_single_path_permission2(TYPE_REWRITE_ACL,
+							      buf, &obj,
+							      profile, mode);
+	}
+	if (!error)
+		error = check_file_perm2(buf, acc_mode, "open", &obj, profile,
+					 mode);
+	if (!error && (flag & O_TRUNC))
+		error = check_single_path_permission2(TYPE_TRUNCATE_ACL, buf,
+						      &obj, profile, mode);
+ out:
+	ccs_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * ccs_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink".
+ *
+ * @operation: Type of operation.
+ * @dentry:    Pointer to "struct dentry".
+ * @mnt:       Pointer to "struct vfsmount".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_1path_perm(const u8 operation, struct dentry *dentry,
+			 struct vfsmount *mnt)
+{
+	struct obj_info obj;
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	if (!mode)
+		return 0;
+	buf = ccs_get_path(dentry, mnt);
+	if (!buf)
+		goto out;
+	switch (operation) {
+	case TYPE_MKDIR_ACL:
+	case TYPE_RMDIR_ACL:
+		if (!buf->is_dir) {
+			/* ccs_get_path() preserves space for appending "/." */
+			strcat((char *) buf->name, "/");
+			ccs_fill_path_info(buf);
+		}
+	}
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry;
+	obj.path1_vfsmnt = mnt;
+	error = check_single_path_permission2(operation, buf, &obj, profile,
+					      mode);
+ out:
+	ccs_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+EXPORT_SYMBOL(ccs_check_1path_perm); /* for net/unix/af_unix.c  */
+
+/**
+ * ccs_check_rewrite_permission - Check permission for "rewrite".
+ *
+ * @filp: Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_rewrite_permission(struct file *filp)
+{
+	struct obj_info obj;
+	int error = -ENOMEM;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	struct path_info *buf = ccs_get_path(filp->f_dentry, filp->f_vfsmnt);
+	if (!buf)
+		goto out;
+	if (!is_no_rewrite_file(buf)) {
+		error = 0;
+		goto out;
+	}
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = filp->f_dentry;
+	obj.path1_vfsmnt = filp->f_vfsmnt;
+	error = check_single_path_permission2(TYPE_REWRITE_ACL, buf, &obj,
+					      profile, mode);
+ out:
+	ccs_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * ccs_check_2path_perm - Check permission for "rename" and "link".
+ *
+ * @operation: Type of operation.
+ * @dentry1:   Pointer to "struct dentry".
+ * @mnt1:      Pointer to "struct vfsmount".
+ * @dentry2:   Pointer to "struct dentry".
+ * @mnt2:      Pointer to "struct vfsmount".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_2path_perm(const u8 operation,
+				     struct dentry *dentry1,
+				     struct vfsmount *mnt1,
+				     struct dentry *dentry2,
+				     struct vfsmount *mnt2)
+{
+	int error = -ENOMEM;
+	struct path_info *buf1, *buf2;
+	struct domain_info * const domain = current->domain_info;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	const char *msg;
+	struct obj_info obj;
+	if (!mode)
+		return 0;
+	buf1 = ccs_get_path(dentry1, mnt1);
+	buf2 = ccs_get_path(dentry2, mnt2);
+	if (!buf1 || !buf2)
+		goto out;
+	if (operation == TYPE_RENAME_ACL) {
+		/* TYPE_LINK_ACL can't reach here for directory. */
+		if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
+			/* ccs_get_path() preserves space for appending "/." */
+			if (!buf1->is_dir) {
+				strcat((char *) buf1->name, "/");
+				ccs_fill_path_info(buf1);
+			}
+			if (!buf2->is_dir) {
+				strcat((char *) buf2->name, "/");
+				ccs_fill_path_info(buf2);
+			}
+		}
+	}
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry1;
+	obj.path1_vfsmnt = mnt1;
+	obj.path2_dentry = dentry2;
+	obj.path2_vfsmnt = mnt2;
+	error = check_double_path_acl(operation, buf1, buf2, &obj);
+	msg = ccs_dp2keyword(operation);
+	audit_file_log(msg, buf1, buf2, !error, profile, mode, NULL);
+	if (!error)
+		goto out;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' "
+		       "denied for %s\n", ccs_get_msg(is_enforce),
+		       msg, buf1->name, buf2->name, ccs_get_last_name(domain));
+	if (is_enforce)
+		error = ccs_check_supervisor("%s\nallow_%s %s %s\n",
+					     domain->domainname->name,
+					     msg, buf1->name, buf2->name);
+	else if (mode == 1 && ccs_check_domain_quota(domain))
+		update_double_path_acl(operation,
+				       get_file_pattern(buf1)->name,
+				       get_file_pattern(buf2)->name,
+				       domain, NULL, false);
+ out:
+	ccs_free(buf1);
+	ccs_free(buf2);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}

-- 

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

* [TOMOYO #7 25/30] Signal operation restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (23 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 24/30] File operation restriction part Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 26/30] Domain transition handler Tetsuo Handa
                   ` (5 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-signal-restriction-part.patch --]
[-- Type: text/plain, Size: 7165 bytes --]

This file controls signal transmission operations.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_signal.c |  227 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 227 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_signal.c
@@ -0,0 +1,227 @@
+/*
+ * fs/tomoyo_signal.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+
+/**
+ * audit_signal_log - Audit signal log.
+ *
+ * @signal:      Signal number.
+ * @dest_domain: Destination domainname.
+ * @is_granted:  True if this is a granted log.
+ * @profile:     Profile number used.
+ * @mode:        Access control mode used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_signal_log(const int signal,
+			    const struct path_info *dest_domain,
+			    const bool is_granted, const u8 profile,
+			    const u8 mode)
+{
+	char *buf;
+	int len;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	len = dest_domain->total_len + 64;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, KEYWORD_ALLOW_SIGNAL "%d %s\n",
+		 signal, dest_domain->name);
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/**
+ * update_signal_acl - Update "struct signal_acl_record" list.
+ *
+ * @sig:          Signal number.
+ * @dest_pattern: Destination domainname.
+ * @domain:       Pointer to "struct domain_info".
+ * @condition:    Pointer to "struct condition_list". May be NULL.
+ * @is_delete:    True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_signal_acl(const int sig, const char *dest_pattern,
+			     struct domain_info *domain,
+			     const struct condition_list *condition,
+			     const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct signal_acl_record *acl;
+	const struct path_info *saved_dest_pattern;
+	const u16 hash = sig;
+	int error = -ENOMEM;
+	if (!domain)
+		return -EINVAL;
+	if (!dest_pattern || !ccs_is_correct_domain(dest_pattern, __func__))
+		return -EINVAL;
+	saved_dest_pattern = ccs_save_name(dest_pattern);
+	if (!saved_dest_pattern)
+		return -ENOMEM;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_SIGNAL_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct signal_acl_record, head);
+		if (acl->sig != hash ||
+		    ccs_pathcmp(acl->domainname, saved_dest_pattern))
+			continue;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_SIGNAL_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->sig = hash;
+	acl->domainname = saved_dest_pattern;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_SIGNAL_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct signal_acl_record, head);
+		if (acl->sig != hash ||
+		    ccs_pathcmp(acl->domainname, saved_dest_pattern))
+			continue;
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * ccs_check_signal_acl - Check permission for signal.
+ *
+ * @sig: Signal number.
+ * @pid: Target's PID.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_signal_acl(const int sig, const int pid)
+{
+	struct domain_info *domain = current->domain_info;
+	struct domain_info *dest = NULL;
+	const char *dest_pattern;
+	struct acl_info *ptr;
+	const u16 hash = sig;
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_SIGNAL);
+	const bool is_enforce = (mode == 3);
+	bool found = false;
+	if (!mode)
+		return 0;
+	if (!sig)
+		return 0;                /* No check for NULL signal. */
+	if (current->pid == pid) {
+		audit_signal_log(sig, domain->domainname, true, profile, mode);
+		return 0;                /* No check for self process. */
+	}
+	{ /* Simplified checking. */
+		struct task_struct *p = NULL;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		if (pid > 0)
+			p = find_task_by_vpid((pid_t) pid);
+		else if (pid == 0)
+			p = current;
+		else if (pid == -1)
+			dest = &KERNEL_DOMAIN;
+		else
+			p = find_task_by_vpid((pid_t) -pid);
+		if (p)
+			dest = p->domain_info;
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (!dest)
+			return 0; /* I can't find destinatioin. */
+	}
+	if (domain == dest) {
+		audit_signal_log(sig, dest->domainname, true, profile, mode);
+		return 0;                /* No check for self domain. */
+	}
+	dest_pattern = dest->domainname->name;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct signal_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_SIGNAL_ACL)
+			continue;
+		acl = container_of(ptr, struct signal_acl_record, head);
+		if (acl->sig == hash && ccs_check_condition(ptr, NULL)) {
+			const int len = acl->domainname->total_len;
+			if (strncmp(acl->domainname->name, dest_pattern, len))
+				continue;
+			switch (dest_pattern[len]) {
+			case ' ':
+			case '\0':
+				break;
+			default:
+				continue;
+			}
+			ccs_update_condition(ptr);
+			found = true;
+			break;
+		}
+	}
+	audit_signal_log(sig, dest->domainname, found, profile, mode);
+	if (found)
+		return 0;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Signal %d "
+		       "to %s denied for %s\n", ccs_get_msg(is_enforce), sig,
+		       ccs_get_last_name(dest), ccs_get_last_name(domain));
+	if (is_enforce)
+		return ccs_check_supervisor("%s\n"
+					    KEYWORD_ALLOW_SIGNAL "%d %s\n",
+					    domain->domainname->name,
+					    sig, dest_pattern);
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_signal_acl(sig, dest_pattern, domain, NULL, false);
+	return 0;
+}
+
+/**
+ * ccs_write_signal_policy - Write "struct signal_acl_record" list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_signal_policy(char *data, struct domain_info *domain,
+			    const struct condition_list *condition,
+			    const bool is_delete)
+{
+	int sig;
+	char *domainname = strchr(data, ' ');
+	if (sscanf(data, "%d", &sig) == 1 && domainname &&
+	    ccs_is_domain_def(domainname + 1))
+		return update_signal_acl(sig, domainname + 1, domain,
+					 condition, is_delete);
+	return -EINVAL;
+}

-- 

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

* [TOMOYO #7 26/30] Domain transition handler.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (24 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 25/30] Signal " Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 27/30] Environment variable restriction part Tetsuo Handa
                   ` (4 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-domain-transition-part.patch --]
[-- Type: text/plain, Size: 46098 bytes --]

This file controls domain creation/deletion/transition.

Every process belongs to a domain in TOMOYO Linux.
Domain transition occurs when execve(2) is called
and the domain is expressed as 'process invocation history',
such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in task_struct->domain_info field.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_domain.c | 1652 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1652 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_domain.c
@@ -0,0 +1,1652 @@
+/*
+ * fs/tomoyo_domain.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+
+/* Variables definitions.*/
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN;
+
+/* The list for "struct domain_info". */
+LIST1_HEAD(domain_list);
+
+#ifdef CONFIG_TOMOYO
+
+/* Lock for appending domain's ACL. */
+DEFINE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DEFINE_MUTEX(new_domain_assign_lock);
+
+/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */
+struct domain_initializer_entry {
+	struct list1_head list;
+	const struct path_info *domainname;    /* This may be NULL */
+	const struct path_info *program;
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_initialize_domain".  */
+	bool is_last_name; /* True if the domainname is ccs_get_last_name(). */
+};
+
+/* Structure for "keep_domain" and "no_keep_domain" keyword. */
+struct domain_keeper_entry {
+	struct list1_head list;
+	const struct path_info *domainname;
+	const struct path_info *program;       /* This may be NULL */
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_keep_domain".        */
+	bool is_last_name; /* True if the domainname is ccs_get_last_name(). */
+};
+
+/* Structure for "aggregator" keyword. */
+struct aggregator_entry {
+	struct list1_head list;
+	const struct path_info *original_name;
+	const struct path_info *aggregated_name;
+	bool is_deleted;
+};
+
+/* Structure for "alias" keyword. */
+struct alias_entry {
+	struct list1_head list;
+	const struct path_info *original_name;
+	const struct path_info *aliased_name;
+	bool is_deleted;
+};
+
+/**
+ * ccs_set_domain_flag - Set or clear domain's attribute flags.
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ * @flags:     Flags to set or clear.
+ *
+ * Returns nothing.
+ */
+void ccs_set_domain_flag(struct domain_info *domain, const bool is_delete,
+			 const u8 flags)
+{
+	mutex_lock(&new_domain_assign_lock);
+	if (!is_delete)
+		domain->flags |= flags;
+	else
+		domain->flags &= ~flags;
+	mutex_unlock(&new_domain_assign_lock);
+}
+
+/**
+ * ccs_get_last_name - Get last component of a domainname.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns the last component of the domainname.
+ */
+const char *ccs_get_last_name(const struct domain_info *domain)
+{
+	const char *cp0 = domain->domainname->name, *cp1 = strrchr(cp0, ' ');
+	if (cp1)
+		return cp1 + 1;
+	return cp0;
+}
+
+/**
+ * ccs_add_domain_acl - Add the given ACL to the given domain.
+ *
+ * @domain: Pointer to "struct domain_info". May be NULL.
+ * @acl:    Pointer to "struct acl_info".
+ *
+ * Returns 0.
+ */
+int ccs_add_domain_acl(struct domain_info *domain, struct acl_info *acl)
+{
+	if (domain)
+		list1_add_tail_mb(&acl->list, &domain->acl_info_list);
+	else
+		acl->type &= ~ACL_DELETED;
+	ccs_update_counter(CCS_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * ccs_del_domain_acl - Delete the given ACL from the domain.
+ *
+ * @acl: Pointer to "struct acl_info". May be NULL.
+ *
+ * Returns 0.
+ */
+int ccs_del_domain_acl(struct acl_info *acl)
+{
+	if (acl)
+		acl->type |= ACL_DELETED;
+	ccs_update_counter(CCS_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * audit_execute_handler_log - Audit execute_handler log.
+ *
+ * @is_default: True if it is "execute_handler" log.
+ * @handler:    The realpath of the handler.
+ * @bprm:       Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_execute_handler_log(const bool is_default,
+				     const char *handler,
+				     struct linux_binprm *bprm)
+{
+	char *buf;
+	int len;
+	int len2;
+	u8 profile;
+	u8 mode;
+	if (ccs_can_save_audit_log(true) < 0)
+		return -ENOMEM;
+	len = strlen(handler) + 32;
+	profile = current->domain_info->profile;
+	mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	buf = ccs_init_audit_log(&len, profile, mode, bprm);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, "%s %s\n",
+		 is_default ? KEYWORD_EXECUTE_HANDLER :
+		 KEYWORD_DENIED_EXECUTE_HANDLER, handler);
+	return ccs_write_audit_log(buf, true);
+}
+
+/**
+ * audit_domain_creation_log - Audit domain creation log.
+ *
+ * @domainname: The name of newly created domain.
+ * @mode:       Access control mode used.
+ * @profile:    Profile number used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_domain_creation_log(const char *domainname, const u8 mode,
+				     const u8 profile)
+{
+	char *buf;
+	char *cp;
+	int len;
+	int len2;
+	if (ccs_can_save_audit_log(false) < 0)
+		return -ENOMEM;
+	len = strlen(domainname) + 32;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	cp = strchr(buf, '\n');
+	if (!cp) {
+		ccs_free(buf);
+		return -ENOMEM;
+	}
+	*++cp = '\0';
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, "%s\nuse_profile %u\n",
+		 domainname, profile);
+	return ccs_write_audit_log(buf, false);
+}
+
+/* The list for "struct domain_initializer_entry". */
+static LIST1_HEAD(domain_initializer_list);
+
+/**
+ * update_domain_initializer_entry - Update "struct domain_initializer_entry" list.
+ *
+ * @domainname: The name of domain. May be NULL.
+ * @program:    The name of program.
+ * @is_not:     True if it is "no_initialize_domain" entry.
+ * @is_delete:  True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_domain_initializer_entry(const char *domainname,
+					   const char *program,
+					   const bool is_not,
+					   const bool is_delete)
+{
+	struct domain_initializer_entry *new_entry;
+	struct domain_initializer_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_program;
+	const struct path_info *saved_domainname = NULL;
+	int error = -ENOMEM;
+	bool is_last_name = false;
+	if (!ccs_is_correct_path(program, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	if (domainname) {
+		if (!ccs_is_domain_def(domainname) &&
+		    ccs_is_correct_path(domainname, 1, -1, -1, __func__))
+			is_last_name = true;
+		else if (!ccs_is_correct_domain(domainname, __func__))
+			return -EINVAL;
+		saved_domainname = ccs_save_name(domainname);
+		if (!saved_domainname)
+			return -ENOMEM;
+	}
+	saved_program = ccs_save_name(program);
+	if (!saved_program)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &domain_initializer_list, list) {
+		if (ptr->is_not != is_not ||
+		    ptr->domainname != saved_domainname ||
+		    ptr->program != saved_program)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list1_add_tail_mb(&new_entry->list, &domain_initializer_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_read_domain_initializer_policy - Read "struct domain_initializer_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_domain_initializer_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &domain_initializer_list) {
+		const char *no;
+		const char *from = "";
+		const char *domain = "";
+		struct domain_initializer_entry *ptr;
+		ptr = list1_entry(pos, struct domain_initializer_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->domainname) {
+			from = " from ";
+			domain = ptr->domainname->name;
+		}
+		if (!ccs_io_printf(head,
+				   "%s" KEYWORD_INITIALIZE_DOMAIN "%s%s%s\n",
+				   no, ptr->program->name, from, domain))
+				goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * ccs_write_domain_initializer_policy - Write "struct domain_initializer_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_not:    True if it is "no_initialize_domain" entry.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_domain_initializer_policy(char *data, const bool is_not,
+					const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+	if (cp) {
+		*cp = '\0';
+		return update_domain_initializer_entry(cp + 6, data, is_not,
+						       is_delete);
+	}
+	return update_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/**
+ * is_domain_initializer - Check whether the given program causes domainname reinitialization.
+ *
+ * @domainname: The name of domain.
+ * @program:    The name of program.
+ * @last_name:  The last component of @domainname.
+ *
+ * Returns true if executing @program reinitializes domain transition,
+ * false otherwise.
+ */
+static bool is_domain_initializer(const struct path_info *domainname,
+				  const struct path_info *program,
+				  const struct path_info *last_name)
+{
+	struct domain_initializer_entry *ptr;
+	bool flag = false;
+	list1_for_each_entry(ptr,  &domain_initializer_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->domainname) {
+			if (!ptr->is_last_name) {
+				if (ptr->domainname != domainname)
+					continue;
+			} else {
+				if (ccs_pathcmp(ptr->domainname, last_name))
+					continue;
+			}
+		}
+		if (ccs_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/* The list for "struct domain_keeper_entry". */
+static LIST1_HEAD(domain_keeper_list);
+
+/**
+ * update_domain_keeper_entry - Update "struct domain_keeper_entry" list.
+ *
+ * @domainname: The name of domain.
+ * @program:    The name of program. May be NULL.
+ * @is_not:     True if it is "no_keep_domain" entry.
+ * @is_delete:  True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_domain_keeper_entry(const char *domainname,
+				      const char *program,
+				      const bool is_not, const bool is_delete)
+{
+	struct domain_keeper_entry *new_entry;
+	struct domain_keeper_entry *ptr;
+	const struct path_info *saved_domainname;
+	const struct path_info *saved_program = NULL;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	bool is_last_name = false;
+	if (!ccs_is_domain_def(domainname) &&
+	    ccs_is_correct_path(domainname, 1, -1, -1, __func__))
+		is_last_name = true;
+	else if (!ccs_is_correct_domain(domainname, __func__))
+		return -EINVAL;
+	if (program) {
+		if (!ccs_is_correct_path(program, 1, -1, -1, __func__))
+			return -EINVAL;
+		saved_program = ccs_save_name(program);
+		if (!saved_program)
+			return -ENOMEM;
+	}
+	saved_domainname = ccs_save_name(domainname);
+	if (!saved_domainname)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_not != is_not ||
+		    ptr->domainname != saved_domainname ||
+		    ptr->program != saved_program)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list1_add_tail_mb(&new_entry->list, &domain_keeper_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_write_domain_keeper_policy - Write "struct domain_keeper_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_not:    True if it is "no_keep_domain" entry.
+ * @is_delete: True if it is a delete request.
+ *
+ */
+int ccs_write_domain_keeper_policy(char *data, const bool is_not,
+				   const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+	if (cp) {
+		*cp = '\0';
+		return update_domain_keeper_entry(cp + 6, data,
+						  is_not, is_delete);
+	}
+	return update_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * ccs_read_domain_keeper_policy - Read "struct domain_keeper_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_domain_keeper_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &domain_keeper_list) {
+		struct domain_keeper_entry *ptr;
+		const char *no;
+		const char *from = "";
+		const char *program = "";
+		ptr = list1_entry(pos, struct domain_keeper_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->program) {
+			from = " from ";
+			program = ptr->program->name;
+		}
+		if (!ccs_io_printf(head,
+				   "%s" KEYWORD_KEEP_DOMAIN "%s%s%s\n", no,
+				   program, from, ptr->domainname->name))
+				goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * is_domain_keeper - Check whether the given program causes domain transition suppression.
+ *
+ * @domainname: The name of domain.
+ * @program:    The name of program.
+ * @last_name:  The last component of @domainname.
+ *
+ * Returns true if executing @program supresses domain transition,
+ * false otherwise.
+ */
+static bool is_domain_keeper(const struct path_info *domainname,
+			     const struct path_info *program,
+			     const struct path_info *last_name)
+{
+	struct domain_keeper_entry *ptr;
+	bool flag = false;
+	list1_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ptr->is_last_name) {
+			if (ptr->domainname != domainname)
+				continue;
+		} else {
+			if (ccs_pathcmp(ptr->domainname, last_name))
+				continue;
+		}
+		if (ptr->program && ccs_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/* The list for "struct alias_entry". */
+static LIST1_HEAD(alias_list);
+
+/**
+ * update_alias_entry - Update "struct alias_entry" list.
+ *
+ * @original_name: The original program's real name.
+ * @aliased_name:  The symbolic program's symbolic link's name.
+ * @is_delete:     True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_alias_entry(const char *original_name,
+			      const char *aliased_name,
+			      const bool is_delete)
+{
+	struct alias_entry *new_entry;
+	struct alias_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aliased_name;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(original_name, 1, -1, -1, __func__) ||
+	    !ccs_is_correct_path(aliased_name, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	saved_original_name = ccs_save_name(original_name);
+	saved_aliased_name = ccs_save_name(aliased_name);
+	if (!saved_original_name || !saved_aliased_name)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &alias_list, list) {
+		if (ptr->original_name != saved_original_name ||
+		    ptr->aliased_name != saved_aliased_name)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->original_name = saved_original_name;
+	new_entry->aliased_name = saved_aliased_name;
+	list1_add_tail_mb(&new_entry->list, &alias_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_read_alias_policy - Read "struct alias_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_alias_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &alias_list) {
+		struct alias_entry *ptr;
+		ptr = list1_entry(pos, struct alias_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALIAS "%s %s\n",
+				   ptr->original_name->name,
+				   ptr->aliased_name->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * ccs_write_alias_policy - Write "struct alias_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_alias_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return update_alias_entry(data, cp, is_delete);
+}
+
+/* The list for "struct aggregator_entry". */
+static LIST1_HEAD(aggregator_list);
+
+/**
+ * update_aggregator_entry - Update "struct aggregator_entry" list.
+ *
+ * @original_name:   The original program's name.
+ * @aggregated_name: The aggregated program's name.
+ * @is_delete:       True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_aggregator_entry(const char *original_name,
+				   const char *aggregated_name,
+				   const bool is_delete)
+{
+	struct aggregator_entry *new_entry;
+	struct aggregator_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aggregated_name;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(original_name, 1, 0, -1, __func__) ||
+	    !ccs_is_correct_path(aggregated_name, 1, -1, -1, __func__))
+		return -EINVAL;
+	saved_original_name = ccs_save_name(original_name);
+	saved_aggregated_name = ccs_save_name(aggregated_name);
+	if (!saved_original_name || !saved_aggregated_name)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &aggregator_list, list) {
+		if (ptr->original_name != saved_original_name ||
+		    ptr->aggregated_name != saved_aggregated_name)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->original_name = saved_original_name;
+	new_entry->aggregated_name = saved_aggregated_name;
+	list1_add_tail_mb(&new_entry->list, &aggregator_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * ccs_read_aggregator_policy - Read "struct aggregator_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool ccs_read_aggregator_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2, &aggregator_list) {
+		struct aggregator_entry *ptr;
+		ptr = list1_entry(pos, struct aggregator_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_AGGREGATOR "%s %s\n",
+				   ptr->original_name->name,
+				   ptr->aggregated_name->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * ccs_write_aggregator_policy - Write "struct aggregator_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_aggregator_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return update_aggregator_entry(data, cp, is_delete);
+}
+
+/* Domain create/delete/undelete handler. */
+
+/* #define DEBUG_DOMAIN_UNDELETE */
+
+/**
+ * ccs_delete_domain - Delete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns 0.
+ */
+int ccs_delete_domain(char *domainname)
+{
+	struct domain_info *domain;
+	struct path_info name;
+	name.name = domainname;
+	ccs_fill_path_info(&name);
+	mutex_lock(&new_domain_assign_lock);
+#ifdef DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "ccs_delete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (ccs_pathcmp(domain->domainname, &name))
+			continue;
+		printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted);
+	}
+#endif
+	/* Is there an active domain? */
+	list1_for_each_entry(domain, &domain_list, list) {
+		struct domain_info *domain2;
+		/* Never delete KERNEL_DOMAIN */
+		if (domain == &KERNEL_DOMAIN)
+			continue;
+		if (domain->is_deleted ||
+		    ccs_pathcmp(domain->domainname, &name))
+			continue;
+		/* Mark already deleted domains as non undeletable. */
+		list1_for_each_entry(domain2, &domain_list, list) {
+			if (!domain2->is_deleted ||
+			    ccs_pathcmp(domain2->domainname, &name))
+				continue;
+#ifdef DEBUG_DOMAIN_UNDELETE
+			if (domain2->is_deleted != 255)
+				printk(KERN_DEBUG
+				       "Marked %p as non undeletable\n",
+				       domain2);
+#endif
+			domain2->is_deleted = 255;
+		}
+		/* Delete and mark active domain as undeletable. */
+		domain->is_deleted = 1;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "Marked %p as undeletable\n", domain);
+#endif
+		break;
+	}
+	mutex_unlock(&new_domain_assign_lock);
+	return 0;
+}
+
+/**
+ * ccs_undelete_domain - Undelete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns pointer to "struct domain_info" on success, NULL otherwise.
+ */
+struct domain_info *ccs_undelete_domain(const char *domainname)
+{
+	struct domain_info *domain;
+	struct domain_info *candidate_domain = NULL;
+	struct path_info name;
+	name.name = domainname;
+	ccs_fill_path_info(&name);
+	mutex_lock(&new_domain_assign_lock);
+#ifdef DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "ccs_undelete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (ccs_pathcmp(domain->domainname, &name))
+			continue;
+		printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted);
+	}
+#endif
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (ccs_pathcmp(&name, domain->domainname))
+			continue;
+		if (!domain->is_deleted) {
+			/* This domain is active. I can't undelete. */
+			candidate_domain = NULL;
+#ifdef DEBUG_DOMAIN_UNDELETE
+			printk(KERN_DEBUG "%p is active. I can't undelete.\n",
+			       domain);
+#endif
+			break;
+		}
+		/* Is this domain undeletable? */
+		if (domain->is_deleted == 1)
+			candidate_domain = domain;
+	}
+	if (candidate_domain) {
+		candidate_domain->is_deleted = 0;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "%p was undeleted.\n", candidate_domain);
+#endif
+	}
+	mutex_unlock(&new_domain_assign_lock);
+	return candidate_domain;
+}
+
+/**
+ * ccs_find_or_assign_new_domain - Create a domain.
+ *
+ * @domainname: The name of domain.
+ * @profile:    Profile number to assign if the domain was newly created.
+ *
+ * Returns pointer to "struct domain_info" on success, NULL otherwise.
+ */
+struct domain_info *ccs_find_or_assign_new_domain(const char *domainname,
+						  const u8 profile)
+{
+	struct domain_info *domain = NULL;
+	const struct path_info *saved_domainname;
+	mutex_lock(&new_domain_assign_lock);
+	domain = ccs_find_domain(domainname);
+	if (domain)
+		goto out;
+	if (!ccs_is_correct_domain(domainname, __func__))
+		goto out;
+	saved_domainname = ccs_save_name(domainname);
+	if (!saved_domainname)
+		goto out;
+	/* Can I reuse memory of deleted domain? */
+	list1_for_each_entry(domain, &domain_list, list) {
+		struct task_struct *p;
+		struct acl_info *ptr;
+		bool flag;
+		if (!domain->is_deleted ||
+		    domain->domainname != saved_domainname)
+			continue;
+		flag = false;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		for_each_process(p) {
+			if (p->domain_info != domain)
+				continue;
+			flag = true;
+			break;
+		}
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (flag)
+			continue;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "Reusing %p %s\n", domain,
+		       domain->domainname->name);
+#endif
+		list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+			ptr->type |= ACL_DELETED;
+		}
+		/*
+		 * Don't use ccs_set_domain_flag() because
+		 * new_domain_assign_lock is held.
+		 */
+		domain->flags = 0;
+		domain->profile = profile;
+		domain->quota_warned = false;
+		mb(); /* Avoid out-of-order execution. */
+		domain->is_deleted = 0;
+		goto out;
+	}
+	/* No memory reusable. Create using new memory. */
+	domain = ccs_alloc_element(sizeof(*domain));
+	if (domain) {
+		INIT_LIST1_HEAD(&domain->acl_info_list);
+		domain->domainname = saved_domainname;
+		domain->profile = profile;
+		list1_add_tail_mb(&domain->list, &domain_list);
+	}
+ out:
+	mutex_unlock(&new_domain_assign_lock);
+	return domain;
+}
+
+/**
+ * get_argv0 - Get argv[0].
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @tmp:  Buffer for temporal use.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool get_argv0(struct linux_binprm *bprm, struct ccs_page_buffer *tmp)
+{
+	char *arg_ptr = tmp->buffer;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+	bool done = false;
+	if (!bprm->argc)
+		goto out;
+	while (1) {
+		struct page *page;
+		const char *kaddr;
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page,
+				   NULL) <= 0)
+			goto out;
+		pos += PAGE_SIZE - offset;
+#else
+		page = bprm->page[i];
+#endif
+		/* Map. */
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			goto out;
+		}
+		/* Read. */
+		while (offset < PAGE_SIZE) {
+			const unsigned char c = kaddr[offset++];
+			if (c && arg_len < CCS_MAX_PATHNAME_LEN - 10) {
+				if (c == '\\') {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = '\\';
+				} else if (c == '/') {
+					arg_len = 0;
+				} else if (c > ' ' && c < 127) {
+					arg_ptr[arg_len++] = c;
+				} else {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = (c >> 6) + '0';
+					arg_ptr[arg_len++]
+						= ((c >> 3) & 7) + '0';
+					arg_ptr[arg_len++] = (c & 7) + '0';
+				}
+			} else {
+				arg_ptr[arg_len] = '\0';
+				done = true;
+				break;
+			}
+		}
+		/* Unmap. */
+		kunmap(page);
+#ifdef CONFIG_MMU
+		put_page(page);
+#endif
+		i++;
+		offset = 0;
+		if (done)
+			break;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * find_next_domain - Find a domain.
+ *
+ * @bprm:           Pointer to "struct linux_binprm".
+ * @next_domain:    Pointer to pointer to "struct domain_info".
+ * @path_to_verify: Pathname to verify. May be NULL.
+ * @tmp:            Buffer for temporal use.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int find_next_domain(struct linux_binprm *bprm,
+			    struct domain_info **next_domain,
+			    const struct path_info *path_to_verify,
+			    struct ccs_page_buffer *tmp)
+{
+	/*
+	 * This function assumes that the size of buffer returned by
+	 * ccs_realpath() = CCS_MAX_PATHNAME_LEN.
+	 */
+	struct domain_info *old_domain = current->domain_info;
+	struct domain_info *domain = NULL;
+	const char *old_domain_name = old_domain->domainname->name;
+	const char *original_name = bprm->filename;
+	char *new_domain_name = NULL;
+	char *real_program_name = NULL;
+	char *symlink_program_name = NULL;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	int retval;
+	struct path_info r; /* real name */
+	struct path_info s; /* symlink name */
+	struct path_info l; /* last name */
+
+	{
+		/*
+		 * Built-in initializers. This is needed because policies are
+		 * not loaded until starting /sbin/init.
+		 */
+		static bool first = true;
+		if (first) {
+			update_domain_initializer_entry(NULL, "/sbin/hotplug",
+							false, false);
+			update_domain_initializer_entry(NULL, "/sbin/modprobe",
+							false, false);
+			first = false;
+		}
+	}
+
+	/* Get ccs_realpath of program. */
+	retval = -ENOENT; /* I hope ccs_realpath() won't fail with -ENOMEM. */
+	real_program_name = ccs_realpath(original_name);
+	if (!real_program_name)
+		goto out;
+	/* Get ccs_realpath of symbolic link. */
+	symlink_program_name = ccs_realpath_nofollow(original_name);
+	if (!symlink_program_name)
+		goto out;
+
+	r.name = real_program_name;
+	ccs_fill_path_info(&r);
+	s.name = symlink_program_name;
+	ccs_fill_path_info(&s);
+	l.name = ccs_get_last_name(old_domain);
+	ccs_fill_path_info(&l);
+
+	if (path_to_verify) {
+		if (ccs_pathcmp(&r, path_to_verify)) {
+			/* Failed to verify execute handler. */
+			static u8 counter = 20;
+			if (counter) {
+				counter--;
+				printk(KERN_WARNING "Failed to verify: %s\n",
+				       path_to_verify->name);
+			}
+			goto out;
+		}
+		goto calculate_domain;
+	}
+
+	/* Check 'alias' directive. */
+	if (ccs_pathcmp(&r, &s)) {
+		struct alias_entry *ptr;
+		/* Is this program allowed to be called via symbolic links? */
+		list1_for_each_entry(ptr, &alias_list, list) {
+			if (ptr->is_deleted ||
+			    ccs_pathcmp(&r, ptr->original_name) ||
+			    ccs_pathcmp(&s, ptr->aliased_name))
+				continue;
+			memset(real_program_name, 0, CCS_MAX_PATHNAME_LEN);
+			strncpy(real_program_name, ptr->aliased_name->name,
+				CCS_MAX_PATHNAME_LEN - 1);
+			ccs_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Compare basename of real_program_name and argv[0] */
+	if (bprm->argc > 0 && ccs_check_flags(CCS_TOMOYO_MAC_FOR_ARGV0)) {
+		char *base_argv0 = tmp->buffer;
+		const char *base_filename;
+		retval = -ENOMEM;
+		if (!get_argv0(bprm, tmp))
+			goto out;
+		base_filename = strrchr(real_program_name, '/');
+		if (!base_filename)
+			base_filename = real_program_name;
+		else
+			base_filename++;
+		if (strcmp(base_argv0, base_filename)) {
+			retval = ccs_check_argv0_perm(&r, base_argv0);
+			if (retval)
+				goto out;
+		}
+	}
+
+	/* Check 'aggregator' directive. */
+	{
+		struct aggregator_entry *ptr;
+		/* Is this program allowed to be aggregated? */
+		list1_for_each_entry(ptr, &aggregator_list, list) {
+			if (ptr->is_deleted ||
+			    !ccs_path_matches_pattern(&r, ptr->original_name))
+				continue;
+			memset(real_program_name, 0, CCS_MAX_PATHNAME_LEN);
+			strncpy(real_program_name, ptr->aggregated_name->name,
+				CCS_MAX_PATHNAME_LEN - 1);
+			ccs_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Check execute permission. */
+	retval = ccs_check_exec_perm(&r, bprm, tmp);
+	if (retval < 0)
+		goto out;
+
+ calculate_domain:
+	new_domain_name = tmp->buffer;
+	if (is_domain_initializer(old_domain->domainname, &r, &l)) {
+		/* Transit to the child of KERNEL_DOMAIN domain. */
+		snprintf(new_domain_name, CCS_MAX_PATHNAME_LEN + 1,
+			 ROOT_NAME " " "%s", real_program_name);
+	} else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started) {
+		/*
+		 * Needn't to transit from kernel domain before starting
+		 * /sbin/init. But transit from kernel domain if executing
+		 * initializers because they might start before /sbin/init.
+		 */
+		domain = old_domain;
+	} else if (is_domain_keeper(old_domain->domainname, &r, &l)) {
+		/* Keep current domain. */
+		domain = old_domain;
+	} else {
+		/* Normal domain transition. */
+		snprintf(new_domain_name, CCS_MAX_PATHNAME_LEN + 1,
+			 "%s %s", old_domain_name, real_program_name);
+	}
+	if (domain || strlen(new_domain_name) >= CCS_MAX_PATHNAME_LEN)
+		goto done;
+	domain = ccs_find_domain(new_domain_name);
+	if (domain)
+		goto done;
+	if (is_enforce && ccs_check_supervisor("#Need to create domain\n%s\n",
+					       new_domain_name))
+			goto done;
+	domain = ccs_find_or_assign_new_domain(new_domain_name,
+					       old_domain->profile);
+	if (domain)
+		audit_domain_creation_log(new_domain_name, mode,
+					  domain->profile);
+ done:
+	if (!domain) {
+		printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n",
+		       new_domain_name);
+		if (is_enforce)
+			retval = -EPERM;
+	} else {
+		retval = 0;
+	}
+ out:
+	ccs_free(real_program_name);
+	ccs_free(symlink_program_name);
+	*next_domain = domain ? domain : old_domain;
+	return retval;
+}
+
+/**
+ * check_environ - Check permission for environment variable names.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @tmp:  Buffer for temporal use.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_environ(struct linux_binprm *bprm, struct ccs_page_buffer *tmp)
+{
+	const u8 profile = current->domain_info->profile;
+	const u8 mode = ccs_check_flags(CCS_TOMOYO_MAC_FOR_ENV);
+	char *arg_ptr = tmp->buffer;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+	int argv_count = bprm->argc;
+	int envp_count = bprm->envc;
+	/* printk(KERN_DEBUG "start %d %d\n", argv_count, envp_count); */
+	int error = -ENOMEM;
+	if (!mode || !envp_count)
+		return 0;
+	while (error == -ENOMEM) {
+		struct page *page;
+		const char *kaddr;
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page,
+				   NULL) <= 0)
+			goto out;
+		pos += PAGE_SIZE - offset;
+#else
+		page = bprm->page[i];
+#endif
+		/* Map. */
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			goto out;
+		}
+		/* Read. */
+		while (argv_count && offset < PAGE_SIZE) {
+			if (!kaddr[offset++])
+				argv_count--;
+		}
+		if (argv_count)
+			goto unmap_page;
+		while (offset < PAGE_SIZE) {
+			const unsigned char c = kaddr[offset++];
+			if (c && arg_len < CCS_MAX_PATHNAME_LEN - 10) {
+				if (c == '=') {
+					arg_ptr[arg_len++] = '\0';
+				} else if (c == '\\') {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = '\\';
+				} else if (c > ' ' && c < 127) {
+					arg_ptr[arg_len++] = c;
+				} else {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = (c >> 6) + '0';
+					arg_ptr[arg_len++]
+						= ((c >> 3) & 7) + '0';
+					arg_ptr[arg_len++] = (c & 7) + '0';
+				}
+			} else {
+				arg_ptr[arg_len] = '\0';
+			}
+			if (c)
+				continue;
+			if (ccs_check_env_perm(arg_ptr, profile, mode)) {
+				error = -EPERM;
+				break;
+			}
+			if (!--envp_count) {
+				error = 0;
+				break;
+			}
+			arg_len = 0;
+		}
+ unmap_page:
+		/* Unmap. */
+		kunmap(page);
+#ifdef CONFIG_MMU
+		put_page(page);
+#endif
+		i++;
+		offset = 0;
+	}
+ out:
+	if (error && mode != 3)
+		error = 0;
+	return error;
+}
+
+/**
+ * unescape - Unescape escaped string.
+ *
+ * @dest: String to unescape.
+ *
+ * Returns nothing.
+ */
+static void unescape(unsigned char *dest)
+{
+	unsigned char *src = dest;
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	while ((c = *src++) != '\0') {
+		if (c != '\\') {
+			*dest++ = c;
+			continue;
+		}
+		c = *src++;
+		if (c == '\\') {
+			*dest++ = c;
+			continue;
+		}
+		if (c < '0' || c > '3')
+			break;
+		d = *src++;
+		if (d < '0' || d > '7')
+			break;
+		e = *src++;
+		if (e < '0' || e > '7')
+			break;
+		*dest++ = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0');
+	}
+	*dest = '\0';
+}
+
+/**
+ * root_depth - Get number of directories to strip.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @vfsmnt: Pointer to "struct vfsmount".
+ *
+ * Returns number of directories to strip.
+ */
+static inline int root_depth(struct dentry *dentry, struct vfsmount *vfsmnt)
+{
+	int depth = 0;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&dcache_lock);
+	spin_lock(&vfsmount_lock);
+	for (;;) {
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Global root? */
+			if (vfsmnt->mnt_parent == vfsmnt)
+				break;
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			continue;
+		}
+		dentry = dentry->d_parent;
+		depth++;
+	}
+	spin_unlock(&vfsmount_lock);
+	spin_unlock(&dcache_lock);
+	/***** CRITICAL SECTION END *****/
+	return depth;
+}
+
+/**
+ * get_root_depth - return the depth of root directory.
+ *
+ * Returns number of directories to strip.
+ */
+static int get_root_depth(void)
+{
+	int depth;
+	struct dentry *dentry;
+	struct vfsmount *vfsmnt;
+	struct path root;
+	/***** CRITICAL SECTION START *****/
+	read_lock(&current->fs->lock);
+	root = current->fs->root;
+	path_get(&current->fs->root);
+	dentry = root.dentry;
+	vfsmnt = root.mnt;
+	read_unlock(&current->fs->lock);
+	/***** CRITICAL SECTION END *****/
+	depth = root_depth(dentry, vfsmnt);
+	path_put(&root);
+	return depth;
+}
+
+/**
+ * try_alt_exec - Try to start execute handler.
+ *
+ * @bprm:        Pointer to "struct linux_binprm".
+ * @filename:    The name of requested program.
+ * @work:        Pointer to pointer to the name of execute handler.
+ * @next_domain: Pointer to pointer to "struct domain_info".
+ * @tmp:         Buffer for temporal use.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int try_alt_exec(struct linux_binprm *bprm,
+			const struct path_info *filename, char **work,
+			struct domain_info **next_domain,
+			struct ccs_page_buffer *tmp)
+{
+	/*
+	 * Contents of modified bprm.
+	 * The envp[] in original bprm is moved to argv[] so that
+	 * the alternatively executed program won't be affected by
+	 * some dangerous environment variables like LD_PRELOAD.
+	 *
+	 * modified bprm->argc
+	 *    = original bprm->argc + original bprm->envc + 7
+	 * modified bprm->envc
+	 *    = 0
+	 *
+	 * modified bprm->argv[0]
+	 *    = the program's name specified by execute_handler
+	 * modified bprm->argv[1]
+	 *    = current->domain_info->domainname->name
+	 * modified bprm->argv[2]
+	 *    = the current process's name
+	 * modified bprm->argv[3]
+	 *    = the current process's information (e.g. uid/gid).
+	 * modified bprm->argv[4]
+	 *    = original bprm->filename
+	 * modified bprm->argv[5]
+	 *    = original bprm->argc in string expression
+	 * modified bprm->argv[6]
+	 *    = original bprm->envc in string expression
+	 * modified bprm->argv[7]
+	 *    = original bprm->argv[0]
+	 *  ...
+	 * modified bprm->argv[bprm->argc + 6]
+	 *     = original bprm->argv[bprm->argc - 1]
+	 * modified bprm->argv[bprm->argc + 7]
+	 *     = original bprm->envp[0]
+	 *  ...
+	 * modified bprm->argv[bprm->envc + bprm->argc + 6]
+	 *     = original bprm->envp[bprm->envc - 1]
+	 */
+	struct file *filp;
+	int retval;
+	const int original_argc = bprm->argc;
+	const int original_envc = bprm->envc;
+	struct task_struct *task = current;
+	char *buffer = tmp->buffer;
+	/* Allocate memory for execute handler's pathname. */
+	char *execute_handler = ccs_alloc(sizeof(struct ccs_page_buffer));
+	*work = execute_handler;
+	if (!execute_handler)
+		return -ENOMEM;
+	strncpy(execute_handler, filename->name,
+		sizeof(struct ccs_page_buffer) - 1);
+	unescape(execute_handler);
+
+	/* Close the requested program's dentry. */
+	allow_write_access(bprm->file);
+	fput(bprm->file);
+	bprm->file = NULL;
+
+	{ /* Adjust root directory for open_exec(). */
+		int depth = get_root_depth();
+		char *cp = execute_handler;
+		if (!*cp || *cp != '/')
+			return -ENOENT;
+		while (depth) {
+			cp = strchr(cp + 1, '/');
+			if (!cp)
+				return -ENOENT;
+			depth--;
+		}
+		memmove(execute_handler, cp, strlen(cp) + 1);
+	}
+
+	/* Move envp[] to argv[] */
+	bprm->argc += bprm->envc;
+	bprm->envc = 0;
+
+	/* Set argv[6] */
+	{
+		snprintf(buffer, sizeof(struct ccs_page_buffer) - 1, "%d",
+			 original_envc);
+		retval = copy_strings_kernel(1, &buffer, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[5] */
+	{
+		snprintf(buffer, sizeof(struct ccs_page_buffer) - 1, "%d",
+			 original_argc);
+		retval = copy_strings_kernel(1, &buffer, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[4] */
+	{
+		retval = copy_strings_kernel(1, &bprm->filename, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[3] */
+	{
+		const u32 tomoyo_flags = task->tomoyo_flags;
+		snprintf(buffer, sizeof(struct ccs_page_buffer) - 1,
+			 "pid=%d uid=%d gid=%d euid=%d egid=%d suid=%d "
+			 "sgid=%d fsuid=%d fsgid=%d state[0]=%u "
+			 "state[1]=%u state[2]=%u",
+			 task->pid, task->uid, task->gid, task->euid,
+			 task->egid, task->suid, task->sgid, task->fsuid,
+			 task->fsgid, (u8) (tomoyo_flags >> 24),
+			 (u8) (tomoyo_flags >> 16), (u8) (tomoyo_flags >> 8));
+		retval = copy_strings_kernel(1, &buffer, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[2] */
+	{
+		char *exe = (char *) ccs_get_exe();
+		if (exe) {
+			retval = copy_strings_kernel(1, &exe, bprm);
+			ccs_free(exe);
+		} else {
+			snprintf(buffer, sizeof(struct ccs_page_buffer) - 1,
+				 "<unknown>");
+			retval = copy_strings_kernel(1, &buffer, bprm);
+		}
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[1] */
+	{
+		strncpy(buffer, task->domain_info->domainname->name,
+			sizeof(struct ccs_page_buffer) - 1);
+		retval = copy_strings_kernel(1, &buffer, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* Set argv[0] */
+	{
+		retval = copy_strings_kernel(1, &execute_handler, bprm);
+		if (retval < 0)
+			goto out;
+		bprm->argc++;
+	}
+
+	/* OK, now restart the process with execute handler program's dentry. */
+	filp = open_exec(execute_handler);
+	if (IS_ERR(filp)) {
+		retval = PTR_ERR(filp);
+		goto out;
+	}
+	bprm->file = filp;
+	bprm->filename = execute_handler;
+	bprm->interp = execute_handler;
+	retval = prepare_binprm(bprm);
+	if (retval < 0)
+		goto out;
+	task->tomoyo_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+	retval = find_next_domain(bprm, next_domain, filename, tmp);
+	task->tomoyo_flags &= ~CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
+ out:
+	return retval;
+}
+
+/**
+ * find_execute_handler - Find an execute handler.
+ *
+ * @type: Type of execute handler.
+ *
+ * Returns pointer to "struct path_info" if found, NULL otherwise.
+ */
+static const struct path_info *find_execute_handler(const u8 type)
+{
+	struct task_struct *task = current;
+	const struct domain_info *domain = task->domain_info;
+	struct acl_info *ptr;
+	/*
+	 * Don't use execute handler if the current process is
+	 * marked as execute handler to avoid infinite execute handler loop.
+	 */
+	if (task->tomoyo_flags & TOMOYO_TASK_IS_EXECUTE_HANDLER)
+		return NULL;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct execute_handler_record *acl;
+		if (ptr->type != type)
+			continue;
+		acl = container_of(ptr, struct execute_handler_record, head);
+		return acl->handler;
+	}
+	return NULL;
+}
+
+/**
+ * search_binary_handler_with_transition - Perform domain transition.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @regs: Pointer to "struct pt_regs".
+ *
+ * Returns result of search_binary_handler() on success,
+ * negative value otherwise.
+ */
+int search_binary_handler_with_transition(struct linux_binprm *bprm,
+					  struct pt_regs *regs)
+{
+	struct task_struct *task = current;
+	struct domain_info *next_domain = NULL;
+	struct domain_info *prev_domain = task->domain_info;
+	const struct path_info *handler;
+	int retval;
+	/*
+	 * "work" holds path to program.
+	 * Thus, keep valid until search_binary_handler() finishes.
+	 */
+	char *work = NULL;
+	struct ccs_page_buffer *buf = ccs_alloc(sizeof(struct ccs_page_buffer));
+	ccs_load_policy(bprm->filename);
+	if (!buf)
+		return -ENOMEM;
+	/* printk(KERN_DEBUG "rootdepth=%d\n", get_root_depth()); */
+	handler = find_execute_handler(TYPE_EXECUTE_HANDLER);
+	if (handler) {
+		retval = try_alt_exec(bprm, handler, &work, &next_domain, buf);
+		if (!retval)
+			audit_execute_handler_log(true, work, bprm);
+		goto ok;
+	}
+	retval = find_next_domain(bprm, &next_domain, NULL, buf);
+	if (retval != -EPERM)
+		goto ok;
+	handler = find_execute_handler(TYPE_DENIED_EXECUTE_HANDLER);
+	if (handler) {
+		retval = try_alt_exec(bprm, handler, &work, &next_domain, buf);
+		if (!retval)
+			audit_execute_handler_log(false, work, bprm);
+	}
+ ok:
+	if (retval)
+		goto out;
+	task->domain_info = next_domain;
+	retval = check_environ(bprm, buf);
+	if (retval)
+		goto out;
+	task->tomoyo_flags |= TOMOYO_CHECK_READ_FOR_OPEN_EXEC;
+	retval = search_binary_handler(bprm, regs);
+	task->tomoyo_flags &= ~TOMOYO_CHECK_READ_FOR_OPEN_EXEC;
+ out:
+	/* Return to previous domain if execution failed. */
+	if (retval < 0)
+		task->domain_info = prev_domain;
+	/* Mark the current process as execute handler. */
+	else if (handler)
+		task->tomoyo_flags |= TOMOYO_TASK_IS_EXECUTE_HANDLER;
+	/* Mark the current process as normal process. */
+	else
+		task->tomoyo_flags &= ~TOMOYO_TASK_IS_EXECUTE_HANDLER;
+	ccs_free(work);
+	ccs_free(buf);
+	return retval;
+}
+
+#else
+
+/**
+ * search_binary_handler_with_transition - Wrapper for search_binary_handler().
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @regs: Pointer to "struct pt_regs".
+ *
+ * Returns the result of search_binary_handler().
+ */
+int search_binary_handler_with_transition(struct linux_binprm *bprm,
+					  struct pt_regs *regs)
+{
+#ifdef CONFIG_SAKURA
+	ccs_load_policy(bprm->filename);
+#endif
+	return search_binary_handler(bprm, regs);
+}
+
+#endif

-- 

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

* [TOMOYO #7 27/30] Environment variable restriction part.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (25 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 26/30] Domain transition handler Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 28/30] Filesystem part of tamper-proof device filesystem Tetsuo Handa
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-environment-variable-restriction-part.patch --]
[-- Type: text/plain, Size: 9030 bytes --]

This file controls environment variable names that can be passed to execve().

TOMOYO checks environment variable's names passed to execve()
because some environment variables affect to the behavior of program
like argv[0].

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/tomoyo_env.c |  297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 297 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/tomoyo_env.c
@@ -0,0 +1,297 @@
+/*
+ * fs/tomoyo_env.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ */
+
+#include <linux/ccs_common.h>
+#include <linux/tomoyo.h>
+#include <linux/realpath.h>
+
+/**
+ * audit_env_log - Audit environment variable name log.
+ *
+ * @env:        The name of environment variable.
+ * @is_granted: True if this is a granted log.
+ * @profile:    Profile number used.
+ * @mode:       Access control mode used.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int audit_env_log(const char *env, const bool is_granted,
+			 const u8 profile, const u8 mode)
+{
+	char *buf;
+	int len;
+	int len2;
+	if (ccs_can_save_audit_log(is_granted) < 0)
+		return -ENOMEM;
+	len = strlen(env) + 64;
+	buf = ccs_init_audit_log(&len, profile, mode, NULL);
+	if (!buf)
+		return -ENOMEM;
+	len2 = strlen(buf);
+	snprintf(buf + len2, len - len2 - 1, KEYWORD_ALLOW_ENV "%s\n", env);
+	return ccs_write_audit_log(buf, is_granted);
+}
+
+/* Structure for "allow_env" keyword. */
+struct globally_usable_env_entry {
+	struct list1_head list;
+	const struct path_info *env;
+	bool is_deleted;
+};
+
+/* The list for "struct globally_usable_env_entry". */
+static LIST1_HEAD(globally_usable_env_list);
+
+/**
+ * update_globally_usable_env_entry - Update "struct globally_usable_env_entry" list.
+ *
+ * @env:       The name of environment variable.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_globally_usable_env_entry(const char *env,
+					    const bool is_delete)
+{
+	struct globally_usable_env_entry *new_entry;
+	struct globally_usable_env_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_env;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(env, 0, 0, 0, __func__) || strchr(env, '='))
+		return -EINVAL;
+	saved_env = ccs_save_name(env);
+	if (!saved_env)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &globally_usable_env_list, list) {
+		if (ptr->env != saved_env)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = ccs_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->env = saved_env;
+	list1_add_tail_mb(&new_entry->list, &globally_usable_env_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	ccs_update_counter(CCS_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * is_globally_usable_env - Check whether the given environment variable is acceptable for all domains.
+ *
+ * @env: The name of environment variable.
+ *
+ * Returns true if @env is globally permitted environment variable's name,
+ * false otherwise.
+ */
+static bool is_globally_usable_env(const struct path_info *env)
+{
+	struct globally_usable_env_entry *ptr;
+	list1_for_each_entry(ptr, &globally_usable_env_list, list) {
+		if (!ptr->is_deleted && ccs_path_matches_pattern(env, ptr->env))
+			return true;
+	}
+	return false;
+}
+
+/**
+ * ccs_write_globally_usable_env_policy - Write "struct globally_usable_env_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_globally_usable_env_policy(char *data, const bool is_delete)
+{
+	return update_globally_usable_env_entry(data, is_delete);
+}
+
+/**
+ * ccs_read_globally_usable_env_policy - Read "struct globally_usable_env_entry" list.
+ *
+ * @head: Pointer to "struct ccs_io_buffer".
+ *
+ * Returns 0 on success, false otherwise.
+ */
+bool ccs_read_globally_usable_env_policy(struct ccs_io_buffer *head)
+{
+	struct list1_head *pos;
+	list1_for_each_cookie(pos, head->read_var2,
+			      &globally_usable_env_list) {
+		struct globally_usable_env_entry *ptr;
+		ptr = list1_entry(pos, struct globally_usable_env_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!ccs_io_printf(head, KEYWORD_ALLOW_ENV "%s\n",
+				   ptr->env->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * update_env_entry - Update "struct env_acl_record" list.
+ *
+ * @env:       The name of environment variable.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_env_entry(const char *env, struct domain_info *domain,
+			    const struct condition_list *condition,
+			    const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct env_acl_record *acl;
+	const struct path_info *saved_env;
+	int error = -ENOMEM;
+	if (!ccs_is_correct_path(env, 0, 0, 0, __func__) || strchr(env, '='))
+		return -EINVAL;
+	saved_env = ccs_save_name(env);
+	if (!saved_env)
+		return -ENOMEM;
+
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type1(ptr) != TYPE_ENV_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct env_acl_record, head);
+		if (acl->env != saved_env)
+			continue;
+		error = ccs_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = ccs_alloc_acl_element(TYPE_ENV_ACL, condition);
+	if (!acl)
+		goto out;
+	acl->env = saved_env;
+	error = ccs_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ccs_acl_type2(ptr) != TYPE_ENV_ACL)
+			continue;
+		if (ccs_get_condition_part(ptr) != condition)
+			continue;
+		acl = container_of(ptr, struct env_acl_record, head);
+		if (acl->env != saved_env)
+			continue;
+		error = ccs_del_domain_acl(ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * check_env_acl - Check permission for environment variable's name.
+ *
+ * @environ: The name of environment variable.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_env_acl(const char *environ)
+{
+	const struct domain_info *domain = current->domain_info;
+	int error = -EPERM;
+	struct acl_info *ptr;
+	struct path_info env;
+	env.name = environ;
+	ccs_fill_path_info(&env);
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct env_acl_record *acl;
+		if (ccs_acl_type2(ptr) != TYPE_ENV_ACL)
+			continue;
+		acl = container_of(ptr, struct env_acl_record, head);
+		if (!ccs_check_condition(ptr, NULL) ||
+		    !ccs_path_matches_pattern(&env, acl->env))
+			continue;
+		ccs_update_condition(ptr);
+		error = 0;
+		break;
+	}
+	if (error &&
+	    (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_ENV) == 0 &&
+	    is_globally_usable_env(&env))
+		error = 0;
+	return error;
+}
+
+/**
+ * ccs_check_env_perm - Check permission for environment variable's name.
+ *
+ * @env:     The name of environment variable.
+ * @profile: Profile number.
+ * @mode:    Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_check_env_perm(const char *env, const u8 profile, const u8 mode)
+{
+	int error = 0;
+	struct domain_info * const domain = current->domain_info;
+	const bool is_enforce = (mode == 3);
+	if (!env || !*env)
+		return 0;
+	error = check_env_acl(env);
+	audit_env_log(env, !error, profile, mode);
+	if (!error)
+		return 0;
+	if (ccs_verbose_mode())
+		printk(KERN_WARNING "TOMOYO-%s: Environ %s denied for %s\n",
+		       ccs_get_msg(is_enforce), env, ccs_get_last_name(domain));
+	if (is_enforce)
+		return ccs_check_supervisor("%s\n" KEYWORD_ALLOW_ENV "%s\n",
+					    domain->domainname->name, env);
+	if (mode == 1 && ccs_check_domain_quota(domain))
+		update_env_entry(env, domain, NULL, false);
+	return 0;
+}
+
+/**
+ * ccs_write_env_policy - Write "struct env_acl_record" list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @condition: Pointer to "struct condition_list". May be NULL.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int ccs_write_env_policy(char *data, struct domain_info *domain,
+			 const struct condition_list *condition,
+			 const bool is_delete)
+{
+	return update_env_entry(data, domain, condition, is_delete);
+}

-- 

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

* [TOMOYO #7 28/30] Filesystem part of tamper-proof device filesystem.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (26 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 27/30] Environment variable restriction part Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 29/30] Kconfig and Makefile Tetsuo Handa
                   ` (2 subsequent siblings)
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-tamper-proof-device-filesystem-part.patch --]
[-- Type: text/plain, Size: 9862 bytes --]

This file is almost identical to fs/ramfs/inode.c except that
there are hooks for checking filename-attribute pairs.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/syaoran_2.6.c |  342 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 342 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/syaoran_2.6.c
@@ -0,0 +1,342 @@
+/*
+ * fs/syaoran_2.6.c
+ *
+ * Implementation of the Tamper-Proof Device Filesystem.
+ *
+ * Portions Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 1.6.0   2008/04/01
+ *
+ * This filesystem is developed using the ramfs implementation.
+ *
+ */
+/*
+ * Resizable simple ram filesystem for Linux.
+ *
+ * Copyright (C) 2000 Linus Torvalds.
+ *               2000 Transmeta Corp.
+ *
+ * Usage limits added by David Gibson, Linuxcare Australia.
+ * This file is released under the GPL.
+ */
+
+/*
+ * NOTE! This filesystem is probably most useful
+ * not as a real filesystem, but as an example of
+ * how virtual filesystems can be written.
+ *
+ * It doesn't get much simpler than this. Consider
+ * that this file implements the full semantics of
+ * a POSIX-compliant read-write filesystem.
+ *
+ * Note in particular how the filesystem does not
+ * need to implement any data structures of its own
+ * to keep track of the virtual data: using the VFS
+ * caches is sufficient.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/smp_lock.h>
+#include <linux/backing-dev.h>
+
+#include <linux/uaccess.h>
+
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+
+static struct super_operations syaoran_ops;
+static struct address_space_operations syaoran_aops;
+static struct inode_operations syaoran_file_inode_operations;
+static struct inode_operations syaoran_dir_inode_operations;
+static struct inode_operations syaoran_symlink_inode_operations;
+static struct file_operations syaoran_file_operations;
+
+static struct backing_dev_info syaoran_backing_dev_info = {
+	.ra_pages      = 0,    /* No readahead */
+	.capabilities  = BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK |
+	BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY |
+	BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP |
+	BDI_CAP_EXEC_MAP,
+};
+
+#include <linux/syaoran.h>
+
+static struct inode *syaoran_get_inode(struct super_block *sb, int mode,
+				       dev_t dev)
+{
+	struct inode *inode = new_inode(sb);
+
+	if (inode) {
+		inode->i_mode = mode;
+		inode->i_uid = current->fsuid;
+		inode->i_gid = current->fsgid;
+		inode->i_blocks = 0;
+		inode->i_mapping->a_ops = &syaoran_aops;
+		inode->i_mapping->backing_dev_info = &syaoran_backing_dev_info;
+		inode->i_ctime = CURRENT_TIME;
+		inode->i_mtime = inode->i_ctime;
+		inode->i_atime = inode->i_mtime;
+		switch (mode & S_IFMT) {
+		default:
+			init_special_inode(inode, mode, dev);
+			if (S_ISBLK(mode))
+				inode->i_fop = &wrapped_def_blk_fops;
+			else if (S_ISCHR(mode))
+				inode->i_fop = &wrapped_def_chr_fops;
+			inode->i_op = &syaoran_file_inode_operations;
+			break;
+		case S_IFREG:
+			inode->i_op = &syaoran_file_inode_operations;
+			inode->i_fop = &syaoran_file_operations;
+			break;
+		case S_IFDIR:
+			inode->i_op = &syaoran_dir_inode_operations;
+			inode->i_fop = &simple_dir_operations;
+			/*
+			 * directory inodes start off with i_nlink == 2
+			 * (for "." entry)
+			 */
+			inode->i_nlink++;
+			break;
+		case S_IFLNK:
+			inode->i_op = &syaoran_symlink_inode_operations;
+			break;
+		}
+	}
+	return inode;
+}
+
+/*
+ * File creation. Allocate an inode, and we're done..
+ */
+/* SMP-safe */
+static int syaoran_mknod(struct inode *dir, struct dentry *dentry, int mode,
+			 dev_t dev)
+{
+	struct inode *inode;
+	int error = -ENOSPC;
+	if (syaoran_may_create_node(dentry, mode, dev) < 0)
+		return -EPERM;
+	inode = syaoran_get_inode(dir->i_sb, mode, dev);
+	if (inode) {
+		if (dir->i_mode & S_ISGID) {
+			inode->i_gid = dir->i_gid;
+			if (S_ISDIR(mode))
+				inode->i_mode |= S_ISGID;
+		}
+		d_instantiate(dentry, inode);
+		dget(dentry); /* Extra count - pin the dentry in core */
+		error = 0;
+	}
+	return error;
+}
+
+static int syaoran_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	int retval = syaoran_mknod(dir, dentry, mode | S_IFDIR, 0);
+	if (!retval)
+		dir->i_nlink++;
+	return retval;
+}
+
+static int syaoran_create(struct inode *dir, struct dentry *dentry, int mode,
+			  struct nameidata *nd)
+{
+	return syaoran_mknod(dir, dentry, mode | S_IFREG, 0);
+}
+
+static int syaoran_symlink(struct inode *dir, struct dentry *dentry,
+			   const char *symname)
+{
+	struct inode *inode;
+	int error = -ENOSPC;
+	if (syaoran_may_create_node(dentry, S_IFLNK, 0) < 0)
+		return -EPERM;
+	inode = syaoran_get_inode(dir->i_sb, S_IFLNK|S_IRWXUGO, 0);
+	if (inode) {
+		int l = strlen(symname)+1;
+		error = page_symlink(inode, symname, l);
+		if (!error) {
+			if (dir->i_mode & S_ISGID)
+				inode->i_gid = dir->i_gid;
+			d_instantiate(dentry, inode);
+			dget(dentry);
+		} else
+			iput(inode);
+	}
+	return error;
+}
+
+static int syaoran_link(struct dentry *old_dentry, struct inode *dir,
+			struct dentry *dentry)
+{
+	struct inode *inode = old_dentry->d_inode;
+	if (!inode || syaoran_may_create_node(dentry, inode->i_mode,
+					      inode->i_rdev) < 0)
+		return -EPERM;
+	return simple_link(old_dentry, dir, dentry);
+}
+
+static int syaoran_unlink(struct inode *dir, struct dentry *dentry)
+{
+	if (syaoran_may_modify_node(dentry, MAY_DELETE) < 0)
+		return -EPERM;
+	return simple_unlink(dir, dentry);
+}
+
+static int syaoran_rename(struct inode *old_dir, struct dentry *old_dentry,
+			  struct inode *new_dir, struct dentry *new_dentry)
+{
+	struct inode *inode = old_dentry->d_inode;
+	if (!inode || syaoran_may_modify_node(old_dentry, MAY_DELETE) < 0 ||
+	    syaoran_may_create_node(new_dentry, inode->i_mode,
+				    inode->i_rdev) < 0)
+		return -EPERM;
+	return simple_rename(old_dir, old_dentry, new_dir, new_dentry);
+}
+
+static int syaoran_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	if (syaoran_may_modify_node(dentry, MAY_DELETE) < 0)
+		return -EPERM;
+	return simple_rmdir(dir, dentry);
+}
+
+static int syaoran_setattr(struct dentry *dentry, struct iattr *attr)
+{
+	struct inode *inode = dentry->d_inode;
+	int error = inode_change_ok(inode, attr);
+	if (!error) {
+		unsigned int ia_valid = attr->ia_valid;
+		unsigned int flags = 0;
+		if (ia_valid & (ATTR_UID | ATTR_GID))
+			flags |= MAY_CHOWN;
+		if (ia_valid & ATTR_MODE)
+			flags |= MAY_CHMOD;
+		if (syaoran_may_modify_node(dentry, flags) < 0)
+			return -EPERM;
+		if (!error)
+			error = inode_setattr(inode, attr);
+	}
+	return error;
+}
+
+static int syaoran_set_page_dirty_no_writeback(struct page *page)
+{
+	if (!PageDirty(page))
+		SetPageDirty(page);
+	return 0;
+}
+static struct address_space_operations syaoran_aops = {
+	.readpage       = simple_readpage,
+	.write_begin    = simple_write_begin,
+	.write_end      = simple_write_end,
+	.set_page_dirty = syaoran_set_page_dirty_no_writeback,
+};
+
+static struct file_operations syaoran_file_operations = {
+	.aio_read    = generic_file_aio_read,
+	.read        = do_sync_read,
+	.aio_write   = generic_file_aio_write,
+	.write       = do_sync_write,
+	.mmap        = generic_file_mmap,
+	.fsync       = simple_sync_file,
+	.splice_read = generic_file_splice_read,
+	.llseek      = generic_file_llseek,
+};
+
+static struct inode_operations syaoran_file_inode_operations = {
+	.getattr    = simple_getattr,
+	.setattr    = syaoran_setattr,
+};
+
+static struct inode_operations syaoran_dir_inode_operations = {
+	.create     = syaoran_create,
+	.lookup     = simple_lookup,
+	.link       = syaoran_link,
+	.unlink     = syaoran_unlink,
+	.symlink    = syaoran_symlink,
+	.mkdir      = syaoran_mkdir,
+	.rmdir      = syaoran_rmdir,
+	.mknod      = syaoran_mknod,
+	.rename     = syaoran_rename,
+	.setattr    = syaoran_setattr,
+};
+
+static struct inode_operations syaoran_symlink_inode_operations = {
+	.readlink       = generic_readlink,
+	.follow_link    = page_follow_link_light,
+	.put_link       = page_put_link,
+	.setattr        = syaoran_setattr,
+};
+
+static struct super_operations syaoran_ops = {
+	.statfs     = simple_statfs,
+	.drop_inode = generic_delete_inode,
+	.put_super  = syaoran_put_super,
+};
+
+static int syaoran_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct inode *inode;
+	struct dentry *root;
+
+	sb->s_maxbytes = MAX_LFS_FILESIZE;
+	sb->s_blocksize = PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+	sb->s_magic = SYAORAN_MAGIC;
+	sb->s_op = &syaoran_ops;
+	sb->s_time_gran = 1;
+	{
+		int error = syaoran_initialize(sb, data);
+		if (error < 0)
+			return error;
+	}
+	inode = syaoran_get_inode(sb, S_IFDIR | 0755, 0);
+	if (!inode)
+		return -ENOMEM;
+
+	root = d_alloc_root(inode);
+	if (!root) {
+		iput(inode);
+		return -ENOMEM;
+	}
+	sb->s_root = root;
+	syaoran_make_initial_nodes(sb);
+	return 0;
+}
+
+static int syaoran_get_sb(struct file_system_type *fs_type,
+			  int flags, const char *dev_name, void *data,
+			  struct vfsmount *mnt)
+{
+	return get_sb_nodev(fs_type, flags, data, syaoran_fill_super, mnt);
+}
+
+static struct file_system_type syaoran_fs_type = {
+	.owner      = THIS_MODULE,
+	.name       = "syaoran",
+	.get_sb     = syaoran_get_sb,
+	.kill_sb    = kill_litter_super,
+};
+
+static int __init init_syaoran_fs(void)
+{
+	return register_filesystem(&syaoran_fs_type);
+}
+
+static void __exit exit_syaoran_fs(void)
+{
+	unregister_filesystem(&syaoran_fs_type);
+}
+
+module_init(init_syaoran_fs);
+module_exit(exit_syaoran_fs);
+
+MODULE_LICENSE("GPL");

-- 

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

* [TOMOYO #7 29/30] Kconfig and Makefile.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (27 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 28/30] Filesystem part of tamper-proof device filesystem Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 12:23 ` [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO Tetsuo Handa
  2008-04-24  5:28 ` [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Toshiharu Harada
  30 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

[-- Attachment #1: tomoyo-kconfig-and-makefile.patch --]
[-- Type: text/plain, Size: 5069 bytes --]

This file contains Kconfig and Makefile.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/Kconfig.ccs      |  112 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/Makefile-2.6.ccs |    5 ++
 2 files changed, 117 insertions(+)

--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/Kconfig.ccs
@@ -0,0 +1,112 @@
+config SAKURA
+	bool "SAKURA (Domain-Free Mandatory Access Control) support"
+	default y
+	help
+	  Say Y here to support the Domain-Free Mandatory Access Control.
+
+	  SAKURA stands for
+	  "Security Advancement Know-how Upon Read-only Approach".
+	  As the name shows, SAKURA was originally a methodology to make
+	  root fs read-only to avoid tampering the system files.
+	  But now, SAKURA is not only a methodology but also a kernel patch
+	  that improves the system security with less effort.
+
+	  SAKURA can restrict operations that affect systemwide.
+
+config TOMOYO
+	bool "TOMOYO (Domain-Based Mandatory Access Control) support"
+	default y
+	help
+	  Say Y here to support the Domain-Based Mandatory Access Control.
+
+	  TOMOYO stands for "Task Oriented Management Obviates Your Onus".
+	  TOMOYO is intended to provide the Domain-Based MAC
+	  utilizing task_struct.
+
+	  The word "domain" in TOMOYO is a class that a process
+	  (i.e. task_struct) belong to.
+	  The domain of a process changes whenever the process
+	  executes a program.
+	  This allows you to classify at the finest level.
+	  The access permission is granted to domains, not to processes.
+	  Policy is defined as "Which domain can access to which resource.".
+	  There is no concept of "user id" nor "role" like RBAC.
+
+	  The biggest feature of TOMOYO is that TOMOYO has "learning mode".
+	  The learning mode can automatically generate policy definition,
+	  and dramatically reduces the policy definition labors.
+
+	  TOMOYO is much simpler and easier than SELinux.
+
+	  TOMOYO is applicable to figuring out the system's behavior, for
+	  TOMOYO uses the canonicalized absolute pathnames and
+	  TreeView style domain transitions.
+
+	  You can make custom root fs with minimum files
+	  to run minimum applications with TOMOYO.
+
+config TOMOYO_MAX_ACCEPT_ENTRY
+	int "Default maximal count for learning mode"
+	default 2048
+	range 0 2147483647
+	depends on TOMOYO
+	help
+	  This is the default value for maximal ACL entries
+	  that are automatically appended into policy at "learning mode".
+	  Some programs access thousands of objects, so running
+	  such programs in "learning mode" dulls the system response
+	  and consumes much memory.
+	  This is the safeguard for such programs.
+
+config TOMOYO_MAX_GRANT_LOG
+	int "Default maximal count for grant log"
+	default 1024
+	range 0 2147483647
+	depends on TOMOYO
+	help
+	  This is the default value for maximal entries for
+	  access grant logs that the kernel can hold on memory.
+	  You can read the log via /proc/ccs/grant_log.
+	  If you don't need access grant logs,
+	  you may set this value to 0.
+
+config TOMOYO_MAX_REJECT_LOG
+	int "Default maximal count for reject log"
+	default 1024
+	range 0 2147483647
+	depends on TOMOYO
+	help
+	  This is the default value for maximal entries for
+	  access reject logs that the kernel can hold on memory.
+	  You can read the log via /proc/ccs/reject_log.
+	  If you don't need access reject logs,
+	  you may set this value to 0.
+
+config SYAORAN
+	tristate "SYAORAN (Tamper-Proof Device Filesystem) support"
+	default m
+	help
+	  Say Y or M here to support the Tamper-Proof Device Filesystem.
+
+	  SYAORAN stands for
+	  "Simple Yet All-important Object Realizing Abiding Nexus".
+	  SYAORAN is a filesystem for /dev with Mandatory Access Control.
+
+	  SAKURA can make root fs read-only, but the system can't work
+	  if /dev is read-only. Therefore you need to mount a writable
+	  filesystem (such as tmpfs) for /dev if root fs is read-only.
+
+	  But the writable /dev means that files on /dev might be tampered.
+	  For example, if /dev/null is deleted and re-created as a symbolic
+	  link to /dev/hda by an attacker, the contents of the IDE HDD
+	  will be destroyed at a blow.
+
+	  Also, TOMOYO controls file access by pathnames,
+	  not by security labels.
+	  Therefore /dev/null, for example, might be tampered
+	  if a process have write permission to /dev/null .
+
+	  SYAORAN can ensure /dev/null is a character device file
+	  with major=1 minor=3.
+
+	  You can use SAKURA to make /dev not unmountable.
--- /dev/null
+++ linux-2.6.25-rc8-mm1/fs/Makefile-2.6.ccs
@@ -0,0 +1,5 @@
+obj-y += tomoyo_domain.o
+obj-$(CONFIG_SAKURA) += ccs_common.o realpath.o sakura_mount.o sakura_chroot.o sakura_umount.o sakura_maymount.o sakura_pivot.o sakura_bind.o
+obj-$(CONFIG_TOMOYO) += ccs_common.o realpath.o tomoyo_cond.o tomoyo_file.o tomoyo_exec.o tomoyo_network.o tomoyo_signal.o tomoyo_capability.o tomoyo_audit.o tomoyo_env.o
+obj-$(CONFIG_SYAORAN) += syaoran.o
+syaoran-objs := syaoran_2.6.o

-- 

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

* [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (28 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 29/30] Kconfig and Makefile Tetsuo Handa
@ 2008-04-04 12:23 ` Tetsuo Handa
  2008-04-04 16:29   ` Daniel Walker
  2008-04-07 15:40   ` Paul Moore
  2008-04-24  5:28 ` [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Toshiharu Harada
  30 siblings, 2 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-04 12:23 UTC (permalink / raw)
  To: akpm, linux-kernel, linux-security-module
  Cc: Kentaro Takeda, Tetsuo Handa, Toshiharu Harada, linux-fsdevel,
	linux-netdev

[-- Attachment #1: tomoyo-hooks.patch --]
[-- Type: text/plain, Size: 49226 bytes --]

This file contains modifications against kernel source code
needed to use TOMOYO Linux 1.6.

Although LSM hooks are provided for performing access control,
TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.

Reason 1:

CONFIG_SECURITY=y adds security fields to various structures and
introduces hooks for allocating/freeing these security fields.
But TOMOYO Linux can hardly utilize them.

TOMOYO Linux requires modification against only "struct task_struct",
and adding two variables to "struct task_struct" is sufficient.

I think reserving LSM hooks for other security modules (e.g. SELinux, SMACK)
and making TOMOYO Linux coexist with them is better than
poorly utilizing LSM hooks and making TOMOYO Linux irreconcilable.
In fact, I'm building TOMOYO Linux kernel packages with SELinux enabled
and I'm experiencing no problems.

Reason 2:

TOMOYO Linux supports "delayed enforcing" mode that allows administrator
judge interactively when policy violation occurred.
To support that mode, all hooks but ccs_may_autobind() are called from
context that is permitted to sleep.
But some of LSM hooks are called from context that is not permitted to sleep.

Reason 3:

LSM hooks are called from VFS helper functions.
But VFS helper functions don't receive "struct vfsmount" that are needed for
TOMOYO Linux.
Current specification of LSM is suitable for label based access control,
but is not suitable for pathname based access control.
As far as I can see, adding "struct vfsmount" to VFS helper functions
to make LSM suitable for pathname based access control is unlikely acceptable.
TOMOYO Linux can achieve its functionality using
mnt_want_write()/mnt_drop_write()-like hooks.

Reason 4:

TOMOYO Linux is not only a tool for doing access control
but also a tool for analyzing a system's behavior.


Currently, this patch encloses inserted lines in
"/***** ... start. *****/" and "/***** end. *****/" comments so that
reviewers can easily find the range of modifications.
Of course, these comments will go away when ready to merge.

This patch makes two lines deletion by
  sed -e 's:search_binary_handler:search_binary_handler_with_transition:'
TOMOYO does domain transition when execve() is called.
Thus, distinguishing search_binary_handler() from do_execve() and
search_binary_handler() from other functions (e.g. load_script())
makes TOMOYO's domain transition handler simple.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>
Cc: linux-netdev <netdev@vger.kernel.org>
---
 Documentation/kernel-parameters.txt |   15 ++++
 arch/ia64/ia32/sys_ia32.c           |    7 ++
 arch/mips/kernel/ptrace32.c         |    7 ++
 arch/s390/kernel/ptrace.c           |    7 ++
 arch/sh/kernel/ptrace_64.c          |    7 ++
 arch/x86/kernel/ptrace.c            |    7 ++
 fs/Kconfig                          |    2 
 fs/Makefile                         |    2 
 fs/attr.c                           |   19 +++++
 fs/compat.c                         |    2 
 fs/compat_ioctl.c                   |    9 ++
 fs/exec.c                           |   20 +++++-
 fs/fcntl.c                          |    9 ++
 fs/ioctl.c                          |    7 ++
 fs/namei.c                          |  118 ++++++++++++++++++++++++++++++++++++
 fs/namespace.c                      |   49 ++++++++++++++
 fs/open.c                           |   27 ++++++++
 fs/proc/Makefile                    |    3 
 fs/proc/proc_misc.c                 |    5 +
 include/linux/init_task.h           |    4 +
 include/linux/sched.h               |    9 ++
 kernel/compat.c                     |    7 ++
 kernel/kexec.c                      |    7 ++
 kernel/kmod.c                       |    5 +
 kernel/module.c                     |   11 +++
 kernel/ptrace.c                     |   11 +++
 kernel/sched.c                      |    7 ++
 kernel/signal.c                     |   21 ++++++
 kernel/sys.c                        |   21 ++++++
 kernel/sysctl.c                     |   94 ++++++++++++++++++++++++++++
 kernel/time.c                       |   11 +++
 kernel/time/ntp.c                   |    7 ++
 net/core/datagram.c                 |   11 +++
 net/ipv4/inet_connection_sock.c     |    7 ++
 net/ipv4/inet_hashtables.c          |    7 ++
 net/ipv4/udp.c                      |   10 +++
 net/socket.c                        |   41 ++++++++++++
 net/unix/af_unix.c                  |   15 ++++
 38 files changed, 626 insertions(+), 2 deletions(-)

--- linux-2.6.25-rc8-mm1.orig/arch/ia64/ia32/sys_ia32.c
+++ linux-2.6.25-rc8-mm1/arch/ia64/ia32/sys_ia32.c
@@ -51,6 +51,9 @@
 #include <asm/types.h>
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #include "ia32priv.h"
 
@@ -1754,6 +1757,10 @@ sys32_ptrace (int request, pid_t pid, un
 	struct task_struct *child;
 	unsigned int value, tmp;
 	long i, ret;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	lock_kernel();
 	if (request == PTRACE_TRACEME) {
--- linux-2.6.25-rc8-mm1.orig/arch/mips/kernel/ptrace32.c
+++ linux-2.6.25-rc8-mm1/arch/mips/kernel/ptrace32.c
@@ -35,6 +35,9 @@
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <asm/bootinfo.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 int ptrace_getregs(struct task_struct *child, __s64 __user *data);
 int ptrace_setregs(struct task_struct *child, __s64 __user *data);
@@ -50,6 +53,10 @@ asmlinkage int sys32_ptrace(int request,
 {
 	struct task_struct *child;
 	int ret;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 #if 0
 	printk("ptrace(r=%d,pid=%d,addr=%08lx,data=%08lx)\n",
--- linux-2.6.25-rc8-mm1.orig/arch/s390/kernel/ptrace.c
+++ linux-2.6.25-rc8-mm1/arch/s390/kernel/ptrace.c
@@ -41,6 +41,9 @@
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #ifdef CONFIG_COMPAT
 #include "compat_ptrace.h"
@@ -698,6 +701,10 @@ sys_ptrace(long request, long pid, long 
 	struct task_struct *child;
 	int ret;
 
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	lock_kernel();
 	if (request == PTRACE_TRACEME) {
 		 ret = ptrace_traceme();
--- linux-2.6.25-rc8-mm1.orig/arch/sh/kernel/ptrace_64.c
+++ linux-2.6.25-rc8-mm1/arch/sh/kernel/ptrace_64.c
@@ -33,6 +33,9 @@
 #include <asm/system.h>
 #include <asm/processor.h>
 #include <asm/mmu_context.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /* This mask defines the bits of the SR which the user is not allowed to
    change, which are everything except S, Q, M, PR, SZ, FR. */
@@ -253,6 +256,10 @@ asmlinkage int sh64_ptrace(long request,
 {
 #define WPC_DBRMODE 0x0d104008
 	static int first_call = 1;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	lock_kernel();
 	if (first_call) {
--- linux-2.6.25-rc8-mm1.orig/arch/x86/kernel/ptrace.c
+++ linux-2.6.25-rc8-mm1/arch/x86/kernel/ptrace.c
@@ -32,6 +32,9 @@
 #include <asm/prctl.h>
 #include <asm/proto.h>
 #include <asm/ds.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #include "tls.h"
 
@@ -1240,6 +1243,10 @@ asmlinkage long sys32_ptrace(long reques
 	void __user *datap = compat_ptr(data);
 	int ret;
 	__u32 val;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	switch (request) {
 	case PTRACE_TRACEME:
--- linux-2.6.25-rc8-mm1.orig/fs/Kconfig
+++ linux-2.6.25-rc8-mm1/fs/Kconfig
@@ -2196,4 +2196,6 @@ endif
 source "fs/nls/Kconfig"
 source "fs/dlm/Kconfig"
 
+source "fs/Kconfig.ccs"
+
 endmenu
--- linux-2.6.25-rc8-mm1.orig/fs/Makefile
+++ linux-2.6.25-rc8-mm1/fs/Makefile
@@ -121,3 +121,5 @@ obj-$(CONFIG_DEBUG_FS)		+= debugfs/
 obj-$(CONFIG_OCFS2_FS)		+= ocfs2/
 obj-$(CONFIG_GFS2_FS)           += gfs2/
 obj-$(CONFIG_UNION_FS)		+= unionfs/
+
+include $(srctree)/fs/Makefile-2.6.ccs
--- linux-2.6.25-rc8-mm1.orig/fs/attr.c
+++ linux-2.6.25-rc8-mm1/fs/attr.c
@@ -14,6 +14,9 @@
 #include <linux/fcntl.h>
 #include <linux/quotaops.h>
 #include <linux/security.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /* Taken over from the old code... */
 
@@ -159,12 +162,28 @@ int notify_change(struct dentry * dentry
 
 	if (inode->i_op && inode->i_op->setattr) {
 		error = security_inode_setattr(dentry, attr);
+		/***** TOMOYO Linux start. *****/
+		if (!error && (ia_valid & ATTR_MODE) &&
+		    !ccs_capable(TOMOYO_SYS_CHMOD))
+			error = -EPERM;
+		if (!error && (ia_valid & (ATTR_UID | ATTR_GID)) &&
+		    !ccs_capable(TOMOYO_SYS_CHOWN))
+			error = -EPERM;
+		/***** TOMOYO Linux end. *****/
 		if (!error)
 			error = inode->i_op->setattr(dentry, attr);
 	} else {
 		error = inode_change_ok(inode, attr);
 		if (!error)
 			error = security_inode_setattr(dentry, attr);
+		/***** TOMOYO Linux start. *****/
+		if (!error && (ia_valid & ATTR_MODE) &&
+		    !ccs_capable(TOMOYO_SYS_CHMOD))
+			error = -EPERM;
+		if (!error && (ia_valid & (ATTR_UID | ATTR_GID)) &&
+		    !ccs_capable(TOMOYO_SYS_CHOWN))
+			error = -EPERM;
+		/***** TOMOYO Linux end. *****/
 		if (!error) {
 			if ((ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
 			    (ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid))
--- linux-2.6.25-rc8-mm1.orig/fs/compat.c
+++ linux-2.6.25-rc8-mm1/fs/compat.c
@@ -1399,7 +1399,7 @@ int compat_do_execve(char * filename,
 	if (retval < 0)
 		goto out;
 
-	retval = search_binary_handler(bprm, regs);
+	retval = search_binary_handler_with_transition(bprm, regs);
 	if (retval >= 0) {
 		/* execve success */
 		security_bprm_free(bprm);
--- linux-2.6.25-rc8-mm1.orig/fs/compat_ioctl.c
+++ linux-2.6.25-rc8-mm1/fs/compat_ioctl.c
@@ -113,6 +113,9 @@
 #ifdef CONFIG_SPARC
 #include <asm/fbio.h>
 #endif
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 static int do_ioctl32_pointer(unsigned int fd, unsigned int cmd,
 			      unsigned long arg, struct file *f)
@@ -2910,6 +2913,12 @@ asmlinkage long compat_sys_ioctl(unsigne
 		/*FALL THROUGH*/
 
 	default:
+		/***** TOMOYO Linux start. *****/
+		if (!ccs_capable(TOMOYO_SYS_IOCTL)) {
+			error = -EPERM;
+			goto out_fput;
+		}
+		/***** TOMOYO Linux end. *****/
 		if (filp->f_op && filp->f_op->compat_ioctl) {
 			error = filp->f_op->compat_ioctl(filp, cmd, arg);
 			if (error != -ENOIOCTLCMD)
--- linux-2.6.25-rc8-mm1.orig/fs/exec.c
+++ linux-2.6.25-rc8-mm1/fs/exec.c
@@ -60,6 +60,10 @@
 #include <linux/kmod.h>
 #endif
 
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
+
 int core_uses_pid;
 char core_pattern[CORENAME_MAX_SIZE] = "core";
 int suid_dumpable = 0;
@@ -118,6 +122,12 @@ asmlinkage long sys_uselib(const char __
 	error = vfs_permission(&nd, MAY_READ | MAY_EXEC);
 	if (error)
 		goto exit;
+	/***** TOMOYO Linux start. *****/
+	/* 01 means "read". */
+	error = ccs_check_open_permission(nd.path.dentry, nd.path.mnt, 01);
+	if (error)
+		goto exit;
+	/***** TOMOYO Linux end. *****/
 
 	file = nameidata_to_filp(&nd, O_RDONLY|O_LARGEFILE);
 	error = PTR_ERR(file);
@@ -664,6 +674,14 @@ struct file *open_exec(const char *name)
 		file = ERR_PTR(-EACCES);
 		if (S_ISREG(inode->i_mode)) {
 			int err = vfs_permission(&nd, MAY_EXEC);
+			/***** TOMOYO Linux start. *****/
+			if (!err && (current->tomoyo_flags &
+				     TOMOYO_CHECK_READ_FOR_OPEN_EXEC))
+				/* 01 means "read". */
+				err = ccs_check_open_permission(nd.path.dentry,
+								nd.path.mnt,
+								01);
+			/***** TOMOYO Linux end. *****/
 			file = ERR_PTR(err);
 			if (!err) {
 				file = nameidata_to_filp(&nd,
@@ -1325,7 +1343,7 @@ int do_execve(char * filename,
 	if (retval < 0)
 		goto out;
 
-	retval = search_binary_handler(bprm,regs);
+	retval = search_binary_handler_with_transition(bprm, regs);
 	if (retval >= 0) {
 		/* execve success */
 		free_arg_pages(bprm);
--- linux-2.6.25-rc8-mm1.orig/fs/fcntl.c
+++ linux-2.6.25-rc8-mm1/fs/fcntl.c
@@ -23,6 +23,9 @@
 #include <asm/poll.h>
 #include <asm/siginfo.h>
 #include <asm/uaccess.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 void set_close_on_exec(unsigned int fd, int flag)
 {
@@ -217,6 +220,12 @@ static int setfl(int fd, struct file * f
 	if (((arg ^ filp->f_flags) & O_APPEND) && IS_APPEND(inode))
 		return -EPERM;
 
+	/***** TOMOYO Linux start. *****/
+	if (((arg ^ filp->f_flags) & O_APPEND) &&
+	    ccs_check_rewrite_permission(filp))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
+
 	/* O_NOATIME can only be set by the owner or superuser */
 	if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME))
 		if (!is_owner_or_cap(inode))
--- linux-2.6.25-rc8-mm1.orig/fs/ioctl.c
+++ linux-2.6.25-rc8-mm1/fs/ioctl.c
@@ -15,6 +15,9 @@
 #include <linux/uaccess.h>
 
 #include <asm/ioctls.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /**
  * vfs_ioctl - call filesystem specific ioctl methods
@@ -35,6 +38,10 @@ static long vfs_ioctl(struct file *filp,
 
 	if (!filp->f_op)
 		goto out;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_IOCTL))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	if (filp->f_op->unlocked_ioctl) {
 		error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
--- linux-2.6.25-rc8-mm1.orig/fs/namei.c
+++ linux-2.6.25-rc8-mm1/fs/namei.c
@@ -36,6 +36,10 @@
 
 #define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
 
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
+
 /* [Feb-1997 T. Schoebel-Theuer]
  * Fundamental changes in the pathname lookup mechanisms (namei)
  * were necessary because of omirr.  The reason is that omirr needs
@@ -1595,6 +1599,14 @@ int vfs_create(struct inode *dir, struct
 	error = security_inode_create(dir, dentry, mode);
 	if (error)
 		return error;
+	/***** TOMOYO Linux start. *****/
+	if (nd) {
+		error = ccs_check_1path_perm(TYPE_CREATE_ACL, dentry,
+					     nd->path.mnt);
+		if (error < 0)
+			return error;
+	}
+	/***** TOMOYO Linux end. *****/
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error)
@@ -1649,6 +1661,13 @@ int may_open(struct nameidata *nd, int a
 		if (!is_owner_or_cap(inode))
 			return -EPERM;
 
+	/***** TOMOYO Linux start. *****/
+	/* includes O_APPEND and O_TRUNC checks */
+	error = ccs_check_open_permission(dentry, nd->path.mnt, flag);
+	if (error)
+		return error;
+	/***** TOMOYO Linux end. *****/
+
 	/*
 	 * Ensure there are no outstanding leases on the file.
 	 */
@@ -1705,6 +1724,9 @@ static int __open_namei_create(struct na
 	return may_open(nd, 0, flag & ~O_TRUNC);
 }
 
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo_vfs.h>
+/***** TOMOYO Linux end. *****/
 /*
  * Note that while the flag value (low two bits) for sys_open means:
  *	00 - read-only
@@ -2076,6 +2098,16 @@ asmlinkage long sys_mknodat(int dfd, con
 
 	if (S_ISDIR(mode))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (S_ISCHR(mode) && !ccs_capable(TOMOYO_CREATE_CHAR_DEV))
+		return -EPERM;
+	if (S_ISBLK(mode) && !ccs_capable(TOMOYO_CREATE_BLOCK_DEV))
+		return -EPERM;
+	if (S_ISFIFO(mode) && !ccs_capable(TOMOYO_CREATE_FIFO))
+		return -EPERM;
+	if (S_ISSOCK(mode) && !ccs_capable(TOMOYO_CREATE_UNIX_SOCKET))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	tmp = getname(filename);
 	if (IS_ERR(tmp))
 		return PTR_ERR(tmp);
@@ -2101,10 +2133,34 @@ asmlinkage long sys_mknodat(int dfd, con
 			error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);
 			break;
 		case S_IFCHR: case S_IFBLK:
+			/***** TOMOYO Linux start. *****/
+			error = pre_vfs_mknod(nd.path.dentry->d_inode, dentry,
+					      mode);
+			if (error)
+				break;
+			error = ccs_check_1path_perm(S_ISCHR(mode) ?
+						     TYPE_MKCHAR_ACL :
+						     TYPE_MKBLOCK_ACL,
+						     dentry, nd.path.mnt);
+			if (error)
+				break;
+			/***** TOMOYO Linux end. *****/
 			error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,
 					new_decode_dev(dev));
 			break;
 		case S_IFIFO: case S_IFSOCK:
+			/***** TOMOYO Linux start. *****/
+			error = pre_vfs_mknod(nd.path.dentry->d_inode, dentry,
+					      mode);
+			if (error)
+				break;
+			error = ccs_check_1path_perm(S_ISFIFO(mode) ?
+						     TYPE_MKFIFO_ACL :
+						     TYPE_MKSOCK_ACL,
+						     dentry, nd.path.mnt);
+			if (error)
+				break;
+			/***** TOMOYO Linux end. *****/
 			error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);
 			break;
 	}
@@ -2172,6 +2228,13 @@ asmlinkage long sys_mkdirat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	/***** TOMOYO Linux start. *****/
+	error = pre_vfs_mkdir(nd.path.dentry->d_inode, dentry);
+	if (!error)
+		error = ccs_check_1path_perm(TYPE_MKDIR_ACL, dentry,
+					     nd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2284,6 +2347,13 @@ static long do_rmdir(int dfd, const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto exit3;
+	/***** TOMOYO Linux start. *****/
+	error = pre_vfs_rmdir(nd.path.dentry->d_inode, dentry);
+	if (!error)
+		error = ccs_check_1path_perm(TYPE_RMDIR_ACL, dentry,
+					     nd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
 	mnt_drop_write(nd.path.mnt);
 exit3:
@@ -2346,6 +2416,10 @@ static long do_unlinkat(int dfd, const c
 	struct dentry *dentry;
 	struct nameidata nd;
 	struct inode *inode = NULL;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_UNLINK))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	name = getname(pathname);
 	if(IS_ERR(name))
@@ -2370,6 +2444,13 @@ static long do_unlinkat(int dfd, const c
 		error = mnt_want_write(nd.path.mnt);
 		if (error)
 			goto exit2;
+		/***** TOMOYO Linux start. *****/
+		error = pre_vfs_unlink(nd.path.dentry->d_inode, dentry);
+		if (!error)
+			error = ccs_check_1path_perm(TYPE_UNLINK_ACL, dentry,
+						     nd.path.mnt);
+		if (!error)
+		/***** TOMOYO Linux end. *****/
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
 		mnt_drop_write(nd.path.mnt);
 	exit2:
@@ -2435,6 +2516,10 @@ asmlinkage long sys_symlinkat(const char
 	char * to;
 	struct dentry *dentry;
 	struct nameidata nd;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SYMLINK))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	from = getname(oldname);
 	if(IS_ERR(from))
@@ -2455,6 +2540,13 @@ asmlinkage long sys_symlinkat(const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	/***** TOMOYO Linux start. *****/
+	error = pre_vfs_symlink(nd.path.dentry->d_inode, dentry);
+	if (!error)
+		error = ccs_check_1path_perm(TYPE_SYMLINK_ACL, dentry,
+					     nd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from, S_IALLUGO);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2529,6 +2621,10 @@ asmlinkage long sys_linkat(int olddfd, c
 	struct nameidata nd, old_nd;
 	int error;
 	char * to;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_LINK))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	if ((flags & ~AT_SYMLINK_FOLLOW) != 0)
 		return -EINVAL;
@@ -2555,6 +2651,15 @@ asmlinkage long sys_linkat(int olddfd, c
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	/***** TOMOYO Linux start. *****/
+	error = pre_vfs_link(old_nd.path.dentry, nd.path.dentry->d_inode,
+			     new_dentry);
+	if (!error)
+		error = ccs_check_2path_perm(TYPE_LINK_ACL, old_nd.path.dentry,
+					     old_nd.path.mnt, new_dentry,
+					     nd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = vfs_link(old_nd.path.dentry, nd.path.dentry->d_inode, new_dentry);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2786,6 +2891,15 @@ static int do_rename(int olddfd, const c
 	error = mnt_want_write(oldnd.path.mnt);
 	if (error)
 		goto exit5;
+	/***** TOMOYO Linux start. *****/
+	error = pre_vfs_rename(old_dir->d_inode, old_dentry, new_dir->d_inode,
+			       new_dentry);
+	if (!error)
+		error = ccs_check_2path_perm(TYPE_RENAME_ACL, old_dentry,
+					     oldnd.path.mnt, new_dentry,
+					     newnd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = vfs_rename(old_dir->d_inode, old_dentry,
 				   new_dir->d_inode, new_dentry);
 	mnt_drop_write(oldnd.path.mnt);
@@ -2809,6 +2923,10 @@ asmlinkage long sys_renameat(int olddfd,
 	int error;
 	char * from;
 	char * to;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_RENAME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	from = getname(oldname);
 	if(IS_ERR(from))
--- linux-2.6.25-rc8-mm1.orig/fs/namespace.c
+++ linux-2.6.25-rc8-mm1/fs/namespace.c
@@ -31,6 +31,12 @@
 #include <asm/unistd.h>
 #include "pnode.h"
 #include "internal.h"
+/***** SAKURA Linux start. *****/
+#include <linux/sakura.h>
+/***** SAKURA Linux end. *****/
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #define HASH_SHIFT ilog2(PAGE_SIZE / sizeof(struct list_head))
 #define HASH_SIZE (1UL << HASH_SHIFT)
@@ -1029,6 +1035,11 @@ static int do_umount(struct vfsmount *mn
 	if (retval)
 		return retval;
 
+	/***** SAKURA Linux start. *****/
+	if (ccs_may_umount(mnt) < 0)
+		return -EPERM;
+	/***** SAKURA Linux end. *****/
+
 	/*
 	 * Allow userspace to request a mountpoint be expired rather than
 	 * unmounting unconditionally. Unmount only happens if:
@@ -1119,6 +1130,10 @@ asmlinkage long sys_umount(char __user *
 {
 	struct nameidata nd;
 	int retval;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_UMOUNT))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	retval = __user_walk(name, LOOKUP_FOLLOW, &nd);
 	if (retval)
@@ -1466,6 +1481,11 @@ static noinline int do_loopback(struct n
 	err = -EINVAL;
 	if (IS_MNT_UNBINDABLE(old_nd.path.mnt))
 		goto out;
+	/***** SAKURA Linux start. *****/
+	err = -EPERM;
+	if (ccs_may_mount(nd) < 0)
+		goto out;
+	/***** SAKURA Linux end. *****/
 
 	if (!check_mnt(nd->path.mnt) || !check_mnt(old_nd.path.mnt))
 		goto out;
@@ -1580,6 +1600,11 @@ static noinline int do_move_mount(struct
 	if (!check_mnt(nd->path.mnt) || !check_mnt(old_nd.path.mnt))
 		goto out;
 
+	/***** SAKURA Linux start. *****/
+	err = -EPERM;
+	if (ccs_may_umount(old_nd.path.mnt) < 0 || ccs_may_mount(nd) < 0)
+		goto out;
+	/***** SAKURA Linux end. *****/
 	err = -ENOENT;
 	mutex_lock(&nd->path.dentry->d_inode->i_mutex);
 	if (IS_DEADDIR(nd->path.dentry->d_inode))
@@ -1684,6 +1709,11 @@ int do_add_mount(struct vfsmount *newmnt
 	err = -EINVAL;
 	if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
 		goto unlock;
+	/***** SAKURA Linux start. *****/
+	err = -EPERM;
+	if (ccs_may_mount(nd) < 0)
+		goto unlock;
+	/***** SAKURA Linux end. *****/
 
 	newmnt->mnt_flags = mnt_flags;
 	if ((err = graft_tree(newmnt, &nd->path)))
@@ -1907,6 +1937,17 @@ long do_mount(char *dev_name, char *dir_
 	if (data_page)
 		((char *)data_page)[PAGE_SIZE - 1] = 0;
 
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_MOUNT))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
+	/***** SAKURA Linux start. *****/
+	retval = ccs_check_mount_permission(dev_name, dir_name, type_page,
+					    &flags);
+	if (retval < 0)
+		return retval;
+	/***** SAKURA Linux end. *****/
+
 	/* Separate the per-mountpoint flags */
 	if (flags & MS_NOSUID)
 		mnt_flags |= MNT_NOSUID;
@@ -2178,6 +2219,10 @@ asmlinkage long sys_pivot_root(const cha
 
 	if (!capable(CAP_SYS_ADMIN))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PIVOT_ROOT))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	error = __user_walk(new_root, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
 			    &new_nd);
@@ -2192,6 +2237,10 @@ asmlinkage long sys_pivot_root(const cha
 		goto out1;
 
 	error = security_sb_pivotroot(&old_nd.path, &new_nd.path);
+	/***** SAKURA Linux start. *****/
+	if (!error)
+		error = ccs_check_pivot_root_permission(&old_nd, &new_nd);
+	/***** SAKURA Linux end. *****/
 	if (error) {
 		path_put(&old_nd.path);
 		goto out1;
--- linux-2.6.25-rc8-mm1.orig/fs/open.c
+++ linux-2.6.25-rc8-mm1/fs/open.c
@@ -27,6 +27,12 @@
 #include <linux/rcupdate.h>
 #include <linux/audit.h>
 #include <linux/falloc.h>
+/***** SAKURA Linux start. *****/
+#include <linux/sakura.h>
+/***** SAKURA Linux end. *****/
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 int vfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
@@ -268,6 +274,11 @@ static long do_sys_truncate(const char _
 	if (error)
 		goto put_write_and_out;
 
+	/***** TOMOYO Linux start. *****/
+	error = ccs_check_1path_perm(TYPE_TRUNCATE_ACL, nd.path.dentry,
+				     nd.path.mnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = locks_verify_truncate(inode, NULL, length);
 	if (!error) {
 		DQUOT_INIT(inode);
@@ -324,6 +335,10 @@ static long do_sys_ftruncate(unsigned in
 	if (IS_APPEND(inode))
 		goto out_putf;
 
+	/***** TOMOYO Linux start. *****/
+	error = ccs_check_1path_perm(TYPE_TRUNCATE_ACL, dentry, file->f_vfsmnt);
+	if (!error)
+	/***** TOMOYO Linux end. *****/
 	error = locks_verify_truncate(inode, file, length);
 	if (!error)
 		error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
@@ -551,6 +566,14 @@ asmlinkage long sys_chroot(const char __
 	error = -EPERM;
 	if (!capable(CAP_SYS_CHROOT))
 		goto dput_and_out;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_CHROOT))
+		goto dput_and_out;
+	/***** TOMOYO Linux end. *****/
+	/***** SAKURA Linux start. *****/
+	if (ccs_check_chroot_permission(&nd))
+		goto dput_and_out;
+	/***** SAKURA Linux end. *****/
 
 	set_fs_root(current->fs, &nd.path);
 	set_fs_altroot();
@@ -1206,6 +1229,10 @@ EXPORT_SYMBOL(sys_close);
  */
 asmlinkage long sys_vhangup(void)
 {
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_VHANGUP))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	if (capable(CAP_SYS_TTY_CONFIG)) {
 		/* XXX: this needs locking */
 		tty_vhangup(current->signal->tty);
--- linux-2.6.25-rc8-mm1.orig/fs/proc/Makefile
+++ linux-2.6.25-rc8-mm1/fs/proc/Makefile
@@ -16,3 +16,6 @@ proc-$(CONFIG_PROC_KCORE)	+= kcore.o
 proc-$(CONFIG_PROC_VMCORE)	+= vmcore.o
 proc-$(CONFIG_PROC_DEVICETREE)	+= proc_devtree.o
 proc-$(CONFIG_PRINTK)	+= kmsg.o
+
+proc-$(CONFIG_SAKURA) += ccs_proc.o
+proc-$(CONFIG_TOMOYO) += ccs_proc.o
--- linux-2.6.25-rc8-mm1.orig/fs/proc/proc_misc.c
+++ linux-2.6.25-rc8-mm1/fs/proc/proc_misc.c
@@ -1049,4 +1049,9 @@ void __init proc_misc_init(void)
 		}
 	}
 #endif
+	/***** CCS start. *****/
+#if defined(CONFIG_SAKURA) || defined(CONFIG_TOMOYO)
+	printk(KERN_INFO "Hook version: 2.6.25-rc8-mm1 2008/04/02\n");
+#endif
+	/***** CCS end. *****/
 }
--- linux-2.6.25-rc8-mm1.orig/include/linux/init_task.h
+++ linux-2.6.25-rc8-mm1/include/linux/init_task.h
@@ -197,6 +197,10 @@ extern struct group_info init_groups;
 	INIT_IDS							\
 	INIT_TRACE_IRQFLAGS						\
 	INIT_LOCKDEP							\
+	/***** TOMOYO Linux start. *****/        \
+	.domain_info = &KERNEL_DOMAIN,           \
+	.tomoyo_flags = 0,                       \
+	/***** TOMOYO Linux end. *****/          \
 }
 
 
--- linux-2.6.25-rc8-mm1.orig/include/linux/sched.h
+++ linux-2.6.25-rc8-mm1/include/linux/sched.h
@@ -29,6 +29,11 @@
 #define CLONE_NEWNET		0x40000000	/* New network namespace */
 #define CLONE_IO		0x80000000	/* Clone io context */
 
+/***** TOMOYO Linux start. *****/
+struct domain_info;
+extern struct domain_info KERNEL_DOMAIN;
+/***** TOMOYO Linux end. *****/
+
 /*
  * Scheduling policies
  */
@@ -1278,6 +1283,10 @@ struct task_struct {
 	int latency_record_count;
 	struct latency_record latency_record[LT_SAVECOUNT];
 #endif
+	/***** TOMOYO Linux start. *****/
+	struct domain_info *domain_info;
+	u32 tomoyo_flags;
+	/***** TOMOYO Linux end. *****/
 };
 
 /*
--- linux-2.6.25-rc8-mm1.orig/kernel/compat.c
+++ linux-2.6.25-rc8-mm1/kernel/compat.c
@@ -25,6 +25,9 @@
 #include <linux/posix-timers.h>
 
 #include <asm/uaccess.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 int get_compat_timespec(struct timespec *ts, const struct compat_timespec __user *cts)
 {
@@ -868,6 +871,10 @@ asmlinkage long compat_sys_stime(compat_
 	err = security_settime(&tv, NULL);
 	if (err)
 		return err;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SETTIME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	do_settimeofday(&tv);
 	return 0;
--- linux-2.6.25-rc8-mm1.orig/kernel/kexec.c
+++ linux-2.6.25-rc8-mm1/kernel/kexec.c
@@ -31,6 +31,9 @@
 #include <asm/system.h>
 #include <asm/semaphore.h>
 #include <asm/sections.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /* Per cpu memory for storing cpu states in case of system crash. */
 note_buf_t* crash_notes;
@@ -933,6 +936,10 @@ asmlinkage long sys_kexec_load(unsigned 
 	/* We only trust the superuser with rebooting the system. */
 	if (!capable(CAP_SYS_BOOT))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_KEXEC_LOAD))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	/*
 	 * Verify we have a legal set of flags
--- linux-2.6.25-rc8-mm1.orig/kernel/kmod.c
+++ linux-2.6.25-rc8-mm1/kernel/kmod.c
@@ -173,6 +173,11 @@ static int ____call_usermodehelper(void 
 	 */
 	set_user_nice(current, 0);
 
+	/***** TOMOYO Linux start. *****/
+	current->domain_info = &KERNEL_DOMAIN;
+	current->tomoyo_flags = 0;
+	/***** TOMOYO Linux end. *****/
+
 	retval = kernel_execve(sub_info->path, sub_info->argv, sub_info->envp);
 
 	/* Exec failed? */
--- linux-2.6.25-rc8-mm1.orig/kernel/module.c
+++ linux-2.6.25-rc8-mm1/kernel/module.c
@@ -47,6 +47,9 @@
 #include <asm/cacheflush.h>
 #include <linux/license.h>
 #include <asm/sections.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #if 0
 #define DEBUGP printk
@@ -686,6 +689,10 @@ sys_delete_module(const char __user *nam
 
 	if (!capable(CAP_SYS_MODULE))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_USE_KERNEL_MODULE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
 		return -EFAULT;
@@ -2157,6 +2164,10 @@ sys_init_module(void __user *umod,
 	/* Must have permission */
 	if (!capable(CAP_SYS_MODULE))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_USE_KERNEL_MODULE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	/* Only one module load at a time, please */
 	if (mutex_lock_interruptible(&module_mutex) != 0)
--- linux-2.6.25-rc8-mm1.orig/kernel/ptrace.c
+++ linux-2.6.25-rc8-mm1/kernel/ptrace.c
@@ -24,6 +24,9 @@
 
 #include <asm/pgtable.h>
 #include <asm/uaccess.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /*
  * ptrace a task: make the debugger its new parent and
@@ -539,6 +542,10 @@ asmlinkage long sys_ptrace(long request,
 	/*
 	 * This lock_kernel fixes a subtle race with suid exec
 	 */
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	lock_kernel();
 	if (request == PTRACE_TRACEME) {
 		ret = ptrace_traceme();
@@ -646,6 +653,10 @@ asmlinkage long compat_sys_ptrace(compat
 	/*
 	 * This lock_kernel fixes a subtle race with suid exec
 	 */
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_PTRACE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	lock_kernel();
 	if (request == PTRACE_TRACEME) {
 		ret = ptrace_traceme();
--- linux-2.6.25-rc8-mm1.orig/kernel/sched.c
+++ linux-2.6.25-rc8-mm1/kernel/sched.c
@@ -70,6 +70,9 @@
 
 #include <asm/tlb.h>
 #include <asm/irq_regs.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /*
  * Scheduler clock - returns current time in nanosec units.
@@ -4509,6 +4512,10 @@ int can_nice(const struct task_struct *p
 asmlinkage long sys_nice(int increment)
 {
 	long nice, retval;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_NICE))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	/*
 	 * Setpriority might change our priority at the same moment.
--- linux-2.6.25-rc8-mm1.orig/kernel/signal.c
+++ linux-2.6.25-rc8-mm1/kernel/signal.c
@@ -32,6 +32,9 @@
 #include <asm/unistd.h>
 #include <asm/siginfo.h>
 #include "audit.h"	/* audit_signal_info() */
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /*
  * SLAB caches for signal bits.
@@ -2147,6 +2150,12 @@ asmlinkage long
 sys_kill(int pid, int sig)
 {
 	struct siginfo info;
+	/***** TOMOYO Linux start. *****/
+	if (sig && !ccs_capable(TOMOYO_SYS_KILL))
+		return -EPERM;
+	if (sig && ccs_check_signal_acl(sig, pid) < 0)
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	info.si_signo = sig;
 	info.si_errno = 0;
@@ -2208,6 +2217,12 @@ asmlinkage long sys_tgkill(int tgid, int
 	/* This is only valid for single tasks */
 	if (pid <= 0 || tgid <= 0)
 		return -EINVAL;
+	/***** TOMOYO Linux start. *****/
+	if (sig && !ccs_capable(TOMOYO_SYS_KILL))
+		return -EPERM;
+	if (sig && ccs_check_signal_acl(sig, pid) < 0)
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	return do_tkill(tgid, pid, sig);
 }
@@ -2221,6 +2236,12 @@ sys_tkill(int pid, int sig)
 	/* This is only valid for single tasks */
 	if (pid <= 0)
 		return -EINVAL;
+	/***** TOMOYO Linux start. *****/
+	if (sig && !ccs_capable(TOMOYO_SYS_KILL))
+		return -EPERM;
+	if (sig && ccs_check_signal_acl(sig, pid) < 0)
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	return do_tkill(0, pid, sig);
 }
--- linux-2.6.25-rc8-mm1.orig/kernel/sys.c
+++ linux-2.6.25-rc8-mm1/kernel/sys.c
@@ -42,6 +42,9 @@
 #include <asm/uaccess.h>
 #include <asm/io.h>
 #include <asm/unistd.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #ifndef SET_UNALIGN_CTL
 # define SET_UNALIGN_CTL(a,b)	(-EINVAL)
@@ -140,6 +143,12 @@ asmlinkage long sys_setpriority(int whic
 
 	if (which > PRIO_USER || which < PRIO_PROCESS)
 		goto out;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_NICE)) {
+		error = -EPERM;
+		goto out;
+	}
+	/***** TOMOYO Linux end. *****/
 
 	/* normalize: avoid signed division (rounding problems) */
 	error = -ESRCH;
@@ -376,6 +385,10 @@ asmlinkage long sys_reboot(int magic1, i
 			magic2 != LINUX_REBOOT_MAGIC2B &&
 	                magic2 != LINUX_REBOOT_MAGIC2C))
 		return -EINVAL;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_REBOOT))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	/* Instead of trying to make the power_off code look like
 	 * halt when pm_power_off is not set do it the easy way.
@@ -1359,6 +1372,10 @@ asmlinkage long sys_sethostname(char __u
 		return -EPERM;
 	if (len < 0 || len > __NEW_UTS_LEN)
 		return -EINVAL;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SETHOSTNAME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 	down_write(&uts_sem);
 	errno = -EFAULT;
 	if (!copy_from_user(tmp, name, len)) {
@@ -1404,6 +1421,10 @@ asmlinkage long sys_setdomainname(char _
 		return -EPERM;
 	if (len < 0 || len > __NEW_UTS_LEN)
 		return -EINVAL;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SETHOSTNAME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	down_write(&uts_sem);
 	errno = -EFAULT;
--- linux-2.6.25-rc8-mm1.orig/kernel/sysctl.c
+++ linux-2.6.25-rc8-mm1/kernel/sysctl.c
@@ -49,6 +49,9 @@
 
 #include <asm/uaccess.h>
 #include <asm/processor.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #ifdef CONFIG_X86
 #include <asm/nmi.h>
@@ -1503,6 +1506,92 @@ repeat:
 	return -ENOTDIR;
 }
 
+/***** TOMOYO Linux start. *****/
+static int try_parse_table(int __user *name, int nlen, void __user *oldval,
+			   void __user *newval, ctl_table *table)
+{
+	int n;
+	int error = -ENOMEM;
+	int op = 0;
+	char *buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (oldval)
+		op |= 004;
+	if (newval)
+		op |= 002;
+	if (!op) { /* Neither read nor write */
+		error = 0;
+		goto out;
+	}
+	if (!buffer)
+		goto out;
+	memset(buffer, 0, PAGE_SIZE);
+	snprintf(buffer, PAGE_SIZE - 1, "/proc/sys");
+ repeat:
+	if (!nlen) {
+		error = -ENOTDIR;
+		goto out;
+	}
+	if (get_user(n, name)) {
+		error = -EFAULT;
+		goto out;
+	}
+	for ( ; table->ctl_name || table->procname; table++) {
+		if (n == table->ctl_name && n) {
+			int pos = strlen(buffer);
+			const char *cp = table->procname;
+			error = -ENOMEM;
+			if (cp) {
+				if (pos + 1 >= PAGE_SIZE - 1)
+					goto out;
+				buffer[pos++] = '/';
+				while (*cp) {
+					const unsigned char c =
+						*(const unsigned char *) cp;
+					if (c == '\\') {
+						if (pos + 2 >= PAGE_SIZE - 1)
+							goto out;
+						buffer[pos++] = '\\';
+						buffer[pos++] = '\\';
+					} else if (c > ' ' && c < 127) {
+						if (pos + 1 >= PAGE_SIZE - 1)
+							goto out;
+						buffer[pos++] = c;
+					} else {
+						if (pos + 4 >= PAGE_SIZE - 1)
+							goto out;
+						buffer[pos++] = '\\';
+						buffer[pos++] = (c >> 6) + '0';
+						buffer[pos++] =
+							((c >> 3) & 7) + '0';
+						buffer[pos++] = (c & 7) + '0';
+					}
+					cp++;
+				}
+			} else {
+				/* Assume nobody assigns "=\$=" for procname. */
+				snprintf(buffer + pos, PAGE_SIZE - pos - 1,
+					 "/=%d=", n);
+				if (!memchr(buffer, '\0', PAGE_SIZE - 2))
+					goto out;
+			}
+			if (table->child) {
+				name++;
+				nlen--;
+				table = table->child;
+				goto repeat;
+			}
+			/* printk("sysctl='%s'\n", buffer); */
+			error = ccs_check_file_perm(buffer, op, "sysctl");
+			goto out;
+		}
+	}
+	error = -ENOTDIR;
+ out:
+	kfree(buffer);
+	return error;
+}
+/***** TOMOYO Linux end. *****/
+
 int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp,
 	       void __user *newval, size_t newlen)
 {
@@ -1519,6 +1608,11 @@ int do_sysctl(int __user *name, int nlen
 
 	for (head = sysctl_head_next(NULL); head;
 			head = sysctl_head_next(head)) {
+		/***** TOMOYO Linux start. *****/
+		error = try_parse_table(name, nlen, oldval, newval,
+					head->ctl_table);
+		if (!error)
+		/***** TOMOYO Linux end. *****/
 		error = parse_table(name, nlen, oldval, oldlenp, 
 					newval, newlen,
 					head->root, head->ctl_table);
--- linux-2.6.25-rc8-mm1.orig/kernel/time.c
+++ linux-2.6.25-rc8-mm1/kernel/time.c
@@ -41,6 +41,9 @@
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
 #include <asm/div64.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 #include "timeconst.h"
 
@@ -91,6 +94,10 @@ asmlinkage long sys_stime(time_t __user 
 	err = security_settime(&tv, NULL);
 	if (err)
 		return err;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SETTIME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	do_settimeofday(&tv);
 	return 0;
@@ -162,6 +169,10 @@ int do_sys_settimeofday(struct timespec 
 	error = security_settime(tv, tz);
 	if (error)
 		return error;
+	/***** TOMOYO Linux start. *****/
+	if (!ccs_capable(TOMOYO_SYS_SETTIME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	if (tz) {
 		/* SMP safe, global irq locking makes it work. */
--- linux-2.6.25-rc8-mm1.orig/kernel/time/ntp.c
+++ linux-2.6.25-rc8-mm1/kernel/time/ntp.c
@@ -18,6 +18,9 @@
 #include <linux/math64.h>
 #include <linux/clocksource.h>
 #include <asm/timex.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 /*
  * Timekeeping variables
@@ -283,6 +286,10 @@ int do_adjtimex(struct timex *txc)
 	/* In order to modify anything, you gotta be super-user! */
 	if (txc->modes && !capable(CAP_SYS_TIME))
 		return -EPERM;
+	/***** TOMOYO Linux start. *****/
+	if (txc->modes && !ccs_capable(TOMOYO_SYS_SETTIME))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	/* Now we validate the data before disabling interrupts */
 
--- linux-2.6.25-rc8-mm1.orig/net/core/datagram.c
+++ linux-2.6.25-rc8-mm1/net/core/datagram.c
@@ -56,6 +56,11 @@
 #include <net/sock.h>
 #include <net/tcp_states.h>
 
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+#include <linux/tomoyo_socket.h>
+/***** TOMOYO Linux end. *****/
+
 /*
  *	Is a socket 'connection oriented' ?
  */
@@ -179,6 +184,12 @@ struct sk_buff *__skb_recv_datagram(stru
 		}
 		spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
 
+		/***** TOMOYO Linux start. *****/
+		error = ccs_socket_recv_datagram_permission(sk, skb, flags);
+		if (error < 0)
+			goto no_packet;
+		/***** TOMOYO Linux end. *****/
+
 		if (skb)
 			return skb;
 
--- linux-2.6.25-rc8-mm1.orig/net/ipv4/inet_connection_sock.c
+++ linux-2.6.25-rc8-mm1/net/ipv4/inet_connection_sock.c
@@ -23,6 +23,9 @@
 #include <net/route.h>
 #include <net/tcp_states.h>
 #include <net/xfrm.h>
+/***** SAKURA Linux start. *****/
+#include <linux/sakura.h>
+/***** SAKURA Linux end. *****/
 
 #ifdef INET_CSK_DEBUG
 const char inet_csk_timer_bug_msg[] = "inet_csk BUG: unknown timer value\n";
@@ -98,6 +101,10 @@ int inet_csk_get_port(struct sock *sk, u
 		do {
 			head = &hashinfo->bhash[inet_bhashfn(rover, hashinfo->bhash_size)];
 			spin_lock(&head->lock);
+			/***** SAKURA Linux start. *****/
+			if (ccs_may_autobind(rover) < 0)
+				goto next;
+			/***** SAKURA Linux end. *****/
 			inet_bind_bucket_for_each(tb, node, &head->chain)
 				if (tb->ib_net == net && tb->port == rover)
 					goto next;
--- linux-2.6.25-rc8-mm1.orig/net/ipv4/inet_hashtables.c
+++ linux-2.6.25-rc8-mm1/net/ipv4/inet_hashtables.c
@@ -22,6 +22,9 @@
 #include <net/inet_connection_sock.h>
 #include <net/inet_hashtables.h>
 #include <net/ip.h>
+/***** SAKURA Linux start. *****/
+#include <linux/sakura.h>
+/***** SAKURA Linux end. *****/
 
 /*
  * Allocate and initialize a new local port bind bucket.
@@ -421,6 +424,10 @@ int __inet_hash_connect(struct inet_time
 		local_bh_disable();
 		for (i = 1; i <= remaining; i++) {
 			port = low + (i + offset) % remaining;
+			/***** SAKURA Linux start. *****/
+			if (ccs_may_autobind(port) < 0)
+				continue;
+			/***** SAKURA Linux end. *****/
 			head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)];
 			spin_lock(&head->lock);
 
--- linux-2.6.25-rc8-mm1.orig/net/ipv4/udp.c
+++ linux-2.6.25-rc8-mm1/net/ipv4/udp.c
@@ -105,6 +105,9 @@
 #include <net/checksum.h>
 #include <net/xfrm.h>
 #include "udp_impl.h"
+/***** SAKURA Linux start. *****/
+#include <linux/sakura.h>
+/***** SAKURA Linux end. *****/
 
 /*
  *	Snmp MIB for the UDP layer
@@ -175,6 +178,10 @@ int udp_lib_get_port(struct sock *sk, un
 		/* 1st pass: look for empty (or shortest) hash chain */
 		for (i = 0; i < UDP_HTABLE_SIZE; i++) {
 			int size = 0;
+			/***** SAKURA Linux start. *****/
+			if (ccs_may_autobind(rover) < 0)
+				goto next;
+			/***** SAKURA Linux end. *****/
 
 			head = &udptable[rover & (UDP_HTABLE_SIZE - 1)];
 			if (hlist_empty(head))
@@ -198,6 +205,9 @@ int udp_lib_get_port(struct sock *sk, un
 		/* 2nd pass: find hole in shortest hash chain */
 		rover = best;
 		for (i = 0; i < (1 << 16) / UDP_HTABLE_SIZE; i++) {
+			/***** SAKURA Linux start. *****/
+			if (ccs_may_autobind(rover) == 0)
+			/***** SAKURA Linux end. *****/
 			if (! __udp_lib_lport_inuse(net, rover, udptable))
 				goto gotit;
 			rover += UDP_HTABLE_SIZE;
--- linux-2.6.25-rc8-mm1.orig/net/socket.c
+++ linux-2.6.25-rc8-mm1/net/socket.c
@@ -94,6 +94,11 @@
 #include <net/sock.h>
 #include <linux/netfilter.h>
 
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+#include <linux/tomoyo_socket.h>
+/***** TOMOYO Linux end. *****/
+
 static int sock_no_open(struct inode *irrelevant, struct file *dontcare);
 static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,
 			 unsigned long nr_segs, loff_t pos);
@@ -557,6 +562,11 @@ static inline int __sock_sendmsg(struct 
 	err = security_socket_sendmsg(sock, msg, size);
 	if (err)
 		return err;
+	/***** TOMOYO Linux start. *****/
+	if (ccs_socket_sendmsg_permission(sock, (struct sockaddr *)
+					  msg->msg_name, msg->msg_namelen))
+		return -EPERM;
+	/***** TOMOYO Linux end. *****/
 
 	return sock->ops->sendmsg(iocb, sock, msg, size);
 }
@@ -1120,6 +1130,12 @@ static int __sock_create(struct net *net
 		family = PF_PACKET;
 	}
 
+	/***** TOMOYO Linux start. *****/
+	err = ccs_socket_create_permission(family, type, protocol);
+	if (err)
+		return err;
+	/***** TOMOYO Linux end. *****/
+
 	err = security_socket_create(family, type, protocol, kern);
 	if (err)
 		return err;
@@ -1351,6 +1367,13 @@ asmlinkage long sys_bind(int fd, struct 
 			err = security_socket_bind(sock,
 						   (struct sockaddr *)address,
 						   addrlen);
+			/***** TOMOYO Linux start. *****/
+			if (!err)
+				err = ccs_socket_bind_permission(sock,
+							 (struct sockaddr *)
+								 address,
+								 addrlen);
+			/***** TOMOYO Linux end. *****/
 			if (!err)
 				err = sock->ops->bind(sock,
 						      (struct sockaddr *)
@@ -1380,6 +1403,10 @@ asmlinkage long sys_listen(int fd, int b
 			backlog = somaxconn;
 
 		err = security_socket_listen(sock, backlog);
+		/***** TOMOYO Linux start. *****/
+		if (!err)
+			err = ccs_socket_listen_permission(sock);
+		/***** TOMOYO Linux end. *****/
 		if (!err)
 			err = sock->ops->listen(sock, backlog);
 
@@ -1444,6 +1471,13 @@ asmlinkage long sys_accept(int fd, struc
 	if (err < 0)
 		goto out_fd;
 
+	/***** TOMOYO Linux start. *****/
+	if (ccs_socket_accept_permission(newsock,
+					 (struct sockaddr *) address)) {
+		err = -ECONNABORTED; /* Hope less harmful than -EPERM. */
+		goto out_fd;
+	}
+	/***** TOMOYO Linux end. *****/
 	if (upeer_sockaddr) {
 		if (newsock->ops->getname(newsock, (struct sockaddr *)address,
 					  &len, 2) < 0) {
@@ -1508,6 +1542,13 @@ asmlinkage long sys_connect(int fd, stru
 	    security_socket_connect(sock, (struct sockaddr *)address, addrlen);
 	if (err)
 		goto out_put;
+	/***** TOMOYO Linux start. *****/
+	err = ccs_socket_connect_permission(sock,
+					    (struct sockaddr *) address,
+					    addrlen);
+	if (err)
+		goto out_put;
+	/***** TOMOYO Linux end. *****/
 
 	err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
 				 sock->file->f_flags);
--- linux-2.6.25-rc8-mm1.orig/net/unix/af_unix.c
+++ linux-2.6.25-rc8-mm1/net/unix/af_unix.c
@@ -116,6 +116,9 @@
 #include <linux/mount.h>
 #include <net/checksum.h>
 #include <linux/security.h>
+/***** TOMOYO Linux start. *****/
+#include <linux/tomoyo.h>
+/***** TOMOYO Linux end. *****/
 
 static struct hlist_head unix_socket_table[UNIX_HASH_SIZE + 1];
 static DEFINE_SPINLOCK(unix_table_lock);
@@ -776,6 +779,11 @@ static int unix_bind(struct socket *sock
 		err = unix_autobind(sock);
 		goto out;
 	}
+	/***** TOMOYO Linux start. *****/
+	err = -EPERM;
+	if (sunaddr->sun_path[0] && !ccs_capable(TOMOYO_CREATE_UNIX_SOCKET))
+		goto out;
+	/***** TOMOYO Linux end. *****/
 
 	err = unix_mkname(sunaddr, addr_len, &hash);
 	if (err < 0)
@@ -822,6 +830,13 @@ static int unix_bind(struct socket *sock
 		err = mnt_want_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;
+		/***** TOMOYO Linux start. *****/
+		err = pre_vfs_mknod(nd.path.dentry->d_inode, dentry, mode);
+		if (!err)
+			err = ccs_check_1path_perm(TYPE_MKSOCK_ACL, dentry,
+						   nd.path.mnt);
+		if (!err)
+		/***** TOMOYO Linux end. *****/
 		err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);
 		mnt_drop_write(nd.path.mnt);
 		if (err)
--- linux-2.6.25-rc8-mm1.orig/Documentation/kernel-parameters.txt
+++ linux-2.6.25-rc8-mm1/Documentation/kernel-parameters.txt
@@ -2130,6 +2130,21 @@ and is between 256 and 4096 characters. 
 	norandmaps	Don't use address space randomization
 			Equivalent to echo 0 > /proc/sys/kernel/randomize_va_space
 
+	CCS_loader=	[SAKURA/TOMOYO] Set policy loader's location.
+			Format: a valid pathname.
+			Default value /sbin/ccs-init.
+			This program is called when /sbin/init starts.
+
+	TOMOYO_QUIET	[TOMOYO] Turn off verbose mode by default.
+			Value can be changed at runtime via /proc/ccs/profile.
+
+	SYAORAN=	[SYAORAN] Set initial access control status.
+			Format: {"accept" | "enforce"}
+			accept -- permissive (log only, no denials).
+			enforce -- enforcing (deny and log).
+			Default value is not set.
+			Value can be changed at runtime via mount option.
+
 ______________________________________________________________________
 
 TODO:

-- 

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

* Re: [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO.
  2008-04-04 12:22 ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO Tetsuo Handa
@ 2008-04-04 15:29   ` Daniel Walker
  2008-04-07 13:56     ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA andTOMOYO Tetsuo Handa
  0 siblings, 1 reply; 74+ messages in thread
From: Daniel Walker @ 2008-04-04 15:29 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, Kentaro Takeda,
	Toshiharu Harada


On Fri, 2008-04-04 at 21:22 +0900, Tetsuo Handa wrote:
> +/**
> + * list1_entry - get the struct for this entry
> + * @ptr:        the &struct list1_head pointer.
> + * @type:       the type of the struct this is embedded in.
> + * @member:     the name of the list1_struct within the struct.
> + */
> +#define list1_entry(ptr, type, member) container_of(ptr, type,
> member)
> +
> +/**
> + * list1_for_each        -       iterate over a list
> + * @pos:        the &struct list1_head to use as a loop cursor.
> + * @head:       the head for your list.
> + */
> +#define list1_for_each(pos,
> head)                                      \
> +       for (pos = (head)->next; prefetch(pos->next), pos !=
> (head);    \
> +            pos = pos->next)
> +

These should go into include/linux/list.h .. It looks like you are
duplicate at least some of what is already in there..

Daniel


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-04 12:23 ` [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO Tetsuo Handa
@ 2008-04-04 16:29   ` Daniel Walker
  2008-04-07 13:56     ` Tetsuo Handa
  2008-04-07 15:40   ` Paul Moore
  1 sibling, 1 reply; 74+ messages in thread
From: Daniel Walker @ 2008-04-04 16:29 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, Kentaro Takeda,
	Toshiharu Harada, linux-fsdevel, linux-netdev


On Fri, 2008-04-04 at 21:23 +0900, Tetsuo Handa wrote:

> This patch makes two lines deletion by
>   sed -e 's:search_binary_handler:search_binary_handler_with_transition:'
> TOMOYO does domain transition when execve() is called.
> Thus, distinguishing search_binary_handler() from do_execve() and
> search_binary_handler() from other functions (e.g. load_script())
> makes TOMOYO's domain transition handler simple.
> 
> Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
> Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>
> Cc: linux-netdev <netdev@vger.kernel.org>
> ---
>  Documentation/kernel-parameters.txt |   15 ++++
>  arch/ia64/ia32/sys_ia32.c           |    7 ++
>  arch/mips/kernel/ptrace32.c         |    7 ++
>  arch/s390/kernel/ptrace.c           |    7 ++
>  arch/sh/kernel/ptrace_64.c          |    7 ++
>  arch/x86/kernel/ptrace.c            |    7 ++
>  fs/Kconfig                          |    2 
>  fs/Makefile                         |    2 
>  fs/attr.c                           |   19 +++++

>From a reviews perspective what I would want is each set of changes,
file system, networking, arch, etc split into separate patches. For
example you have a number of patches just adding header files. You could
merge the header file with the hook additions. Then you have a natural
code split up which should be easier to review..

> +	/***** TOMOYO Linux start. *****/
> +	if (!ccs_capable(TOMOYO_SYS_PTRACE))
> +		return -EPERM;
> +	/***** TOMOYO Linux end. *****/

For instance if the function name was "tomoyo_check_capable" it would be
clear that it's part of your code.. The current function naming here is
obscure ..
 

> +	/***** CCS start. *****/
> +#if defined(CONFIG_SAKURA) || defined(CONFIG_TOMOYO)
> +	printk(KERN_INFO "Hook version: 2.6.25-rc8-mm1 2008/04/02\n");
> +#endif
> +	/***** CCS end. *****/

This printk clearly needs to go away ..

> --- linux-2.6.25-rc8-mm1.orig/include/linux/init_task.h
> +++ linux-2.6.25-rc8-mm1/include/linux/init_task.h
> @@ -197,6 +197,10 @@ extern struct group_info init_groups;
>  	INIT_IDS							\
>  	INIT_TRACE_IRQFLAGS						\
>  	INIT_LOCKDEP							\
> +	/***** TOMOYO Linux start. *****/        \
> +	.domain_info = &KERNEL_DOMAIN,           \
> +	.tomoyo_flags = 0,                       \
> +	/***** TOMOYO Linux end. *****/          \
>  }

ifdef's ?

> 
> --- linux-2.6.25-rc8-mm1.orig/include/linux/sched.h
> +++ linux-2.6.25-rc8-mm1/include/linux/sched.h
> @@ -29,6 +29,11 @@
>  #define CLONE_NEWNET		0x40000000	/* New network namespace */
>  #define CLONE_IO		0x80000000	/* Clone io context */
>  
> +/***** TOMOYO Linux start. *****/
> +struct domain_info;
> +extern struct domain_info KERNEL_DOMAIN;
> +/***** TOMOYO Linux end. *****/
> +
>  /*
>   * Scheduling policies
>   */
> @@ -1278,6 +1283,10 @@ struct task_struct {
>  	int latency_record_count;
>  	struct latency_record latency_record[LT_SAVECOUNT];
>  #endif
> +	/***** TOMOYO Linux start. *****/
> +	struct domain_info *domain_info;
> +	u32 tomoyo_flags;
> +	/***** TOMOYO Linux end. *****/
>  };

ifdefs? 
 



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

* Re: [TOMOYO #7 02/30] Internal functions prototypes for SAKURA andTOMOYO.
  2008-04-04 15:29   ` Daniel Walker
@ 2008-04-07 13:56     ` Tetsuo Handa
  2008-04-07 15:24       ` Daniel Walker
  0 siblings, 1 reply; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-07 13:56 UTC (permalink / raw)
  To: dwalker; +Cc: akpm, linux-kernel, linux-security-module, takedakn, haradats

Daniel Walker wrote:
> > +/**
> > + * list1_entry - get the struct for this entry
> > + * @ptr:        the &struct list1_head pointer.
> > + * @type:       the type of the struct this is embedded in.
> > + * @member:     the name of the list1_struct within the struct.
> > + */
> > +#define list1_entry(ptr, type, member) container_of(ptr, type,
> > member)
> > +
> > +/**
> > + * list1_for_each        -       iterate over a list
> > + * @pos:        the &struct list1_head to use as a loop cursor.
> > + * @head:       the head for your list.
> > + */
> > +#define list1_for_each(pos,
> > head)                                      \
> > +       for (pos = (head)->next; prefetch(pos->next), pos !=
> > (head);    \
> > +            pos = pos->next)
> > +
> 
> These should go into include/linux/list.h .. It looks like you are
> duplicate at least some of what is already in there..

I see. I'll move it on next posting.
Although people advised me to use standard doubly linked list,
TOMOYO wants to use singly linked list because the elements are append only.
If TOMOYO is allowed to use singly linked list, it can make the source code read lock free.

Thank you.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-04 16:29   ` Daniel Walker
@ 2008-04-07 13:56     ` Tetsuo Handa
  2008-04-07 15:39       ` Daniel Walker
  0 siblings, 1 reply; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-07 13:56 UTC (permalink / raw)
  To: dwalker
  Cc: akpm, linux-kernel, linux-security-module, takedakn, haradats,
	linux-fsdevel, netdev

Daniel Walker wrote:
> From a reviews perspective what I would want is each set of changes,
> file system, networking, arch, etc split into separate patches. For
> example you have a number of patches just adding header files. You could
> merge the header file with the hook additions. Then you have a natural
> code split up which should be easier to review..
Filesystem part to fsdevel, socket operation part to netdev. That's OK.
But I wonder where to post the rest part.
Since the patches for passing "struct vfsmount" to LSM has been rejected,
TOMOYO has been unable to use LSM.
Thus, I proposed this patch set as a non-LSM version, but it bothers me
where to post this patch. If you know, please teach me where to post.

> > --- linux-2.6.25-rc8-mm1.orig/include/linux/init_task.h
> > +++ linux-2.6.25-rc8-mm1/include/linux/init_task.h
> > @@ -197,6 +197,10 @@ extern struct group_info init_groups;
> >  	INIT_IDS							\
> >  	INIT_TRACE_IRQFLAGS						\
> >  	INIT_LOCKDEP							\
> > +	/***** TOMOYO Linux start. *****/        \
> > +	.domain_info = &KERNEL_DOMAIN,           \
> > +	.tomoyo_flags = 0,                       \
> > +	/***** TOMOYO Linux end. *****/          \
> >  }
> 
> ifdef's ?

Or security_initcall()?

> > @@ -1278,6 +1283,10 @@ struct task_struct {
> >  	int latency_record_count;
> >  	struct latency_record latency_record[LT_SAVECOUNT];
> >  #endif
> > +	/***** TOMOYO Linux start. *****/
> > +	struct domain_info *domain_info;
> > +	u32 tomoyo_flags;
> > +	/***** TOMOYO Linux end. *****/
> >  };
> 
> ifdefs? 
>
I see.

Thank you.

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

* Re: [TOMOYO #7 02/30] Internal functions prototypes for SAKURA andTOMOYO.
  2008-04-07 13:56     ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA andTOMOYO Tetsuo Handa
@ 2008-04-07 15:24       ` Daniel Walker
  0 siblings, 0 replies; 74+ messages in thread
From: Daniel Walker @ 2008-04-07 15:24 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, takedakn, haradats


On Mon, 2008-04-07 at 22:56 +0900, Tetsuo Handa wrote:

> I see. I'll move it on next posting.
> Although people advised me to use standard doubly linked list,
> TOMOYO wants to use singly linked list because the elements are append only.
> If TOMOYO is allowed to use singly linked list, it can make the source code read lock free.

I was just suggesting that you move your singly linked list operations
into list.h . There are some other places in the kernel that use singly
linked lists, but they have open coded operations (for instance
tasklets) ..

Daniel


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-07 13:56     ` Tetsuo Handa
@ 2008-04-07 15:39       ` Daniel Walker
  0 siblings, 0 replies; 74+ messages in thread
From: Daniel Walker @ 2008-04-07 15:39 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, takedakn, haradats,
	linux-fsdevel, netdev


On Mon, 2008-04-07 at 22:56 +0900, Tetsuo Handa wrote:
> Daniel Walker wrote:
> > From a reviews perspective what I would want is each set of changes,
> > file system, networking, arch, etc split into separate patches. For
> > example you have a number of patches just adding header files. You could
> > merge the header file with the hook additions. Then you have a natural
> > code split up which should be easier to review..
> Filesystem part to fsdevel, socket operation part to netdev. That's OK.
> But I wonder where to post the rest part.
> Since the patches for passing "struct vfsmount" to LSM has been rejected,
> TOMOYO has been unable to use LSM.
> Thus, I proposed this patch set as a non-LSM version, but it bothers me
> where to post this patch. If you know, please teach me where to post.

Not sure .. You might look through the MAINTAINERS file and see if
anyone maintains the sections of code your changing. Usually LKML and
Andrew are pretty good bets especially when your not sure who the
patches should go to.

Daniel


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-04 12:23 ` [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO Tetsuo Handa
  2008-04-04 16:29   ` Daniel Walker
@ 2008-04-07 15:40   ` Paul Moore
  2008-04-07 22:57     ` Casey Schaufler
  2008-04-09  8:37     ` Toshiharu Harada
  1 sibling, 2 replies; 74+ messages in thread
From: Paul Moore @ 2008-04-07 15:40 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, Kentaro Takeda,
	Toshiharu Harada, linux-fsdevel, linux-netdev

On Friday 04 April 2008 8:23:12 am Tetsuo Handa wrote:
> This file contains modifications against kernel source code
> needed to use TOMOYO Linux 1.6.
>
> Although LSM hooks are provided for performing access control,
> TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.

Hello,

I understand your frustration with the existing LSM hooks/API and your 
reasoning for abandoning LSM in favor of a new set of hooks, however, I 
think this sets a dangerous precedence which could result in an 
abundance of security related hooks scattered throughout the kernel.  I 
would much rather see the LSM API extended/tweaked to support the needs 
of SAKURA and TOMOYO than ignored and duplicated; I suspect several 
others will say the same.

You have made good progress with TOMOYO so far and if I can remember 
correctly you really only have one hurdle left, the VFS portion.  
Please continue to seek a solution to this that fits within the LSM 
framework.

Thank you.

-- 
paul moore
linux @ hp

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-07 15:40   ` Paul Moore
@ 2008-04-07 22:57     ` Casey Schaufler
  2008-04-09  8:37     ` Toshiharu Harada
  1 sibling, 0 replies; 74+ messages in thread
From: Casey Schaufler @ 2008-04-07 22:57 UTC (permalink / raw)
  To: Paul Moore, Tetsuo Handa
  Cc: akpm, linux-kernel, linux-security-module, Kentaro Takeda,
	Toshiharu Harada, linux-fsdevel, linux-netdev


--- Paul Moore <paul.moore@hp.com> wrote:

> On Friday 04 April 2008 8:23:12 am Tetsuo Handa wrote:
> > This file contains modifications against kernel source code
> > needed to use TOMOYO Linux 1.6.
> >
> > Although LSM hooks are provided for performing access control,
> > TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.
> 
> Hello,
> 
> I understand your frustration with the existing LSM hooks/API and your 
> reasoning for abandoning LSM in favor of a new set of hooks, however, I 
> think this sets a dangerous precedence which could result in an 
> abundance of security related hooks scattered throughout the kernel.  I 
> would much rather see the LSM API extended/tweaked to support the needs 
> of SAKURA and TOMOYO than ignored and duplicated; I suspect several 
> others will say the same.

The same.



Casey Schaufler
casey@schaufler-ca.com

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-07 15:40   ` Paul Moore
  2008-04-07 22:57     ` Casey Schaufler
@ 2008-04-09  8:37     ` Toshiharu Harada
  2008-04-09 12:49       ` Stephen Smalley
                         ` (2 more replies)
  1 sibling, 3 replies; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-09  8:37 UTC (permalink / raw)
  To: Paul Moore
  Cc: Tetsuo Handa, akpm, linux-kernel, linux-security-module,
	Kentaro Takeda, linux-fsdevel, linux-netdev

On 4/8/2008 12:40 AM, Paul Moore wrote:
> On Friday 04 April 2008 8:23:12 am Tetsuo Handa wrote:
>> This file contains modifications against kernel source code
>> needed to use TOMOYO Linux 1.6.
>>
>> Although LSM hooks are provided for performing access control,
>> TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.
> 
> Hello,
> 
> I understand your frustration with the existing LSM hooks/API and your 
> reasoning for abandoning LSM in favor of a new set of hooks, however, I 
> think this sets a dangerous precedence which could result in an 
> abundance of security related hooks scattered throughout the kernel.  I 
> would much rather see the LSM API extended/tweaked to support the needs 
> of SAKURA and TOMOYO than ignored and duplicated; I suspect several 
> others will say the same.
> 
> You have made good progress with TOMOYO so far and if I can remember 
> correctly you really only have one hurdle left, the VFS portion.  
> Please continue to seek a solution to this that fits within the LSM 
> framework.
> 
> Thank you.

Thank you for your comments and concern.

I realized that we should have included the reason why we decided to
post non-LSM version. Let me explain the reason and the history.

We started developing TOMOYO Linux as original patch sets against
2.4 vanilla kernel. We understand the role of LSM, so we ported
TOMOYO Linux to use LSM and submitted it to the LKML on 13 June 2007.
We kept working to reflect feedbacks from the community and believe
no critical Nack remains.

http://lwn.net/Articles/238049/
http://lwn.net/Articles/246930/
http://lwn.net/Articles/252652/
http://lwn.net/Articles/254503/
http://lwn.net/Articles/258905/
http://lwn.net/Articles/263179/
http://lwn.net/Articles/264187/
http://lwn.net/Articles/276603/

Still there remains an issue of LSM limitation (vfsmount parameter
isn’t passed to LSM).

LWN article 239962 says, "At the 2006 summit, Linus took a clear
position that the use of pathnames for security policies seemed
reasonable to him". Current LSM implementation is sufficient for SELinux
and other label based MACs but not for pathname-based MACs.
This has been argued in the AppAmor thread for quite a long time.
Though proposals had been posted by AppArmor and TOMOYO Linux project,
none has been merged until now.

We apologize for the confusion we caused in the last posting,
but we don't want to give up returning our work to the mainline. 

We cordially request LSM changes to pass vfsmount parameters.

Finally, the following links are our answers to the Linux Weather Forecast. (http://www.linux-foundation.org/en/Linux_Weather_Forecast/security#TOMOYO_Linux)

http://tomoyo.sourceforge.jp/wiki-e/?WhatIs#comparison
http://sourceforge.jp/projects/tomoyo/document/fosdem2008.pdf
http://sourceforge.jp/projects/tomoyo/document/PacSec2007-handout.pdf

Regards,
Toshiharu Harada
NTT DATA CORPORATION


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09  8:37     ` Toshiharu Harada
@ 2008-04-09 12:49       ` Stephen Smalley
  2008-04-10  5:57         ` Toshiharu Harada
  2008-04-09 13:11       ` Matthew Wilcox
  2008-04-09 13:22       ` Serge E. Hallyn
  2 siblings, 1 reply; 74+ messages in thread
From: Stephen Smalley @ 2008-04-09 12:49 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev


On Wed, 2008-04-09 at 17:37 +0900, Toshiharu Harada wrote:
> On 4/8/2008 12:40 AM, Paul Moore wrote:
> > On Friday 04 April 2008 8:23:12 am Tetsuo Handa wrote:
> >> This file contains modifications against kernel source code
> >> needed to use TOMOYO Linux 1.6.
> >>
> >> Although LSM hooks are provided for performing access control,
> >> TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.
> > 
> > Hello,
> > 
> > I understand your frustration with the existing LSM hooks/API and your 
> > reasoning for abandoning LSM in favor of a new set of hooks, however, I 
> > think this sets a dangerous precedence which could result in an 
> > abundance of security related hooks scattered throughout the kernel.  I 
> > would much rather see the LSM API extended/tweaked to support the needs 
> > of SAKURA and TOMOYO than ignored and duplicated; I suspect several 
> > others will say the same.
> > 
> > You have made good progress with TOMOYO so far and if I can remember 
> > correctly you really only have one hurdle left, the VFS portion.  
> > Please continue to seek a solution to this that fits within the LSM 
> > framework.
> > 
> > Thank you.
> 
> Thank you for your comments and concern.
> 
> I realized that we should have included the reason why we decided to
> post non-LSM version. Let me explain the reason and the history.
> 
> We started developing TOMOYO Linux as original patch sets against
> 2.4 vanilla kernel. We understand the role of LSM, so we ported
> TOMOYO Linux to use LSM and submitted it to the LKML on 13 June 2007.
> We kept working to reflect feedbacks from the community and believe
> no critical Nack remains.
> 
> http://lwn.net/Articles/238049/
> http://lwn.net/Articles/246930/
> http://lwn.net/Articles/252652/
> http://lwn.net/Articles/254503/
> http://lwn.net/Articles/258905/
> http://lwn.net/Articles/263179/
> http://lwn.net/Articles/264187/
> http://lwn.net/Articles/276603/
> 
> Still there remains an issue of LSM limitation (vfsmount parameter
> isn’t passed to LSM).
> 
> LWN article 239962 says, "At the 2006 summit, Linus took a clear
> position that the use of pathnames for security policies seemed
> reasonable to him". Current LSM implementation is sufficient for SELinux
> and other label based MACs but not for pathname-based MACs.
> This has been argued in the AppAmor thread for quite a long time.
> Though proposals had been posted by AppArmor and TOMOYO Linux project,
> none has been merged until now.
> 
> We apologize for the confusion we caused in the last posting,
> but we don't want to give up returning our work to the mainline. 
> 
> We cordially request LSM changes to pass vfsmount parameters.

Don't cordially request it - submit patches to make it happen.  Or work
with others who have been submitting such patches.

There are two options:
1) Submit patches to pass down the vfsmounts to the vfs helpers so that
they can be passed to the existing security_inode hooks. -or-
2) Submit patches to add new security hooks to the callers where the
vfsmount is already available (some have suggested moving the existing
security_inode hooks to the callers, but that would cause problems for
SELinux as I've posted elsewhere, so adding new hooks is preferable, and
then SELinux can just default to the dummy functions for those new
hooks).

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09  8:37     ` Toshiharu Harada
  2008-04-09 12:49       ` Stephen Smalley
@ 2008-04-09 13:11       ` Matthew Wilcox
  2008-04-09 13:26         ` Stephen Smalley
  2008-04-11 14:12         ` Tetsuo Handa
  2008-04-09 13:22       ` Serge E. Hallyn
  2 siblings, 2 replies; 74+ messages in thread
From: Matthew Wilcox @ 2008-04-09 13:11 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev

On Wed, Apr 09, 2008 at 05:37:38PM +0900, Toshiharu Harada wrote:
> LWN article 239962 says, "At the 2006 summit, Linus took a clear
> position that the use of pathnames for security policies seemed
> reasonable to him". Current LSM implementation is sufficient for SELinux
> and other label based MACs but not for pathname-based MACs.
> This has been argued in the AppAmor thread for quite a long time.
> Though proposals had been posted by AppArmor and TOMOYO Linux project,
> none has been merged until now.

How about an approach which doesn't require the vfsmount to be passed
down?

When the rule is put in place, say "No modifications to /etc/passwd",
look up the inode and major:minor of /etc/passwd.  If there's a rename,
look up the new inode number.  If it's mounted elsewhere, it doesn't
matter, they still can't modify it because it has the same
major:minor:inode.

Is this workable?

-- 
Intel are signing my paycheques ... these opinions are still mine
"Bill, look, we understand that you're interested in selling us this
operating system, but compare it to ours.  We can't possibly take such
a retrograde step."

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09  8:37     ` Toshiharu Harada
  2008-04-09 12:49       ` Stephen Smalley
  2008-04-09 13:11       ` Matthew Wilcox
@ 2008-04-09 13:22       ` Serge E. Hallyn
  2008-04-11  3:57         ` Toshiharu Harada
  2 siblings, 1 reply; 74+ messages in thread
From: Serge E. Hallyn @ 2008-04-09 13:22 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev

Quoting Toshiharu Harada (haradats@nttdata.co.jp):
> On 4/8/2008 12:40 AM, Paul Moore wrote:
>> On Friday 04 April 2008 8:23:12 am Tetsuo Handa wrote:
>>> This file contains modifications against kernel source code
>>> needed to use TOMOYO Linux 1.6.
>>>
>>> Although LSM hooks are provided for performing access control,
>>> TOMOYO Linux 1.6 doesn't use LSM because of the following reasons.
>> Hello,
>> I understand your frustration with the existing LSM hooks/API and your 
>> reasoning for abandoning LSM in favor of a new set of hooks, however, I 
>> think this sets a dangerous precedence which could result in an abundance 
>> of security related hooks scattered throughout the kernel.  I would much 
>> rather see the LSM API extended/tweaked to support the needs of SAKURA and 
>> TOMOYO than ignored and duplicated; I suspect several others will say the 
>> same.
>> You have made good progress with TOMOYO so far and if I can remember 
>> correctly you really only have one hurdle left, the VFS portion.  Please 
>> continue to seek a solution to this that fits within the LSM framework.
>> Thank you.
>
> Thank you for your comments and concern.
>
> I realized that we should have included the reason why we decided to
> post non-LSM version.

First let me point out that reviewing patches is always a lot of work.
What you've done here by posting an entirely new 30-patch implementation
of tomoyo when (I hope) you're not even serious about that is to
basically tell us our time means nothing to you...

If you *are* serious about it, than to whatever extent I can, which
isn't very much, I say nack.

Like you say there appear to be no real remaining objections to the LSM,
only to the VFS part.  You're going to try to get around the VFS
objections by not being an LSM?

Look right now TOMOYO is an out of tree patch.  You want to get it in
tree.  Don't be too hung up on getting it all in at once.  Why not
push a subset of the patch without the vfs controls, which will help
to motivate the vfs controls you need?  You can (1) keep a much smaller
out of tree patch with your implementation of the vfs controls for your
current customrs/installations, and/or (2) implement a temporary
non-pathname-based alternative, say using xattrs to tag files at setup
time - probably insufficient, but sufficient for people to play.

The smaller patch would also be easier to review.

> Let me explain the reason and the history.

We remember the history.  On the one hand we feel for you, but on the
other hand many of us have gone through the same thing, and if you'll
notice Casey went through the same thing and persisted.

> We started developing TOMOYO Linux as original patch sets against
> 2.4 vanilla kernel. We understand the role of LSM, so we ported
> TOMOYO Linux to use LSM and submitted it to the LKML on 13 June 2007.
> We kept working to reflect feedbacks from the community and believe
> no critical Nack remains.

Right, at this point it's mainly a question of finding a way to upstream
tomoyo.  (That's mainly *your* burden, but we do try to help :)

> http://lwn.net/Articles/238049/
> http://lwn.net/Articles/246930/
> http://lwn.net/Articles/252652/
> http://lwn.net/Articles/254503/
> http://lwn.net/Articles/258905/
> http://lwn.net/Articles/263179/
> http://lwn.net/Articles/264187/
> http://lwn.net/Articles/276603/
>
> Still there remains an issue of LSM limitation (vfsmount parameter
> isn?t passed to LSM).
>
> LWN article 239962 says, "At the 2006 summit, Linus took a clear
> position that the use of pathnames for security policies seemed
> reasonable to him".

Yes, but he didn't say you could implement it in a way that offends the
affected maintainers.  Nor did he say it's those maintainers'
responsibility to find you an acceptable solution.  They are in fact
being very nice by offering you suggestions.

Also, isn't Miklos helping you to try and find an acceptable approach?

> Current LSM implementation is sufficient for SELinux
> and other label based MACs but not for pathname-based MACs.
> This has been argued in the AppAmor thread for quite a long time.
> Though proposals had been posted by AppArmor and TOMOYO Linux project,
> none has been merged until now.

You're trying to make it sound like you've spent night and day for years
trying to work with the relevant people to come up with something
reasonable.  Yet for instance in the thread 
	"vfs: add helpers to check r/o bind mounts"
(april 2) where iiuc two reasonable approaches are discussed, you don't
even take part.

> We apologize for the confusion we caused in the last posting,
> but we don't want to give up returning our work to the mainline. 

I'm glad to hear that.  Please keep trying.

> We cordially request LSM changes to pass vfsmount parameters.

Again let me point out there is a difference between saying "Linus said
we can have pathname-based access control, but you won't implement it
for me" and doing the hard work to come up with something reasonable.
I know you've tried a few times, but from what I've seen your impression
of the work you've put into it is far different from my impression of
it.

-serge

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09 13:11       ` Matthew Wilcox
@ 2008-04-09 13:26         ` Stephen Smalley
  2008-04-11 14:12         ` Tetsuo Handa
  1 sibling, 0 replies; 74+ messages in thread
From: Stephen Smalley @ 2008-04-09 13:26 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Toshiharu Harada, Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev


On Wed, 2008-04-09 at 07:11 -0600, Matthew Wilcox wrote:
> On Wed, Apr 09, 2008 at 05:37:38PM +0900, Toshiharu Harada wrote:
> > LWN article 239962 says, "At the 2006 summit, Linus took a clear
> > position that the use of pathnames for security policies seemed
> > reasonable to him". Current LSM implementation is sufficient for SELinux
> > and other label based MACs but not for pathname-based MACs.
> > This has been argued in the AppAmor thread for quite a long time.
> > Though proposals had been posted by AppArmor and TOMOYO Linux project,
> > none has been merged until now.
> 
> How about an approach which doesn't require the vfsmount to be passed
> down?
> 
> When the rule is put in place, say "No modifications to /etc/passwd",
> look up the inode and major:minor of /etc/passwd.  If there's a rename,
> look up the new inode number.  If it's mounted elsewhere, it doesn't
> matter, they still can't modify it because it has the same
> major:minor:inode.
> 
> Is this workable?

Sounds similar to audit watches, e.g. see audit_add_watch() and
audit_handle_ievent().  That leverages inotify internally.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09 12:49       ` Stephen Smalley
@ 2008-04-10  5:57         ` Toshiharu Harada
  2008-04-10 12:51           ` Stephen Smalley
  0 siblings, 1 reply; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-10  5:57 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev

[-- Attachment #1: Type: text/plain, Size: 918 bytes --]

On 4/9/2008 9:49 PM, Stephen Smalley wrote:
>> We cordially request LSM changes to pass vfsmount parameters.
> 
> Don't cordially request it - submit patches to make it happen.  Or work
> with others who have been submitting such patches.

You are (always) right. :)

> There are two options:
> 1) Submit patches to pass down the vfsmounts to the vfs helpers so that
> they can be passed to the existing security_inode hooks. -or-
> 2) Submit patches to add new security hooks to the callers where the
> vfsmount is already available (some have suggested moving the existing
> security_inode hooks to the callers, but that would cause problems for
> SELinux as I've posted elsewhere, so adding new hooks is preferable, and
> then SELinux can just default to the dummy functions for those new
> hooks).

Thank you for your suggestions. I drew a diagram. Is this correct?

Regards,
Toshiharu Harada
NTT DATA CORPORATION

[-- Attachment #2: options.png --]
[-- Type: image/png, Size: 40962 bytes --]

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-10  5:57         ` Toshiharu Harada
@ 2008-04-10 12:51           ` Stephen Smalley
  2008-04-11 11:48             ` Toshiharu Harada
  0 siblings, 1 reply; 74+ messages in thread
From: Stephen Smalley @ 2008-04-10 12:51 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev


On Thu, 2008-04-10 at 14:57 +0900, Toshiharu Harada wrote:
> On 4/9/2008 9:49 PM, Stephen Smalley wrote:
> >> We cordially request LSM changes to pass vfsmount parameters.
> > 
> > Don't cordially request it - submit patches to make it happen.  Or work
> > with others who have been submitting such patches.
> 
> You are (always) right. :)

Definitely not always.

> > There are two options:
> > 1) Submit patches to pass down the vfsmounts to the vfs helpers so that
> > they can be passed to the existing security_inode hooks. -or-
> > 2) Submit patches to add new security hooks to the callers where the
> > vfsmount is already available (some have suggested moving the existing
> > security_inode hooks to the callers, but that would cause problems for
> > SELinux as I've posted elsewhere, so adding new hooks is preferable, and
> > then SELinux can just default to the dummy functions for those new
> > hooks).
> 
> Thank you for your suggestions. I drew a diagram. Is this correct?

I think the text above is self-explanatory; I'm not sure what the
diagram adds.  Also, Matthew Wilcox pointed out a third option that you
ought to consider, and you can look to the example of audit filesystem
watches there, which leverages inotify internally.

If that isn't feasible for some reason, then option (2) should be fairly
straightforward - you just define and insert some new security hooks in
the callers where the vfsmount is already available.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09 13:22       ` Serge E. Hallyn
@ 2008-04-11  3:57         ` Toshiharu Harada
  0 siblings, 0 replies; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-11  3:57 UTC (permalink / raw)
  To: Serge E. Hallyn
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev

On 4/9/2008 10:22 PM, Serge E. Hallyn wrote:
> First let me point out that reviewing patches is always a lot of work.
> What you've done here by posting an entirely new 30-patch implementation
> of tomoyo when (I hope) you're not even serious about that is to
> basically tell us our time means nothing to you...
> 
> If you *are* serious about it, than to whatever extent I can, which
> isn't very much, I say nack.
You are right. I appreciate your comments and thank you.

> Like you say there appear to be no real remaining objections to the LSM,
> only to the VFS part.  You're going to try to get around the VFS
> objections by not being an LSM?
What annoyed us was the fact we didn't know how the merge would take place.
No nacks can mean no interests and does not mean the ack.
Coding issues/problems can be corrected, but we didn't know how to react with
different way of thinking and concepts. We were at a loss rather than
being frustrated.

BUT I know every those things can't be an excuse. I feel sorry for our
last posting. I apology you all and promise we will never do that.
(I don't mean we will stop submitting)

> Look right now TOMOYO is an out of tree patch.  You want to get it in
> tree.  Don't be too hung up on getting it all in at once.  Why not
> push a subset of the patch without the vfs controls, which will help
> to motivate the vfs controls you need?  You can (1) keep a much smaller
> out of tree patch with your implementation of the vfs controls for your
> current customrs/installations, and/or (2) implement a temporary
> non-pathname-based alternative, say using xattrs to tag files at setup
> time - probably insufficient, but sufficient for people to play.
Yes.

> The smaller patch would also be easier to review.
Yes.

>> Let me explain the reason and the history.
> 
> We remember the history.  On the one hand we feel for you, but on the
> other hand many of us have gone through the same thing, and if you'll
> notice Casey went through the same thing and persisted.
Yes, it was ... really surprising. (I was even scared)

>> We started developing TOMOYO Linux as original patch sets against
>> 2.4 vanilla kernel. We understand the role of LSM, so we ported
>> TOMOYO Linux to use LSM and submitted it to the LKML on 13 June 2007.
>> We kept working to reflect feedbacks from the community and believe
>> no critical Nack remains.
> 
> Right, at this point it's mainly a question of finding a way to upstream
> tomoyo.  (That's mainly *your* burden, but we do try to help :)
It's a great pleasure to receive comments from people.

> Yes, but he didn't say you could implement it in a way that offends the
> affected maintainers.  Nor did he say it's those maintainers'
> responsibility to find you an acceptable solution.  They are in fact
> being very nice by offering you suggestions.
I couldn't agree with you more. I'll never quote that. :)

> Also, isn't Miklos helping you to try and find an acceptable approach?
We started following the thread and found it's very close to
our case. We will watch and join the discussion.

>> Current LSM implementation is sufficient for SELinux
>> and other label based MACs but not for pathname-based MACs.
>> This has been argued in the AppAmor thread for quite a long time.
>> Though proposals had been posted by AppArmor and TOMOYO Linux project,
>> none has been merged until now.
> 
> You're trying to make it sound like you've spent night and day for years
> trying to work with the relevant people to come up with something
> reasonable.  Yet for instance in the thread 
> 	"vfs: add helpers to check r/o bind mounts"
> (april 2) where iiuc two reasonable approaches are discussed, you don't
> even take part.
Right and I am ashamed of it now.

>> We apologize for the confusion we caused in the last posting,
>> but we don't want to give up returning our work to the mainline. 
> 
> I'm glad to hear that.  Please keep trying.
Thank you. (T_T) (crying)

>> We cordially request LSM changes to pass vfsmount parameters.
I wish I could revoke the above statement...

> Again let me point out there is a difference between saying "Linus said
> we can have pathname-based access control, but you won't implement it
> for me" and doing the hard work to come up with something reasonable.
> I know you've tried a few times, but from what I've seen your impression
> of the work you've put into it is far different from my impression of
> it.
Roger. (_ _) (bowing)

Best regards,
Toshiharu


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-10 12:51           ` Stephen Smalley
@ 2008-04-11 11:48             ` Toshiharu Harada
  0 siblings, 0 replies; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-11 11:48 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Paul Moore, Tetsuo Handa, akpm, linux-kernel,
	linux-security-module, Kentaro Takeda, linux-fsdevel,
	linux-netdev

On 4/10/2008 9:51 PM, Stephen Smalley wrote:
>>> There are two options:
>>> 1) Submit patches to pass down the vfsmounts to the vfs helpers so that
>>> they can be passed to the existing security_inode hooks. -or-
>>> 2) Submit patches to add new security hooks to the callers where the
>>> vfsmount is already available (some have suggested moving the existing
>>> security_inode hooks to the callers, but that would cause problems for
>>> SELinux as I've posted elsewhere, so adding new hooks is preferable, and
>>> then SELinux can just default to the dummy functions for those new
>>> hooks).
>> Thank you for your suggestions. I drew a diagram. Is this correct?
> 
> I think the text above is self-explanatory; I'm not sure what the
> diagram adds.  Also, Matthew Wilcox pointed out a third option that you
> ought to consider, and you can look to the example of audit filesystem
> watches there, which leverages inotify internally.
The diagram was meant to help clarifying things not to add/change
the information. I also like texts but IMO diagrams are useful
for starting arguments over networks.

Yes. Regarding the third option, Tetsuo is preparing to respond
(Matthew, sorry for snail response. it's on the way).

> If that isn't feasible for some reason, then option (2) should be fairly
> straightforward - you just define and insert some new security hooks in
> the callers where the vfsmount is already available.

My diagram worked very well for me. I noticed theoretically
there are four options.

option (1) "pass down the vfsmounts to the vfs helpers"
           (let "vfsmount" bridge namespace and filesystems)
+ LSM needs less changes
- VFS and filesystems need more changes

option (2) "add new security hooks to the callers"
           (adding hooks in namespace)
+ VFS and filesystems need very little changes
- LSM needs to be added new hooks

option (3) "pathname based policy and inode based access control" (by Wilcox)
           (self-explanatory)
+ does not need changes for LSM nor VFS
- can not keep consistency of policy and results

option (4) "introduce completely orthogonal access control besides LSM"
           (like devcgroup, r/o bind mounts (in mm tree))
+ does not need LSM changes
+ pathname based MAC can coexists with label based MAC
- should not ... (the LAST method)


Regarding option 3, Tetsuo will explain difficulties in
another message. TOMOYO Linux project is planning to
make patches of option 2 because it's the most straightforward way
as you suggested. Also we will be carefully watching the
discussion of "vfs: add helpers to check r/o bind mounts".

Regards,
Toshiharu Harada
NTT DATA CORPORATION



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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-09 13:11       ` Matthew Wilcox
  2008-04-09 13:26         ` Stephen Smalley
@ 2008-04-11 14:12         ` Tetsuo Handa
  2008-04-11 14:30           ` Matthew Wilcox
  1 sibling, 1 reply; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-11 14:12 UTC (permalink / raw)
  To: matthew
  Cc: paul.moore, penguin-kernel, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

Sorry for slow response.

Matthew Wilcox wrote:
> When the rule is put in place, say "No modifications to /etc/passwd",
> look up the inode and major:minor of /etc/passwd.  If there's a rename,
> look up the new inode number.  If it's mounted elsewhere, it doesn't
> matter, they still can't modify it because it has the same
> major:minor:inode.

If write access is denied because of a rule "No modifications to /etc/passwd",
a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
"mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
"mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.

"No modifications" (i.e. "forbid modifications") and
"Allow modifications" (i.e. "don't forbid modifications") are incompatible
rules as long as the rules are described using pathnames but the judgment is
done using inodes (or labels).

If rules are described like "No modifications to passwd_t",
it is correct to deny modifications of the file when the file
with passwd_t was renamed or bind-mounted or hard-linked.
Those who want to do access restriction based on the entity of the file
prefer rules described using inodes (or labels).

If rules are described like "No modifications to /etc/passwd" and
"Allow modifications to /tmp/passwd", it is wrong to deny modifications of
the file when /etc/passwd was renamed or bind-mounted or hard-linked to
/tmp/passwd .
Those who want to do access restriction based on the location of the file
prefer rules described using pathnames.

SELinux and Smack are the former.
AppArmor and TOMOYO Linux are the latter.

> Is this workable?
I'm afraid it is unlikely.

Thank you.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-11 14:12         ` Tetsuo Handa
@ 2008-04-11 14:30           ` Matthew Wilcox
  2008-04-12 11:33             ` Tetsuo Handa
                               ` (2 more replies)
  0 siblings, 3 replies; 74+ messages in thread
From: Matthew Wilcox @ 2008-04-11 14:30 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: paul.moore, akpm, linux-kernel, linux-security-module, takedakn,
	linux-fsdevel, netdev

On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
> Matthew Wilcox wrote:
> > When the rule is put in place, say "No modifications to /etc/passwd",
> > look up the inode and major:minor of /etc/passwd.  If there's a rename,
> > look up the new inode number.  If it's mounted elsewhere, it doesn't
> > matter, they still can't modify it because it has the same
> > major:minor:inode.
> 
> If write access is denied because of a rule "No modifications to /etc/passwd",
> a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
> "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
> "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.

That's a fundamental limitation of pathname-based security though.
If the same file exists in two places, you have to resolve the question
of which rule overrides the other.

In my role as a sysadmin, I would consider it a flaw if someone could
edit a file I'd marked uneditable -- simply by creating a hard-link to it.
If we look at existing systems, such as the immutable bit, those apply to
inodes, not to paths, so they can't be evaded.  If a system such as TOMOYA
allows evasion this easily, then it doesn't seem like an improvement.

So when considering your problem, I worked from the point of view of the
attacker "Oh, I can't modify /etc/passwd?  In that case, I'll create
a new name to the same file", and then figured out a defense to it.
I did not consider the case where the admin /wants/ to allow access
through different pathnames to the same file that's denied access.
That doesn't seem like a secure system to me.

In short, if a file is named as protected, I think the admin clearly
means to protect that file -- no matter what name is used to address it.

> If rules are described like "No modifications to passwd_t",
> it is correct to deny modifications of the file when the file
> with passwd_t was renamed or bind-mounted or hard-linked.
> Those who want to do access restriction based on the entity of the file
> prefer rules described using inodes (or labels).
> 
> If rules are described like "No modifications to /etc/passwd" and
> "Allow modifications to /tmp/passwd", it is wrong to deny modifications of
> the file when /etc/passwd was renamed or bind-mounted or hard-linked to
> /tmp/passwd .
> Those who want to do access restriction based on the location of the file
> prefer rules described using pathnames.

My impression of pathname-based security was that it was preferred
because it's easier to administer than setting labels and making sure
everything has the right capabilities.  But from what you're saying,
it seems like it's no additional security at all.

Let's take an example.  We have the two rules "nobody can edit
/etc/passwd" and "root can edit /tmp/passwd".  A daemon running as root
is compromised.  Instead of being able to simply write to /etc/passwd,
the attacker has to "ln /etc/passwd /tmp" first.  There's no additional
security here, just obfuscation.

So I say a 'DENY' rule must always override an 'ALLOW' rule where two
filenames happen to map to the same file.  Or it's just snake-oil.

-- 
Intel are signing my paycheques ... these opinions are still mine
"Bill, look, we understand that you're interested in selling us this
operating system, but compare it to ours.  We can't possibly take such
a retrograde step."

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-11 14:30           ` Matthew Wilcox
@ 2008-04-12 11:33             ` Tetsuo Handa
  2008-04-13 16:36             ` Serge E. Hallyn
  2008-04-14  1:41             ` Crispin Cowan
  2 siblings, 0 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-12 11:33 UTC (permalink / raw)
  To: matthew
  Cc: paul.moore, akpm, linux-kernel, linux-security-module, takedakn,
	linux-fsdevel, netdev

----- part 1 -----

Matthew Wilcox wrote:
> In my role as a sysadmin, I would consider it a flaw if someone could
> edit a file I'd marked uneditable -- simply by creating a hard-link to it.

That depends on the *language* you used for marking.

I feel it is a flaw if the policy says "you can read a file reached by
resolving /tmp/ro_file" and "you can read/write a file reached by resolving
/tmp/rw_file" but the access control mechanism says "you cannot write a file
reached by resolving /tmp/rw_file because this file is a hard link of
/tmp/ro_file".
Both statements "you can read a file reached by resolving /tmp/ro_file" and
"you can read/write a file reached by resolving /tmp/rw_file" have to be
adhered by the access control mechanism. If not, I dismiss such access control
mechanism.

I feel it is a flaw if the policy says "you can read a file with ro_file_t" and
"you can read/write a file with rw_file_t" but the access control mechanism
says "you cannot write a file with rw_file_t".

Those who use policy based access control must understand what the policy says.
Pathname language does not refer to the entity of a file.
It refers to one of routes to reach to a file.
Don't expect pathname language to refer to the entity of a file.
If you use pathnames as the language to describe rules in policy,
you must understand what the pathname language refers to and accept it.
If you cannot accept it, don't use pathname language in policy.
Use label (or inode number) as the language to describe rules in policy
(like SELinux).

> In short, if a file is named as protected, I think the admin clearly
> means to protect that file -- no matter what name is used to address it.

If "named" in your statement used pathname, the admin didn't clearly mean to
protect that file.
If "named" in your statement used inode number (or label), the admin clearly
meant to protect that file.

----- part 2 -----

> If the same file exists in two places, you have to resolve the question
> of which rule overrides the other.

Your suggestion to use pathname in policy file but judge based on inode number
is incompatible.

What users see and approve as the policy is pathnames, and they expect
the policy to be adhered. But if the policy cannot be adhered by some reason
(e.g. hard link problems), access control mechanism must ask users for
explicit approval *before* the policy is loaded.
It is no good to override the rules without asking users for explicit approval.
If the rules are implicitly overridden and the system became malfunction,
users will dismiss such access control mechanism.

If you suggest to use inode number in policy file (and users see and approve
as the policy) and judge based on inode number, it is correct, and that is
LIDS (Linux Intrusion Detecting System).

> Let's take an example.  We have the two rules "nobody can edit
> /etc/passwd" and "root can edit /tmp/passwd".  A daemon running as root
> is compromised.  Instead of being able to simply write to /etc/passwd,
> the attacker has to "ln /etc/passwd /tmp" first.  There's no additional
> security here, just obfuscation.

It seems to me that you are assuming that pathname based access control uses
black listing. Pathname based access control uses while listing.
So, unless permission to do "ln /etc/passwd /tmp" is explicitly defined in
the policy, the attacker cannot do "ln /etc/passwd /tmp".

The character of pathname based policy is that pathname doesn't reflect
the past state. But please be patient and continue reading before you complain
"Pathname based access control is completely helpless for security".
Pathname based access control has a role in security.

----- part 3 -----

I don't want to start label-vs-pathname battle again.
But I must explain that security is not so simple that can be covered by
only label (or inode number) based access control.

Correctness of userland application code plays some part of security,
correctness of user's operation plays some part of security,
correctness of kernel code plays some part of security,
correctness of file's label plays some part of security,
correctness of file's pathname plays some part of security,
and more...

What SELinux people are talking about is "correctness of file's label is
important".
But I want to talk about "correctness of file's pathname is also important".

Do you think pathname has nothing to do with system's security?
The answer is no. Names have meaning. Names are very important.

The system's availability depends on whether essential files are in place.

You can write policy that allows mounting procfs on /sys/ and sysfs on /proc/ .
But if you actually do such mounts, the system will no longer work as expected.
/proc/ is expected that procfs is mounted and /sys/ is expected that sysfs
is mounted.

Let's consider that buggy program1 needs to create /etc/config1 and
rename /etc/config1 to /etc/config1.old .
You can write policy that allows creating and renaming files in /etc/ .
But if the granularity is "allow creating and renaming files in /etc/ ",
it can lead to unexpected behavior.
If program1 created /etc/config1 and renamed /etc/config1 to /etc/config1.old ,
that's no problem.
If program1 created /etc/config1 and renamed /etc/config1 to /etc/nologin ,
that's problematic. You know, /etc/nologin is a marker that indicates
non-root users are not permitted to login to the system.
You should have written policy that only allows "create /etc/config1" and
"rename /etc/config1 to /etc/config1.old" rather than
"create files in /etc/ directory" and "rename files in /etc/ directory"
to make sure that program1 won't intervene other user's login.

No problem because labels are not changed?
OK. Let's consider that malicious admin1 is permitted to create /forcefsck
and run /sbin/reboot to do maintenance task.
You can write policy that allows creating files in / .
If malicious admin1 creates /forcefsck and runs /sbin/reboot ,
that's no problem.
If malicious admin1 creates /.autorelabel and runs /sbin/reboot ,
that's problematic. You know, files that are not in place will lose their
labels since relabeling operation takes place upon bootup.
You should have written policy that only allows "create /forcefsck" rather than
"create files in / directory" to make sure that admin1 won't tamper labels.

What if /etc/passwd is renamed to /etc/my_passwd?
Label (or inode number) is not changed. But users can no longer login.
Still no problems?

What if /bin/cat and /usr/bin/md5sum are exchanged?
Label (or inode number) is not changed. But many scripts stops working.
Still no problems?

What if /etc/ is bind mounted to /bin/ ?
Label (or inode number) is not changed. But programs won't work.
Still no problems?

No way...

It is a label's (or inode's) advantage that the result of access request is not
affected by rename/link/bind-mount operations.
But that is different from "I can rename/link/bind-mount files freely because
rename/link/bind-mount won't affect the result of access request".
The system behaves differently if essential files are not in place.
To keep the system available, don't allow rename/link/bind-mount operations
freely from the beginning, even if the labels (or inode numbers) are preserved.

----- part 4 -----

Operations that changes pathnames (i.e. rename/link/bind-mount ) are
unnecessary for almost all files in a system. Only a few files such as
/etc/passwd.$$ are subjected to rename/link/bind-mount operations.

When defining pathname based policy, firstly, separate files that are subjected
to rename/link/bind-mount operations and files that are not.
Secondly, write policy that allows rename/link/bind-mount operations with
the granularity of /foo/bar rather than /foo/ to minimize the uncertain part
in the pathname.

> So when considering your problem, I worked from the point of view of the
> attacker "Oh, I can't modify /etc/passwd?  In that case, I'll create
> a new name to the same file", and then figured out a defense to it.

Don't expect that /etc/passwd is allowed to freely rename/link/bind-mount from
the beginning. It is not allowed by default, and is allowed only if it is
needed for the system to work properly.

You might think that "Why do you spend resources in restricting names that
can exist within a mount tree? You can save a lot of resources if you don't
use pathnames for restricting access.", and the below is my answer.

"Applications depend on pathnames, not on inode's number or labels.
 Thinking little of pathnames leads to serious result."

SELinux (label based access control) tries to keep correctness of
file's attributes by restricting operation that changes attributes.
TOMOYO Linux (pathname based access control) tries to keep correctness of
file's pathnames by restricting operation that changes names.
SELinux and TOMOYO Linux are both security tools.
But they play different part in security.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-11 14:30           ` Matthew Wilcox
  2008-04-12 11:33             ` Tetsuo Handa
@ 2008-04-13 16:36             ` Serge E. Hallyn
  2008-04-14  2:05               ` Crispin Cowan
  2008-04-14  1:41             ` Crispin Cowan
  2 siblings, 1 reply; 74+ messages in thread
From: Serge E. Hallyn @ 2008-04-13 16:36 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev, crispin

Quoting Matthew Wilcox (matthew@wil.cx):
> On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
> > Matthew Wilcox wrote:
> > > When the rule is put in place, say "No modifications to /etc/passwd",
> > > look up the inode and major:minor of /etc/passwd.  If there's a rename,
> > > look up the new inode number.  If it's mounted elsewhere, it doesn't
> > > matter, they still can't modify it because it has the same
> > > major:minor:inode.
> > 
> > If write access is denied because of a rule "No modifications to /etc/passwd",
> > a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
> > "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
> > "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.
> 
> That's a fundamental limitation of pathname-based security though.
> If the same file exists in two places, you have to resolve the question
> of which rule overrides the other.

In the past, Crispin has given clear, concise explanations of a few of
the things pathname based access control in fact excels at.  Crispin,
can you recite those again so we can think constructively about which
(if any) of the currently considered options are or are not sufficient?

I.e. what would be a motivation for a rule like 'no modifications to
/etc/passwd', and what precisely would and would not be accepted ways to
get around it (and why)?

thanks,
-serge

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-11 14:30           ` Matthew Wilcox
  2008-04-12 11:33             ` Tetsuo Handa
  2008-04-13 16:36             ` Serge E. Hallyn
@ 2008-04-14  1:41             ` Crispin Cowan
  2008-04-14 13:48               ` Matthew Wilcox
  2 siblings, 1 reply; 74+ messages in thread
From: Crispin Cowan @ 2008-04-14  1:41 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

Matthew Wilcox wrote:
> On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
>   
>> If write access is denied because of a rule "No modifications to /etc/passwd",
>> a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
>> "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
>> "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.
>>     
> That's a fundamental limitation of pathname-based security though.
> If the same file exists in two places, you have to resolve the question
> of which rule overrides the other.
>
> In my role as a sysadmin, I would consider it a flaw if someone could
> edit a file I'd marked uneditable -- simply by creating a hard-link to it.
> If we look at existing systems, such as the immutable bit, those apply to
> inodes, not to paths, so they can't be evaded.  If a system such as TOMOYA
> allows evasion this easily, then it doesn't seem like an improvement.
>   
You are discussing a straw-man, because AppArmor (and I think TOMOYO) do 
not operate that way.

It is not, and never has been, "mark /etc/passwd not writable". Please 
delete this broken concept from the discussion.

Rather, it is "can write to /tmp/ntpd/*". You *grant* permissions. You 
do *not* throw deny rules.

So if you grant write access to /tmp/mumble/barf you should expect it to 
always be accessible, regardless of whether someone creates an alias for it.

Please re-consider the rest of your analysis, because it doesn't work if 
there are only "allow" rules and no "deny" rules. You are correct that a 
pathname-based deny rule is trivially bypassable, that's why there 
aren't any :)

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
Botnets are the only commercially viable utility computing market


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-13 16:36             ` Serge E. Hallyn
@ 2008-04-14  2:05               ` Crispin Cowan
  2008-04-14 14:17                 ` Stephen Smalley
  2008-04-15 13:00                 ` Toshiharu Harada
  0 siblings, 2 replies; 74+ messages in thread
From: Crispin Cowan @ 2008-04-14  2:05 UTC (permalink / raw)
  To: Serge E. Hallyn
  Cc: Matthew Wilcox, Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

Serge E. Hallyn wrote:
> Quoting Matthew Wilcox (matthew@wil.cx):
>   
>> On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
>>     
>>> Matthew Wilcox wrote:
>>>       
>>>> When the rule is put in place, say "No modifications to /etc/passwd",
>>>> look up the inode and major:minor of /etc/passwd.  If there's a rename,
>>>> look up the new inode number.  If it's mounted elsewhere, it doesn't
>>>> matter, they still can't modify it because it has the same
>>>> major:minor:inode.
>>>>         
>>> If write access is denied because of a rule "No modifications to /etc/passwd",
>>> a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
>>> "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
>>> "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.
>>>       
>> That's a fundamental limitation of pathname-based security though.
>> If the same file exists in two places, you have to resolve the question
>> of which rule overrides the other.
>>     
> In the past, Crispin has given clear, concise explanations of a few of
> the things pathname based access control in fact excels at.  Crispin,
> can you recite those again so we can think constructively about which
> (if any) of the currently considered options are or are not sufficient?
>
> I.e. what would be a motivation for a rule like 'no modifications to
> /etc/passwd', and what precisely would and would not be accepted ways to
> get around it (and why)?
>   
As I just posted, a rule of "no mods to /some/pathname" is broken, which 
is why AppArmor has no such construct.

Things that pathname-based access control is good at:

    * *System Integrity:* Many of the vital components of a UNIX system
      are stored in files with Well Known Names such as /etc/shadow,
      /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
      contents of the actual data blocks is less important than the
      integrity of what some random process gets when it asks for these
      resources by name. Preserving the integrity of what responds to
      the Well Known Name is thus easier if you restrict access based on
      the name.
    * *Dynamic Access Control:* A special case of the above pertains to
      files that may or may not exist. If you don't *have* a /etc/hosts
      file, it is still important to have a rule that controls whether
      it can be created. This is hard to do in label-based systems,
      because the file does not exist to put a label on, so you have to
      kludge it by either creating the file with zero length and
      labeling it, or by creating more complex policy for the parent
      /etc directory, and that's hard given the number of uses for /etc
      such as /etc/motd. In a name based scheme, you simply don't
      provide write permission to "/etc/hosts" unless you mean it, and
      it can be enforced even if such a file does not exist.
    * *Ad Hoc Generalization:* Label-based access control generalizes
      policy in that the policy treats all files and resources that
      share a label the same. If you want to make a new generalization
      that encompasses *part* of the files that share a label, but *not
      all* of those files, then you have to "split the label", relabel
      the file system, and revise the policy accordingly. Name-based
      access control lets you create ad hoc generalizations, so that
      *my* policy can grant access to "/var/www/*.pl" without regard to
      whatever labels or rules some other policy has applied to the same
      directory tree.
    * *Familiar Interface:* Administrators are accustomed to
      administering the box in terms of the names of the files that
      matter. A name-based policy is therefore somewhat more familiar
      than a policy with label abstractions, where they then have to go
      look at the restorecon file to discover what files will/should
      have what labels.
    * *Practical Concerns:* Not all file systems support labels, and so
      label-based schemes become coarse grained when they run into them.
      Name based schemes remain granular when specifying access down
      into the internals of such file systems. Legacy Linux file systems
      like ext2 don't matter much any more, but persistent file systems
      that will never have label support include NFSv3 file systems
      (nearly every Network Appliance NAS server out there) and FAT32
      file system (most every USB storage device).

A lot of what's going on is the duality of the one:many relationships 
between labels and names:

    * Labels: one label represents many files
          o this property is why ad hoc generalization fails in label
            based systems
    * Names: many names can represent a single file
          o This property is why deny rules fail in name based systems

Therefore, I am not claiming name-based access controls to be the 
ultimate. Rather, there are duals for all of the above that induce 
circumstances where label-based systems are superior. Each class of 
system has strengths and weaknesses. These are name-based strengths.

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
The Olympic Games: Symbolizing oppression and corruption for over a hundred years


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14  1:41             ` Crispin Cowan
@ 2008-04-14 13:48               ` Matthew Wilcox
  2008-04-15  3:21                 ` Crispin Cowan
  0 siblings, 1 reply; 74+ messages in thread
From: Matthew Wilcox @ 2008-04-14 13:48 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

On Sun, Apr 13, 2008 at 06:41:19PM -0700, Crispin Cowan wrote:
> You are discussing a straw-man, because AppArmor (and I think TOMOYO) do 
> not operate that way.

Thanks for clarifying that.  I had to put a straw-man up for discussion
because nobody else had.  I'll continue this discussion in terms of
allow-rules.

> Rather, it is "can write to /tmp/ntpd/*". You *grant* permissions. You 
> do *not* throw deny rules.

So primarily we're concerned here with things that are running as root,
daemons and the like.  Normal unix file permissions (or ACLs, if you
must) are adequate to handle anything not running as uid 0.

I don't see what apparmour and tomoya buy us that namespaces can't.
Maybe a nicer interface, but that's something that a nice userspace
management interface can handle.

Create an empty namespace.  Create /tmp/ntpd in it.  Bind the outside
/tmp/ntpd onto that directory.  Presto, the equivalent to an allow-rule
of 'can write to /tmp/ntpd/*'.

The equivalent of 'can read, but not write /home/crispin/.ssh/id_rsa.pub'
will need r-o bind mounts, which Miklos seems to have become distracted
from by working on the hooks for TOMOYA.

Do you have a good example of something that apparmour can protect against
that namespaces can't?

-- 
Intel are signing my paycheques ... these opinions are still mine
"Bill, look, we understand that you're interested in selling us this
operating system, but compare it to ours.  We can't possibly take such
a retrograde step."

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14  2:05               ` Crispin Cowan
@ 2008-04-14 14:17                 ` Stephen Smalley
  2008-04-14 17:05                   ` Casey Schaufler
  2008-04-15  4:59                   ` Crispin Cowan
  2008-04-15 13:00                 ` Toshiharu Harada
  1 sibling, 2 replies; 74+ messages in thread
From: Stephen Smalley @ 2008-04-14 14:17 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev


On Sun, 2008-04-13 at 19:05 -0700, Crispin Cowan wrote:
> Serge E. Hallyn wrote:
> > Quoting Matthew Wilcox (matthew@wil.cx):
> >   
> >> On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
> >>     
> >>> Matthew Wilcox wrote:
> >>>       
> >>>> When the rule is put in place, say "No modifications to /etc/passwd",
> >>>> look up the inode and major:minor of /etc/passwd.  If there's a rename,
> >>>> look up the new inode number.  If it's mounted elsewhere, it doesn't
> >>>> matter, they still can't modify it because it has the same
> >>>> major:minor:inode.
> >>>>         
> >>> If write access is denied because of a rule "No modifications to /etc/passwd",
> >>> a rule "Allow modifications to /tmp/passwd" can no longer be enforced after
> >>> "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
> >>> "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.
> >>>       
> >> That's a fundamental limitation of pathname-based security though.
> >> If the same file exists in two places, you have to resolve the question
> >> of which rule overrides the other.
> >>     
> > In the past, Crispin has given clear, concise explanations of a few of
> > the things pathname based access control in fact excels at.  Crispin,
> > can you recite those again so we can think constructively about which
> > (if any) of the currently considered options are or are not sufficient?
> >
> > I.e. what would be a motivation for a rule like 'no modifications to
> > /etc/passwd', and what precisely would and would not be accepted ways to
> > get around it (and why)?
> >   
> As I just posted, a rule of "no mods to /some/pathname" is broken, which 
> is why AppArmor has no such construct.
> 
> Things that pathname-based access control is good at:
> 
>     * *System Integrity:* Many of the vital components of a UNIX system
>       are stored in files with Well Known Names such as /etc/shadow,
>       /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
>       contents of the actual data blocks is less important than the
>       integrity of what some random process gets when it asks for these
>       resources by name. Preserving the integrity of what responds to
>       the Well Known Name is thus easier if you restrict access based on
>       the name.

I think some might argue that the integrity of the data in /etc/shadow
and your .ssh files is very important, not just their names.  And as
names are themselves just data contained by directories, the integrity
of the names is a particular case of the data integrity problem.  And
ultimately data integrity requires information flow control to preserve.

>     * *Dynamic Access Control:* A special case of the above pertains to
>       files that may or may not exist. If you don't *have* a /etc/hosts
>       file, it is still important to have a rule that controls whether
>       it can be created. This is hard to do in label-based systems,
>       because the file does not exist to put a label on, so you have to
>       kludge it by either creating the file with zero length and
>       labeling it, or by creating more complex policy for the parent
>       /etc directory, and that's hard given the number of uses for /etc
>       such as /etc/motd. In a name based scheme, you simply don't
>       provide write permission to "/etc/hosts" unless you mean it, and
>       it can be enforced even if such a file does not exist.

I'd view this as another example of practical or legacy concerns, as it
wouldn't pose a problem if either of the following were true:
- /etc was further partitioned into multiple subdirectories (seemingly
true in an increasing number of cases, so we seem to be moving in that
direction regardless) so that inheritance from parent directory would
suffice, or
- specific programs were used to create or update the individual files
(true for some of the files in /etc, and generally helpful for ensuring
proper data formatting and applying basic sanity checks on the content)
so that the files created by them could be labeled based on the creating
domain or via appropriate instrumentation of the programs to set the
attributes (the latter is not fundamentally different than the situation
for DAC ownership/mode or ACLs).

Also, note that in the case of SELinux, we do have a solution to the
above situation, namely restorecond, which leverages the kernel's
inotify mechanism.

As an example where the name-based schemes have difficulties with
"dynamic" data, files created at runtime in /tmp and other shared
directories often have security-irrelevant names, yet protecting them
from tampering by others can be very relevant to security.  There the
attribute-based schemes have the advantage.

>     * *Ad Hoc Generalization:* Label-based access control generalizes
>       policy in that the policy treats all files and resources that
>       share a label the same. If you want to make a new generalization
>       that encompasses *part* of the files that share a label, but *not
>       all* of those files, then you have to "split the label", relabel
>       the file system, and revise the policy accordingly. Name-based
>       access control lets you create ad hoc generalizations, so that
>       *my* policy can grant access to "/var/www/*.pl" without regard to
>       whatever labels or rules some other policy has applied to the same
>       directory tree.

We might argue about whether ad hoc policy writing is the best approach.
Regardless, the corresponding weakness here for the path-based schemes
is that they force you into writing policy in terms of the filesystem
layout and names, which may have little or no bearing on the actual
security characteristics of the files.

>     * *Familiar Interface:* Administrators are accustomed to
>       administering the box in terms of the names of the files that
>       matter. A name-based policy is therefore somewhat more familiar
>       than a policy with label abstractions, where they then have to go
>       look at the restorecon file to discover what files will/should
>       have what labels.

restorecon /path/to/file doesn't require the user to deal with anything
other than a pathname.  The file contexts configuration is entirely
hidden from the user by that interface.

>     * *Practical Concerns:* Not all file systems support labels, and so
>       label-based schemes become coarse grained when they run into them.
>       Name based schemes remain granular when specifying access down
>       into the internals of such file systems. Legacy Linux file systems
>       like ext2 don't matter much any more, but persistent file systems
>       that will never have label support include NFSv3 file systems
>       (nearly every Network Appliance NAS server out there) and FAT32
>       file system (most every USB storage device).

At present this is a limitation, although we do support per-mount
labeling of such filesystems, which is really all we can guarantee in
terms of protection anyway - anything further is misleading as the
server or device won't ensure any finer grained separation for us.  But
labeled NFS support is in progress, and one should never let legacy code
cripple one's approach.  Making compatibility allowances for legacy code
is quite reasonable, and is precisely what we do in terms of supporting
default type transitions, per-mount labels, etc, but that isn't the same
thing as completely constraining your design to only what is supported
by existing code.

> A lot of what's going on is the duality of the one:many relationships 
> between labels and names:
> 
>     * Labels: one label represents many files
>           o this property is why ad hoc generalization fails in label
>             based systems
>     * Names: many names can represent a single file
>           o This property is why deny rules fail in name based systems
> 
> Therefore, I am not claiming name-based access controls to be the 
> ultimate. Rather, there are duals for all of the above that induce 
> circumstances where label-based systems are superior. Each class of 
> system has strengths and weaknesses. These are name-based strengths.

The weaknesses from my POV are:
- no system-wide view of the subjects and objects and thus of the
policy,
- no uniform abstraction for handling objects (not everything has a
pathname), leading to inconsistent or incomplete control,
- no useful information for runtime objects,
- forcing policy to be written in terms of individual objects and
filesystem layout rather than security properties.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14 14:17                 ` Stephen Smalley
@ 2008-04-14 17:05                   ` Casey Schaufler
  2008-04-15 11:14                     ` Tetsuo Handa
  2008-04-15  4:59                   ` Crispin Cowan
  1 sibling, 1 reply; 74+ messages in thread
From: Casey Schaufler @ 2008-04-14 17:05 UTC (permalink / raw)
  To: Stephen Smalley, Crispin Cowan
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev


--- Stephen Smalley <sds@tycho.nsa.gov> wrote:

> 
> ...
> 
> I think some might argue that the integrity of the data in /etc/shadow
> and your .ssh files is very important, not just their names.

I believe that the key word here is "just". If you go back to
the Orange Book you will see that the combination of the object
and the name by which it is referenced is important. The audit
requirements are most interesting, as they make it clear that
without a relevant name associated with the object an audit
record has no value.

The Orange Book Unix evaluations raised enormous issues regarding
the file system name space. I know that at least two vendors
stated that the name of the object was its device/inode pair and
that the pathname stuff was there strictly for user convenience
and was not security relevent. This is of course hogwash, but
was logically consistant in the context of security policy
documentation. These same vendors had to then make completely
distinct arguments about their audit systems in order to meet
the name identification requirements there.

The question of protections on the object named /etc/passwd came
up time and time again. The notion that /etc/passwd could be a
symlink to /home/smalley/heeheehee really gave evaluators the
whillies. As did the chroot environment, where /roots/crispin/etc/passwd
could magicly become /etc/passwd. One reason that the object
access model is so firmly ingrained is that it allows the
security documentation to wave it's paw and say "bah" when
these questions arise.

> And as
> names are themselves just data contained by directories, the integrity
> of the names is a particular case of the data integrity problem.  And
> ultimately data integrity requires information flow control to preserve.

Good example of my point. The argument is true, and the implications
important, but the conclusion is not satisfactory.

> >     * *Dynamic Access Control:* A special case of the above pertains to
> >       files that may or may not exist. If you don't *have* a /etc/hosts
> >       file, it is still important to have a rule that controls whether
> >       it can be created. This is hard to do in label-based systems,
> >       because the file does not exist to put a label on, so you have to
> >       kludge it by either creating the file with zero length and
> >       labeling it, or by creating more complex policy for the parent
> >       /etc directory, and that's hard given the number of uses for /etc
> >       such as /etc/motd. In a name based scheme, you simply don't
> >       provide write permission to "/etc/hosts" unless you mean it, and
> >       it can be enforced even if such a file does not exist.
> 
> I'd view this as another example of practical or legacy concerns, as it
> wouldn't pose a problem if either of the following were true:
> - /etc was further partitioned into multiple subdirectories (seemingly
> true in an increasing number of cases, so we seem to be moving in that
> direction regardless) so that inheritance from parent directory would
> suffice, or
> - specific programs were used to create or update the individual files
> (true for some of the files in /etc, and generally helpful for ensuring
> proper data formatting and applying basic sanity checks on the content)
> so that the files created by them could be labeled based on the creating
> domain or via appropriate instrumentation of the programs to set the
> attributes (the latter is not fundamentally different than the situation
> for DAC ownership/mode or ACLs).

Yes, a proper design of the administrative data and interfaces
would solve the problem of /etc/passwd right quick. It would not
address the general issue which is assuring that access is controlled
based on the names that people use.

> Also, note that in the case of SELinux, we do have a solution to the
> above situation, namely restorecond, which leverages the kernel's
> inotify mechanism.

Well, I don't see that addressing the case where /etc/passwd is
a symlink unless restorecond is more sophisticated than I understand
it to be (correct me if I'm wrong) and I certainly can't see it
being used on every file on a multiple pedabyte filesystem.

> As an example where the name-based schemes have difficulties with
> "dynamic" data, files created at runtime in /tmp and other shared
> directories often have security-irrelevant names, yet protecting them
> from tampering by others can be very relevant to security.  There the
> attribute-based schemes have the advantage.

Very true.
 
> ...

> The weaknesses from my POV are:
> - no system-wide view of the subjects and objects and thus of the
> policy,
> - no uniform abstraction for handling objects (not everything has a
> pathname), leading to inconsistent or incomplete control,
> - no useful information for runtime objects,
> - forcing policy to be written in terms of individual objects and
> filesystem layout rather than security properties.

These are all legitimate concerns, but it is really hard to say
that they outweigh the value of controlling access to /etc/passwd
by specifying that you want to control access to /etc/passwd.
The real problem lies in the perfectly reasonable but nontheless
untrue assumptions that users, administrators, and developers
make about the relationship between names and objects on *ix
filesystems. The object model is more correct, and the dickins
easier to write kernel code for I might add. The problem is that
the object model is not what is exposed by the syscall interface,
it's the filesystem namespace. I have oft considered an OS (casex?)
that exposes the device/inode pair and leaves all the pathname
stuff to application space, but I doubt it would be popular
because the namespace is riddled with handy features.

We could fix the filesystem namespace, and then the issue would
evaporate, but I think too many people would think it was fixed
in the veterenary sense.


Casey Schaufler
casey@schaufler-ca.com

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14 13:48               ` Matthew Wilcox
@ 2008-04-15  3:21                 ` Crispin Cowan
  2008-04-15  4:57                   ` Al Viro
  0 siblings, 1 reply; 74+ messages in thread
From: Crispin Cowan @ 2008-04-15  3:21 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

Matthew Wilcox wrote:
> On Sun, Apr 13, 2008 at 06:41:19PM -0700, Crispin Cowan wrote:
>   
>> Rather, it is "can write to /tmp/ntpd/*". You *grant* permissions. You 
>> do *not* throw deny rules.
>>     
> So primarily we're concerned here with things that are running as root,
> daemons and the like.  Normal unix file permissions (or ACLs, if you
> must) are adequate to handle anything not running as uid 0.
>   
That's not true. Both AppArmor and SELinux Targeted Policy address 
confinement of both root and non-root applications. Examples:

    * Confining even non-root applications keeps them from accessing
      world and group accessible files.
    * Many services run as nobody instead of root, and smarter ones
      create themselves a new UID to run as. Even so, confining them is
      useful because the least-privilege posture is much easier to
      specify and verify in a capability model (as SELinux and AppArmor
      are) than an ACL model (as permission bits on files are).
    * You may want to confine a desktop application. E.g. Pidgin is a
      great IM tool because it speaks so many protocols, but with that
      large functionality comes a large attack surface, and it has had
      vulnerabilities from time to time. A confined IM client can be
      configured to only have access to your IM files, and not e.g. your
      SSH private keys.


> I don't see what apparmour and tomoya buy us that namespaces can't.
>   
Controlled overlap. You can use AppArmor to confine every *individual* 
piece of a web site shopping cart, and yet they still can interact with 
each other by sharing files. You cannot do that with namespaces.

Conversely, it is very convenient to use namespaces to set up private 
virtual domains, and that is not at all convenient to do with AppArmor, 
TOMOYO, or SELinux.

The correct answer is to use namespaces for total isolation (virtual 
domain hosting) and LSM confinement tools for security within a virtual 
domain.

> Maybe a nicer interface, but that's something that a nice userspace
> management interface can handle.
>   
Not true. Ease of management of access control is about the security 
model. Cute GUIs help, but not much.

> Create an empty namespace.  Create /tmp/ntpd in it.  Bind the outside
> /tmp/ntpd onto that directory.  Presto, the equivalent to an allow-rule
> of 'can write to /tmp/ntpd/*'.
>   
Now get ntpd to show you that you need to do this, in one pass. If you 
already know all of the files to be accessed, and you are going to write 
the security policy by hand, then the two approaches might be kind of 
comparable. But that's not how AppArmor policies are created. This is 
not a minor distinction.

> The equivalent of 'can read, but not write /home/crispin/.ssh/id_rsa.pub'
> will need r-o bind mounts, which Miklos seems to have become distracted
> from by working on the hooks for TOMOYA.
>
> Do you have a good example of something that apparmour can protect against
> that namespaces can't?
>   
See above. The major classes of things that namespaces can't do are:

    * deliberate overlap
    * learning mode
    * wild cards, e.g. 'can read /var/www/**.html' to grant access to
      all of the HTML files in the tree, but not the .pl source code files

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
The Olympic Games: Symbolizing oppressiiion and corruption for over a
hundred years



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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-15  3:21                 ` Crispin Cowan
@ 2008-04-15  4:57                   ` Al Viro
  0 siblings, 0 replies; 74+ messages in thread
From: Al Viro @ 2008-04-15  4:57 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Matthew Wilcox, Tetsuo Handa, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel, netdev

On Mon, Apr 14, 2008 at 08:21:42PM -0700, Crispin Cowan wrote:

> See above. The major classes of things that namespaces can't do are:
> 
>    * deliberate overlap

How do you think new namespaces are created[1] and how, in your opinion, do
they look right after that?

[1] no, "openvz crowd posts a patch revealing yet another offense against
Occam's Razor" is not the right answer to this question.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14 14:17                 ` Stephen Smalley
  2008-04-14 17:05                   ` Casey Schaufler
@ 2008-04-15  4:59                   ` Crispin Cowan
  2008-04-16 16:31                     ` Stephen Smalley
  1 sibling, 1 reply; 74+ messages in thread
From: Crispin Cowan @ 2008-04-15  4:59 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev

Stephen Smalley wrote:
> On Sun, 2008-04-13 at 19:05 -0700, Crispin Cowan wrote:
>   
>> Things that pathname-based access control is good at:
>>
>>     * *System Integrity:* Many of the vital components of a UNIX system
>>       are stored in files with Well Known Names such as /etc/shadow,
>>       /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
>>       contents of the actual data blocks is less important than the
>>       integrity of what some random process gets when it asks for these
>>       resources by name. Preserving the integrity of what responds to
>>       the Well Known Name is thus easier if you restrict access based on
>>       the name.
>>     
> I think some might argue that the integrity of the data in /etc/shadow
> and your .ssh files is very important, not just their names.
I understand how the confidentiality of secrets like the contents of 
/etc/shadow and your .ssh files is important, but how can the integrity 
of these data objects be important? Back them up if you care ...

>   And as
> names are themselves just data contained by directories, the integrity
> of the names is a particular case of the data integrity problem.
That's just access control for the containing directory, and/or access 
control to the raw partition, which is also  controlled by name-based 
access control to /dev

>   And
> ultimately data integrity requires information flow control to preserve.
>   
You've argued that before, and I've never been convinced. Rather, it 
looked a lot like a stretched definition trying really hard to turn 
integrity into an information flow problem.The most information flow 
that I will buy in the integrity problem is taint analysis of software 
inputs; that software should validate inputs before acting on it.

>>     * *Dynamic Access Control:* A special case of the above pertains to
>>       files that may or may not exist. If you don't *have* a /etc/hosts
>>       file, it is still important to have a rule that controls whether
>>       it can be created. This is hard to do in label-based systems,
>>       because the file does not exist to put a label on, so you have to
>>       kludge it by either creating the file with zero length and
>>       labeling it, or by creating more complex policy for the parent
>>       /etc directory, and that's hard given the number of uses for /etc
>>       such as /etc/motd. In a name based scheme, you simply don't
>>       provide write permission to "/etc/hosts" unless you mean it, and
>>       it can be enforced even if such a file does not exist.
>>     
> I'd view this as another example of practical or legacy concerns, as it
> wouldn't pose a problem if either of the following were true:
> - /etc was further partitioned into multiple subdirectories (seemingly
> true in an increasing number of cases, so we seem to be moving in that
> direction regardless) so that inheritance from parent directory would
> suffice, or
>   
If you want to move this particular benefit to the "practical" bucket, I 
have no issue with that. It is a core part of the name-based argument 
that it is a practical solution for system administrators who     
experience the system through names.

> - specific programs were used to create or update the individual files
> (true for some of the files in /etc, and generally helpful for ensuring
> proper data formatting and applying basic sanity checks on the content)
> so that the files created by them could be labeled based on the creating
> domain or via appropriate instrumentation of the programs to set the
> attributes (the latter is not fundamentally different than the situation
> for DAC ownership/mode or ACLs).
>   
Security architecture for a clean&pure architecture are an interesting 
problem, but certainly are not the LSM problem.

> Also, note that in the case of SELinux, we do have a solution to the
> above situation, namely restorecond, which leverages the kernel's
> inotify mechanism.
>   
IMHO that is a slow and racy solution. If one applied the kind of 
standards that AppArmor is being asked to pass to restorecond, it would 
have a tough time too.

> As an example where the name-based schemes have difficulties with
> "dynamic" data, files created at runtime in /tmp and other shared
> directories often have security-irrelevant names,
Presumably you mean that the name is not meaningfully predictable with 
respect tot he application. Yes, sometimes this is a problem, and 
AppArmor doesn't presently deal with it well.

>  yet protecting them
> from tampering by others can be very relevant to security.  There the
> attribute-based schemes have the advantage.
>   
John Johansen has some designs he has been working on to let an AA 
profile require a UID match as well as a name match. To the extent that 
file ownership is a kind of label, this is a small step towards a hybrid 
model that leverages the strengths of both name and label based access 
controls.

>>     * *Ad Hoc Generalization:* Label-based access control generalizes
>>       policy in that the policy treats all files and resources that
>>       share a label the same. If you want to make a new generalization
>>       that encompasses *part* of the files that share a label, but *not
>>       all* of those files, then you have to "split the label", relabel
>>       the file system, and revise the policy accordingly. Name-based
>>       access control lets you create ad hoc generalizations, so that
>>       *my* policy can grant access to "/var/www/*.pl" without regard to
>>       whatever labels or rules some other policy has applied to the same
>>       directory tree.
>>     
> We might argue about whether ad hoc policy writing is the best approach.
>   
Another way of saying "ad hoc policy writing" is "incremental policy 
development."

If you can anticipate everything you need in your data center and get it 
right ahead of time, then you don't need ad hoc policy. If circumstances 
change on you and you need to adapt, then your policy is necessarily ad hoc.

> Regardless, the corresponding weakness here for the path-based schemes
> is that they force you into writing policy in terms of the filesystem
> layout and names, which may have little or no bearing on the actual
> security characteristics of the files.
>   
I assert that the pathname has a strong correspondence to the integrity 
sensitivity of the file, and a weaker correspondence to the 
confidentiality sensitivity of the file, e.g. a backup copy of your 
actual /etc/shadow file is still sensitive if it gets disclosed to 
someone intent on password cracking.

This is not a severe problem for AppArmor in practice because of the 
default-deny nature of the policy: only grant read access to the files 
you intend to allow to be read. It is fairly difficult to accidentally 
disclose a file in an AppArmor policy unless you engage in really sloppy 
practice like dropping copies of sensitive files in publicly readable 
places like /tmp

>>     * *Familiar Interface:* Administrators are accustomed to
>>       administering the box in terms of the names of the files that
>>       matter. A name-based policy is therefore somewhat more familiar
>>       than a policy with label abstractions, where they then have to go
>>       look at the restorecon file to discover what files will/should
>>       have what labels.
>>     
> restorecon /path/to/file doesn't require the user to deal with anything
> other than a pathname.
Yes it does. To understand your security policy, you have to understand 
both the SELinux policy about labels & types and which are allowed to 
access each other, and also the restorecon specification that determines 
what labels will be applied to which files.

>>     * *Practical Concerns:* Not all file systems support labels, and so
>>       label-based schemes become coarse grained when they run into them.
>>       Name based schemes remain granular when specifying access down
>>       into the internals of such file systems. Legacy Linux file systems
>>       like ext2 don't matter much any more, but persistent file systems
>>       that will never have label support include NFSv3 file systems
>>       (nearly every Network Appliance NAS server out there) and FAT32
>>       file system (most every USB storage device).
>>     
> At present this is a limitation, although we do support per-mount
> labeling of such filesystems, which is really all we can guarantee in
> terms of protection anyway
That's what I meant by granular: you get access to the whole NFS mount, 
or none of it.

>  - anything further is misleading as the
> server or device won't ensure any finer grained separation for us.
I don't understand this issue. The enforcement here is t contain the 
program executing on the NFS *client* to permit it to only mangle the 
parts of the NFS mount that you want it to mangle. That the server won't 
enforce anything for you is irrelevant when the threat is the confined 
application.

>> A lot of what's going on is the duality of the one:many relationships 
>> between labels and names:
>>
>>     * Labels: one label represents many files
>>           o this property is why ad hoc generalization fails in label
>>             based systems
>>     * Names: many names can represent a single file
>>           o This property is why deny rules fail in name based systems
>>
>> Therefore, I am not claiming name-based access controls to be the 
>> ultimate. Rather, there are duals for all of the above that induce 
>> circumstances where label-based systems are superior. Each class of 
>> system has strengths and weaknesses. These are name-based strengths.
>>     
>
> The weaknesses from my POV are:
> - no system-wide view of the subjects and objects and thus of the
> policy,
>   
... and no *requirement* to construct a system-wide consistent view of 
security policy.

> - no uniform abstraction for handling objects (not everything has a
> pathname), leading to inconsistent or incomplete control,
>   
*Strawman* argument: AppArmor doesn't try to apply pathnames to 
everything, just the file system. The "uniform abstraction" is to 
specify security policy in the native terms of the resource being 
mediated. Files are named as /path/to/some/files/*.html and network 
resources are named in terms of ports and network addresses reminiscent 
of firewall rules.

In contrast, SELinux *does* apply the labeled model to everything. That 
has the strength that you are dealing with the same abstraction all the 
time, and the weakness that the mapping from the label abstraction to 
the stuff that admins and users have to actually deal with is arcane.

> - forcing policy to be written in terms of individual objects and
> filesystem layout rather than security properties.
>   
Name-based access control makes the overt assumption that the name of an 
object corresponds to its security properties. If your data layout does 
*not* make such an assumption, then you have some very strange data 
layout putting highly sensitive objects next to non-sensitive objects.

Note also that the SELinux restorecon mechanism also makes the 
assumption that path names correspond to security properties: in fact, 
that is precisely its function, to take a path name and use it to apply 
a security property (a label). Naturally I have no objection to 
inferring a security property from the path name :) I just object to the 
racy way that restorecon does it, combined with the complaint that 
AppArmor is wrong for doing exactly the same thing in a different way.

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
The Olympic Games: Symbolizing oppression and corruption for over a
hundred years


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14 17:05                   ` Casey Schaufler
@ 2008-04-15 11:14                     ` Tetsuo Handa
  2008-04-15 16:32                       ` Casey Schaufler
  2008-04-16 19:13                       ` Pavel Machek
  0 siblings, 2 replies; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-15 11:14 UTC (permalink / raw)
  To: casey, sds, crispin, casey
  Cc: serue, matthew, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel

Casey Schaufler wrote:
> The question of protections on the object named /etc/passwd came
> up time and time again. The notion that /etc/passwd could be a
> symlink to /home/smalley/heeheehee really gave evaluators the
> whillies. As did the chroot environment, where /roots/crispin/etc/passwd
> could magicly become /etc/passwd.
Why do people continue speaking symlinks and chroots?
To avoid the effect of symlinks and chroots, AppArmor and TOMOYO Linux
derive pathnames from dentry and vfsmount.
If /etc/passwd was a symlink, the derived pathname will be /home/smalley/heeheehee.
If accessed from inside a chroot, the derived pathname will be /roots/crispin/etc/passwd.

It is true that namespace may differ between processes,
but I think that that is the matter of how to restrict namespace manipulation operations.
As I said, a system can't survive if namespace is madly manipulated.
To keep the system workable, /bin/ must be the directory for binary programs,
/etc/ must be the directory for configuration files, and so on in all namespaces.

It is true that the pathname may change while traversing up the dentry/vfsmount trees.
But the change does not occur infinitely.
As I said, a system can't survive if files and directories are madly renamed.
The possible changes are bounded by the policy.

At least, I want people not to speak symlinks and chroots when talking about
AppArmor and TOMOYO Linux.

Regards.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-14  2:05               ` Crispin Cowan
  2008-04-14 14:17                 ` Stephen Smalley
@ 2008-04-15 13:00                 ` Toshiharu Harada
  1 sibling, 0 replies; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-15 13:00 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev

> Serge E. Hallyn wrote:
>> Quoting Matthew Wilcox (matthew@wil.cx):
>>
>>> On Fri, Apr 11, 2008 at 11:12:27PM +0900, Tetsuo Handa wrote:
>>>
>>>> Matthew Wilcox wrote:
>>>>
>>>>> When the rule is put in place, say "No modifications to /etc/passwd",
>>>>> look up the inode and major:minor of /etc/passwd.  If there's a
>>>>> rename,
>>>>> look up the new inode number.  If it's mounted elsewhere, it doesn't
>>>>> matter, they still can't modify it because it has the same
>>>>> major:minor:inode.
>>>>>
>>>> If write access is denied because of a rule "No modifications to
>>>> /etc/passwd",
>>>> a rule "Allow modifications to /tmp/passwd" can no longer be
>>>> enforced after
>>>> "mount --bind /etc/ /tmp/" or "mount --bind /etc/passwd /tmp/passwd" or
>>>> "mv /etc/passwd /tmp/passwd" or "ln /etc/passwd /tmp/passwd" is done.
>>>>
>>> That's a fundamental limitation of pathname-based security though.
>>> If the same file exists in two places, you have to resolve the question
>>> of which rule overrides the other.
>>>
>> In the past, Crispin has given clear, concise explanations of a few of
>> the things pathname based access control in fact excels at.  Crispin,
>> can you recite those again so we can think constructively about which
>> (if any) of the currently considered options are or are not sufficient?
>>
>> I.e. what would be a motivation for a rule like 'no modifications to
>> /etc/passwd', and what precisely would and would not be accepted ways to
>> get around it (and why)?
>>
> As I just posted, a rule of "no mods to /some/pathname" is broken, which
> is why AppArmor has no such construct.

Traditional "talking by examples" is a good way and works in general,
but not always right for this case (I mean "label or path" discussion.
Yes, I've learned this from AppArmor thread. :)

> Things that pathname-based access control is good at:
>
>    * *System Integrity:* Many of the vital components of a UNIX system
>      are stored in files with Well Known Names such as /etc/shadow,
>      /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
>      contents of the actual data blocks is less important than the
>      integrity of what some random process gets when it asks for these
>      resources by name. Preserving the integrity of what responds to
>      the Well Known Name is thus easier if you restrict access based on
>      the name.
>    * *Dynamic Access Control:* A special case of the above pertains to
>      files that may or may not exist. If you don't *have* a /etc/hosts
>      file, it is still important to have a rule that controls whether
>      it can be created. This is hard to do in label-based systems,
>      because the file does not exist to put a label on, so you have to
>      kludge it by either creating the file with zero length and
>      labeling it, or by creating more complex policy for the parent
>      /etc directory, and that's hard given the number of uses for /etc
>      such as /etc/motd. In a name based scheme, you simply don't
>      provide write permission to "/etc/hosts" unless you mean it, and
>      it can be enforced even if such a file does not exist.
>    * *Ad Hoc Generalization:* Label-based access control generalizes
>      policy in that the policy treats all files and resources that
>      share a label the same. If you want to make a new generalization
>      that encompasses *part* of the files that share a label, but *not
>      all* of those files, then you have to "split the label", relabel
>      the file system, and revise the policy accordingly. Name-based
>      access control lets you create ad hoc generalizations, so that
>      *my* policy can grant access to "/var/www/*.pl" without regard to
>      whatever labels or rules some other policy has applied to the same
>      directory tree.
>    * *Familiar Interface:* Administrators are accustomed to
>      administering the box in terms of the names of the files that
>      matter. A name-based policy is therefore somewhat more familiar
>      than a policy with label abstractions, where they then have to go
>      look at the restorecon file to discover what files will/should
>      have what labels.
>    * *Practical Concerns:* Not all file systems support labels, and so
>      label-based schemes become coarse grained when they run into them.
>      Name based schemes remain granular when specifying access down
>      into the internals of such file systems. Legacy Linux file systems
>      like ext2 don't matter much any more, but persistent file systems
>      that will never have label support include NFSv3 file systems
>      (nearly every Network Appliance NAS server out there) and FAT32
>      file system (most every USB storage device).
>
> A lot of what's going on is the duality of the one:many relationships
> between labels and names:
>
>    * Labels: one label represents many files
>          o this property is why ad hoc generalization fails in label
>            based systems
>    * Names: many names can represent a single file
>          o This property is why deny rules fail in name based systems
>
> Therefore, I am not claiming name-based access controls to be the
> ultimate. Rather, there are duals for all of the above that induce
> circumstances where label-based systems are superior. Each class of
> system has strengths and weaknesses. These are name-based strengths.

Agreed and I personally would love to stay discussions in this layer
forgetting existing implementations, including my own project of
TOMOYO Linux. The above summary of labels and names are very important,
but I would like to "raise" the layer of discussion.

The essence of MAC is limiting and restricting.
My version of the MAC *issues* are as follows:

- how to distinguish good(necessary) and bad(unnecessary) accesses
- how to describe the rule (the most important result is a policy language)
- how to keep Linux kernel aware of the rule and keep it safely
- how to administrate the whole picture (with human error in mind)

The first one belongs to a human responsible part and the
remainders belong to implementations. Let me note, label vs. names
issue resides in the implementation layer.

How to describe rules imply "what is the natural way for human
administrators" while keeping the Linux kernel implies
"what is the most solid and trusted way for Linux".
>From the fact is Linux works with names and inodes, I'm pretty sure
we will end up with some sort of hybrid system.

>From the above point of view, the option 2 which Stephen
kindly showed quite *practical* to me, because it allows
loose connection of the "namespace" and "inode". (If your
initial is not S.S, my PNG diagram posted on April 10 might help
to understand what I wrote here)


BTW I'm attending the ELC2008. Hope to see and talk in peace
with some of related people. :)

Regards,
Toshiharu Harada
NTT DATA CORPORATION

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-15 11:14                     ` Tetsuo Handa
@ 2008-04-15 16:32                       ` Casey Schaufler
  2008-04-17  7:24                         ` Crispin Cowan
  2008-04-16 19:13                       ` Pavel Machek
  1 sibling, 1 reply; 74+ messages in thread
From: Casey Schaufler @ 2008-04-15 16:32 UTC (permalink / raw)
  To: Tetsuo Handa, casey, sds, crispin
  Cc: serue, matthew, paul.moore, akpm, linux-kernel,
	linux-security-module, takedakn, linux-fsdevel


--- Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> wrote:

> Casey Schaufler wrote:
> > The question of protections on the object named /etc/passwd came
> > up time and time again. The notion that /etc/passwd could be a
> > symlink to /home/smalley/heeheehee really gave evaluators the
> > whillies. As did the chroot environment, where /roots/crispin/etc/passwd
> > could magicly become /etc/passwd.
>
> Why do people continue speaking symlinks and chroots?

Because on any given Linux system you could have an arbitrarily
large number of different things that might be accessed by the
name "/etc/passwd" and a different, but similarly large number
of names other than "/etc/passwd" that can be used to access them.

> To avoid the effect of symlinks and chroots, AppArmor and TOMOYO Linux
> derive pathnames from dentry and vfsmount.
> If /etc/passwd was a symlink, the derived pathname will be
> /home/smalley/heeheehee.
> If accessed from inside a chroot, the derived pathname will be
> /roots/crispin/etc/passwd.

Which doesn't hold up under hard links, which I had carefully
avoided and that both AppArmor and TOMOYO Linux have to place
restrictions on for the systems to make sense.

> It is true that namespace may differ between processes,
> but I think that that is the matter of how to restrict namespace manipulation
> operations.
> As I said, a system can't survive if namespace is madly manipulated.

That's hardly the viewpoint of those who would have every
user mount their own version of /tmp.

> To keep the system workable, /bin/ must be the directory for binary programs,
> /etc/ must be the directory for configuration files, and so on in all
> namespaces.

Only for general purpose shell access. General purpose shell access
is decreasing in popularity.

> It is true that the pathname may change while traversing up the
> dentry/vfsmount trees.
> But the change does not occur infinitely.
> As I said, a system can't survive if files and directories are madly renamed.
> The possible changes are bounded by the policy.
> 
> At least, I want people not to speak symlinks and chroots when talking about
> AppArmor and TOMOYO Linux.

The issues with links, symlinks, chroots and mounts in the
context of a name based access control scheme will always
need to be addressed, just as the issues of unlabeled filesystems
and /tmp will have to be in label based scheme.


Casey Schaufler
casey@schaufler-ca.com

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-15  4:59                   ` Crispin Cowan
@ 2008-04-16 16:31                     ` Stephen Smalley
  2008-04-17  7:49                       ` Crispin Cowan
  0 siblings, 1 reply; 74+ messages in thread
From: Stephen Smalley @ 2008-04-16 16:31 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev


On Mon, 2008-04-14 at 21:59 -0700, Crispin Cowan wrote:
> Stephen Smalley wrote:
> > On Sun, 2008-04-13 at 19:05 -0700, Crispin Cowan wrote:
> >   
> >> Things that pathname-based access control is good at:
> >>
> >>     * *System Integrity:* Many of the vital components of a UNIX system
> >>       are stored in files with Well Known Names such as /etc/shadow,
> >>       /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
> >>       contents of the actual data blocks is less important than the
> >>       integrity of what some random process gets when it asks for these
> >>       resources by name. Preserving the integrity of what responds to
> >>       the Well Known Name is thus easier if you restrict access based on
> >>       the name.
> >>     
> > I think some might argue that the integrity of the data in /etc/shadow
> > and your .ssh files is very important, not just their names.
> I understand how the confidentiality of secrets like the contents of 
> /etc/shadow and your .ssh files is important, but how can the integrity 
> of these data objects be important? Back them up if you care ...

If you aren't concerned with unauthorized data flow into
your /etc/shadow and .ssh files, then I think we'll just have to stop
right there in our discussion, as we evidently don't have a common point
of reference in what we mean by "security".  Personally I'd be troubled
if an unauthorized entity can ultimately feed data to such files, even
if indirectly by tricking a privileged process into conveying the data
to its ultimate target, a not-so-uncommon pattern.

> >   And as
> > names are themselves just data contained by directories, the integrity
> > of the names is a particular case of the data integrity problem.
> That's just access control for the containing directory, and/or access 
> control to the raw partition, which is also  controlled by name-based 
> access control to /dev
> 
> >   And
> > ultimately data integrity requires information flow control to preserve.
> >   
> You've argued that before, and I've never been convinced. Rather, it 
> looked a lot like a stretched definition trying really hard to turn 
> integrity into an information flow problem.The most information flow 
> that I will buy in the integrity problem is taint analysis of software 
> inputs; that software should validate inputs before acting on it.

In some cases, you can simply prohibit a security-relevant process from
taking untrustworthy inputs.  Like blocking privileged processes from
following untrustworthy symlinks to counter malicious symlink attacks or
from reading any files other than ones created by the admin.  In other
cases, you need to allow untrustworthy inputs to ultimately flow to the
security-relevant process, but you want to force them through some kind
of validation as you say above, which you can do by enforcing a
processing pipeline that forces the data to go through a subsystem that
performs validation and/or sanitization before it ever reaches the
security-relevant process.  That's how integrity is an information flow
problem.  And this isn't a new idea, btw, it is one that was expressed
long ago in the Biba model, a variant of which happens to be implemented
and used in Vista, and is more usefully achievable via Type Enforcement
since there we can control the processing flow precisely and bind the
validation/sanitization subsystem to specific code.

> >  - anything further is misleading as the
> > server or device won't ensure any finer grained separation for us.
> I don't understand this issue. The enforcement here is t contain the 
> program executing on the NFS *client* to permit it to only mangle the 
> parts of the NFS mount that you want it to mangle. That the server won't 
> enforce anything for you is irrelevant when the threat is the confined 
> application.

Except that you have to consider what is happening on the server too,
given that the files are visible to local processes there, and what
happens on all of the clients.  And the aliasing problem that exists in
the local filesystem case becomes exacerbated in the NFS environment.

> > - no uniform abstraction for handling objects (not everything has a
> > pathname), leading to inconsistent or incomplete control,
> >   
> *Strawman* argument: AppArmor doesn't try to apply pathnames to 
> everything, just the file system. The "uniform abstraction" is to 
> specify security policy in the native terms of the resource being 
> mediated. Files are named as /path/to/some/files/*.html and network 
> resources are named in terms of ports and network addresses reminiscent 
> of firewall rules.
> 
> In contrast, SELinux *does* apply the labeled model to everything. That 
> has the strength that you are dealing with the same abstraction all the 
> time, and the weakness that the mapping from the label abstraction to 
> the stuff that admins and users have to actually deal with is arcane.

It isn't a strawman argument.  I know that AppArmor doesn't try to apply
pathnames to non-files.  Which leads it down the first case of
inconsistent" control - at the end of the day in looking at an AppArmor
policy you can't say anything about how information may have ultimately
flowed in violation of your confidentiality or integrity goals because
you have a lossy abstraction.  Whereas we can convey the same uniform
control over files, network IPC, local IPC, etc and make such
statements.

> > - forcing policy to be written in terms of individual objects and
> > filesystem layout rather than security properties.
> >   
> Name-based access control makes the overt assumption that the name of an 
> object corresponds to its security properties. If your data layout does 
> *not* make such an assumption, then you have some very strange data 
> layout putting highly sensitive objects next to non-sensitive objects.
> 
> Note also that the SELinux restorecon mechanism also makes the 
> assumption that path names correspond to security properties: in fact, 
> that is precisely its function, to take a path name and use it to apply 
> a security property (a label). Naturally I have no objection to 
> inferring a security property from the path name :) I just object to the 
> racy way that restorecon does it, combined with the complaint that 
> AppArmor is wrong for doing exactly the same thing in a different way.

Making that inference when a file is first installed (as from rpm) is
reasonable.  restorecon (the utility) is for the filesystem to the
initial install-time labeling state, which is why it uses the same
mapping.  Making that inference on every access in complete ignorance of
the actual runtime state of the system is what I object to.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-15 11:14                     ` Tetsuo Handa
  2008-04-15 16:32                       ` Casey Schaufler
@ 2008-04-16 19:13                       ` Pavel Machek
  2008-04-17 11:58                         ` Tetsuo Handa
  1 sibling, 1 reply; 74+ messages in thread
From: Pavel Machek @ 2008-04-16 19:13 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: casey, sds, crispin, serue, matthew, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel

Hi!

> > The question of protections on the object named /etc/passwd came
> > up time and time again. The notion that /etc/passwd could be a
> > symlink to /home/smalley/heeheehee really gave evaluators the
> > whillies. As did the chroot environment, where /roots/crispin/etc/passwd
> > could magicly become /etc/passwd.
> Why do people continue speaking symlinks and chroots?
> To avoid the effect of symlinks and chroots, AppArmor and TOMOYO Linux
> derive pathnames from dentry and vfsmount.
> If /etc/passwd was a symlink, the derived pathname will be /home/smalley/heeheehee.
> If accessed from inside a chroot, the derived pathname will be /roots/crispin/etc/passwd.
> 
> It is true that namespace may differ between processes,
> but I think that that is the matter of how to restrict namespace manipulation operations.
> As I said, a system can't survive if namespace is madly manipulated.
> To keep the system workable, /bin/ must be the directory for binary programs,
> /etc/ must be the directory for configuration files, and so on in all namespaces.

Ehm? Where did you get those ideas?

I'm free to name my directories any way I want, and keep config files
in /pavlix_config, thank you... There is even distro that does
something like that, IIRC...

							Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-15 16:32                       ` Casey Schaufler
@ 2008-04-17  7:24                         ` Crispin Cowan
  0 siblings, 0 replies; 74+ messages in thread
From: Crispin Cowan @ 2008-04-17  7:24 UTC (permalink / raw)
  To: casey
  Cc: Tetsuo Handa, sds, serue, matthew, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel

Casey Schaufler wrote:
> --- Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> wrote
>> Casey Schaufler wrote:
>>     
>>> The question of protections on the object named /etc/passwd came
>>> up time and time again. The notion that /etc/passwd could be a
>>> symlink to /home/smalley/heeheehee really gave evaluators the
>>> whillies. As did the chroot environment, where /roots/crispin/etc/passwd
>>> could magicly become /etc/passwd.
>>>       
>> Why do people continue speaking symlinks and chroots?
>>     
> Because on any given Linux system you could have an arbitrarily
> large number of different things that might be accessed by the
> name "/etc/passwd" and a different, but similarly large number
> of names other than "/etc/passwd" that can be used to access them.
>   
But that's not quite true.

"/etc/passwd" can indeed point anywhere, but it can only point to a 
single place at a time. I've alluded to this several times in pointing 
out that labels and names have dualistic many:one and one:many 
relationships to actual files.

This is Tetsuo's point: if you symlink or chroot /etc/shadow to point 
some place strange, then the redirection will be resolved *before* 
AppArmor and TOMOYO consider the security question of whether access 
should be allowed. Therefore, the fact that you re-directed it is 
irrelevant to security.

>> To avoid the effect of symlinks and chroots, AppArmor and TOMOYO Linux
>> derive pathnames from dentry and vfsmount.
>> If /etc/passwd was a symlink, the derived pathname will be
>> /home/smalley/heeheehee.
>> If accessed from inside a chroot, the derived pathname will be
>> /roots/crispin/etc/passwd.
>>     
> Which doesn't hold up under hard links, which I had carefully
> avoided and that both AppArmor and TOMOYO Linux have to place
> restrictions on for the systems to make sense.
>   
Hard links are indeed handled differently, but they are handled. I don't 
know what TOMOYO does. What AppArmor does is exploit the fact that you 
cannot hard link a directory, so the target of a hard link must be a 
file. From there, we can use the    dentry to disambiguate which file. 
So again, even though more than one name points to the inode, the name 
that was actually  used to get to this inode is unique, and we recover 
it and then consider the security question of whether you get to access 
that name.

>> It is true that namespace may differ between processes,
>> but I think that that is the matter of how to restrict namespace manipulation
>> operations.
>> As I said, a system can't survive if namespace is madly manipulated.
>>     
> That's hardly the viewpoint of those who would have every
> user mount their own version of /tmp.
>   
Well, AppArmor and TOMOYO don't do well if the namespace is madly 
manipulated. They remain secure, because they prohibit name space 
manipulations by confined processes. If what you wanted to do was lots 
of  name space manipulations, it makes (at least AppArmor) a poor choice 
for you.

>> It is true that the pathname may change while traversing up the
>> dentry/vfsmount trees.
>> But the change does not occur infinitely.
>> As I said, a system can't survive if files and directories are madly renamed.
>> The possible changes are bounded by the policy.
>>
>> At least, I want people not to speak symlinks and chroots when talking about
>> AppArmor and TOMOYO Linux.
>>     
> The issues with links, symlinks, chroots and mounts in the
> context of a name based access control scheme will always
> need to be addressed, just as the issues of unlabeled filesystems
> and /tmp will have to be in label based scheme.
>   
Agreed. Duality abounds in this space.

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
The Olympic Games: Symbolizing oppressiiion and corruption for over a
hundred years


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-16 16:31                     ` Stephen Smalley
@ 2008-04-17  7:49                       ` Crispin Cowan
  2008-04-17  8:45                         ` Jamie Lokier
  2008-04-17 12:42                         ` Stephen Smalley
  0 siblings, 2 replies; 74+ messages in thread
From: Crispin Cowan @ 2008-04-17  7:49 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev

Stephen Smalley wrote:
> On Mon, 2008-04-14 at 21:59 -0700, Crispin Cowan wrote:
>   
>> Stephen Smalley wrote:
>>     
>>> On Sun, 2008-04-13 at 19:05 -0700, Crispin Cowan wrote:  
>>>       
>>>> Things that pathname-based access control is good at:
>>>>
>>>>     * *System Integrity:* Many of the vital components of a UNIX system
>>>>       are stored in files with Well Known Names such as /etc/shadow,
>>>>       /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
>>>>       contents of the actual data blocks is less important than the
>>>>       integrity of what some random process gets when it asks for these
>>>>       resources by name. Preserving the integrity of what responds to
>>>>       the Well Known Name is thus easier if you restrict access based on
>>>>       the name.
>>>>         
>>> I think some might argue that the integrity of the data in /etc/shadow
>>> and your .ssh files is very important, not just their names.
>>>       
>> I understand how the confidentiality of secrets like the contents of 
>> /etc/shadow and your .ssh files is important, but how can the integrity 
>> of these data objects be important? Back them up if you care ...
>>     
> If you aren't concerned with unauthorized data flow into
> your /etc/shadow and .ssh files, then I think we'll just have to stop
> right there in our discussion, as we evidently don't have a common point
> of reference in what we mean by "security".  Personally I'd be troubled
> if an unauthorized entity can ultimately feed data to such files, even
> if indirectly by tricking a privileged process into conveying the data
> to its ultimate target, a not-so-uncommon pattern.
>   
Of *course* AppArmor protects the integrity of /etc/shadow, and 
unauthorized parties are not permitted to feed data into that file 
unless explicit access is granted. The difference is in how it is done:

    * SELinux marks the inode with a label, and only processes with the
      right permissions can mess with the label.
          o Residual problem: someone could rename the inode and drop a
            new inode into place named "/etc/shadow". SELinux addresses
            this with access control on the parent directory.
    * AppArmor checks the name "/etc/shadow" so that you cannot access
      that name without explicit permission.
          o AppArmor cares about the integrity of what the OS returns
            when you access the name "/etc/shadow" and does not care a
            wit what happens to the inode that was *previously* named
            "/etc/shadow".

Now, without running off into the weeds again, tell me again why I 
should care about the *integrity* of an inode that was *previously* 
known as "/etc/shadow"?

>>>   And
>>> ultimately data integrity requires information flow control to preserve.
>>>   
>>>       
>> You've argued that before, and I've never been convinced. Rather, it 
>> looked a lot like a stretched definition trying really hard to turn 
>> integrity into an information flow problem.The most information flow 
>> that I will buy in the integrity problem is taint analysis of software 
>> inputs; that software should validate inputs before acting on it.
>>     
> In some cases, you can simply prohibit a security-relevant process from
> taking untrustworthy inputs.  Like blocking privileged processes from
> following untrustworthy symlinks to counter malicious symlink attacks or
> from reading any files other than ones created by the admin.  In other
> cases, you need to allow untrustworthy inputs to ultimately flow to the
> security-relevant process, but you want to force them through some kind
> of validation as you say above, which you can do by enforcing a
> processing pipeline that forces the data to go through a subsystem that
> performs validation and/or sanitization before it ever reaches the
> security-relevant process.  That's how integrity is an information flow
> problem.  And this isn't a new idea, btw, it is one that was expressed
> long ago in the Biba model, a variant of which happens to be implemented
> and used in Vista, and is more usefully achievable via Type Enforcement
> since there we can control the processing flow precisely and bind the
> validation/sanitization subsystem to specific code.
>   
Ok. I view the above as a marginal nice-to-have property that I don't 
actually care much about, because it is a large amount of work to manage 
for a small amount of integrity to gain. People who want that should use 
some kind of information flow controlling policy system like SELinux.

IMHO people with that need are a small minority, which is why I think it 
is over-strong to say that integrity "requires" information flow 
control. No it doesn't; the particular form of integrity you are talking 
about requires information flow control, but other forms do not.

>>>  - anything further is misleading as the
>>> server or device won't ensure any finer grained separation for us.
>>>       
>> I don't understand this issue. The enforcement here is t contain the 
>> program executing on the NFS *client* to permit it to only mangle the 
>> parts of the NFS mount that you want it to mangle. That the server won't 
>> enforce anything for you is irrelevant when the threat is the confined 
>> application.
>>     
> Except that you have to consider what is happening on the server too,
> given that the files are visible to local processes there, and what
> happens on all of the clients.
You don't have to consider any such thing when you are *only* concerned 
with confining the impact of the process running on the NFS client.

If you want to concern yourself with funny business coming from other 
clients, then you need to apply policy to those other clients. If you 
want to control funny business happening on the server, then you need to 
apply security policy to the server. But this is all irrelevant to 
secure confinement of the single NFS client process being confined.

> It isn't a strawman argument.  I know that AppArmor doesn't try to apply
> pathnames to non-files.  Which leads it down the first case of
> inconsistent" control - at the end of the day in looking at an AppArmor
> policy you can't say anything about how information may have ultimately
> flowed in violation of your confidentiality or integrity goals because
> you have a lossy abstraction.  Whereas we can convey the same uniform
> control over files, network IPC, local IPC, etc and make such
> statements.
>   
Conversely, at the end of the day you can't say much about what your 
SELinux policy enforces, because you can't understand it :)

Duality again: SELinux policy is easier for machines (semantic 
analyzers) to understand. AppArmor is easier for humans to understand.

>>> - forcing policy to be written in terms of individual objects and
>>> filesystem layout rather than security properties.  
>>>       
>> Note also that the SELinux restorecon mechanism also makes the 
>> assumption that path names correspond to security properties: in fact, 
>> that is precisely its function, to take a path name and use it to apply 
>> a security property (a label). Naturally I have no objection to 
>> inferring a security property from the path name :) I just object to the 
>> racy way that restorecon does it, combined with the complaint that 
>> AppArmor is wrong for doing exactly the same thing in a different way.
>>     
> Making that inference when a file is first installed (as from rpm) is
> reasonable.  restorecon (the utility) is for the filesystem to the
> initial install-time labeling state, which is why it uses the same
> mapping.  Making that inference on every access in complete ignorance of
> the actual runtime state of the system is what I object to.
>   
So associating a security property with a name is ok if you do it 
statically at some arbitrary point in time, but not if you consider it 
at the time of access? WtF? Isn't that a gigantic race condition?

To the contrary, I argue that the *current* name of a file is vastly 
more meaningful for security properties than the name the file had some 
months ago when someone ran restorecon over the file system.

I've said this before too: SELinux works well if your IT systems are 
static. AppArmor works better if your IT systems change, precisely 
because it evaluates the access based on the name at the time of access, 
rather than some historic name the file once had.

Crispin

-- 
Crispin Cowan, Ph.D.               http://crispincowan.com/~crispin
The Olympic Games: Symbolizing oppressiiion and corruption for over a
hundred years


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-17  7:49                       ` Crispin Cowan
@ 2008-04-17  8:45                         ` Jamie Lokier
  2008-04-17 12:42                         ` Stephen Smalley
  1 sibling, 0 replies; 74+ messages in thread
From: Jamie Lokier @ 2008-04-17  8:45 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Stephen Smalley, Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa,
	paul.moore, akpm, linux-kernel, linux-security-module, takedakn,
	linux-fsdevel, netdev

Crispin Cowan wrote:
> Of *course* AppArmor protects the integrity of /etc/shadow, and 
> unauthorized parties are not permitted to feed data into that file 
> unless explicit access is granted. The difference is in how it is done:
> 
>    * SELinux marks the inode with a label, and only processes with the
>      right permissions can mess with the label.
>          o Residual problem: someone could rename the inode and drop a
>            new inode into place named "/etc/shadow". SELinux addresses
>            this with access control on the parent directory.

<small> I have actually hacked a system by renaming /etc/passwd in
this way.  /etc was owned by user "bin", and I had a login as "bin"
due to a misfeature in some program.  So I substituted another
/etc/passwd, and gave myself a root shell. </small>

The trouble with access control on the parent directory is that
occasionally some human accidentally forgets how important that is,
thinking that permissions on the /etc/shadow file are important.

Also *programs* care about a file with that name.  They reference it
by name, apply security decisions based on a process which starts with
that name.  So the name is the most relevant point of communication
between the policy setter and programs which need to be affected.

So I think AppArmor's approach is good here.

>    * AppArmor checks the name "/etc/shadow" so that you cannot access
>      that name without explicit permission.
>          o AppArmor cares about the integrity of what the OS returns
>            when you access the name "/etc/shadow" and does not care a
>            wit what happens to the inode that was *previously* named
>            "/etc/shadow".
> 
> Now, without running off into the weeds again, tell me again why I 
> should care about the *integrity* of an inode that was *previously* 
> known as "/etc/shadow"?

But insufficient here.

If you rename /etc/shadow legitimately, after changing a password,
there might be a program which still has a handle to the _old_ inode
and is still reading it, still comparing a password against its contents.

If policy was entirely name based, so modifications may be possible to
that file after it's renamed from /etc/shadow to /etc/shadow.bak,
_while_ some programs are still reading it (because it was /etc/shadow
when they opened it, and they got swapped for a moment), that's a failure.

So you *should* care about the integrity of an inode that was
previously known as /etc/shadow - at least until you can prove that
nobody is still dependent on it's earlier security properties.  That's
a garbage collection problem.

> So associating a security property with a name is ok if you do it 
> statically at some arbitrary point in time, but not if you consider it 
> at the time of access? WtF? Isn't that a gigantic race condition?

Both are race conditions.

> To the contrary, I argue that the *current* name of a file is vastly 
> more meaningful for security properties than the name the file had some 
> months ago when someone ran restorecon over the file system.

I agree that the current name is meaningful, but it's not watertight
when your systems change.  To avoid unexpected weaknesses, you'll need
to apply the intersection of permissions over a time period, using
name based policy but having it follow renames until you can prove
it's safe to release the following.

-- Jamie

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-16 19:13                       ` Pavel Machek
@ 2008-04-17 11:58                         ` Tetsuo Handa
  2008-04-17 17:46                           ` Pavel Machek
  0 siblings, 1 reply; 74+ messages in thread
From: Tetsuo Handa @ 2008-04-17 11:58 UTC (permalink / raw)
  To: pavel
  Cc: casey, sds, crispin, serue, matthew, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel


Pavel Machek wrote:
> > It is true that namespace may differ between processes,
> > but I think that that is the matter of how to restrict namespace manipulation operations.
> > As I said, a system can't survive if namespace is madly manipulated.
> > To keep the system workable, /bin/ must be the directory for binary programs,
> > /etc/ must be the directory for configuration files, and so on in all namespaces.
> 
> Ehm? Where did you get those ideas?
> 
> I'm free to name my directories any way I want, and keep config files
> in /pavlix_config, thank you... There is even distro that does
> something like that, IIRC...
> 
We can make processes have different namespace by using clone() with CLONE_NEWNS.
But even if some process got a different namespace, it need to follow conventional rules.
Optional files (e.g. /pavlix_config) need not to follow conventional rules.
What I'm talking about is essetial files (e.g. /bin/sh and /etc/passwd).
Can your system continue running even if essetial files are not in place
(like /bin/sh moved to /etc/sh and /etc/passwd moved to /bin/passwd) ?
I don't think so...

Thanks.

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-17  7:49                       ` Crispin Cowan
  2008-04-17  8:45                         ` Jamie Lokier
@ 2008-04-17 12:42                         ` Stephen Smalley
  1 sibling, 0 replies; 74+ messages in thread
From: Stephen Smalley @ 2008-04-17 12:42 UTC (permalink / raw)
  To: Crispin Cowan
  Cc: Serge E. Hallyn, Matthew Wilcox, Tetsuo Handa, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel,
	netdev


On Thu, 2008-04-17 at 00:49 -0700, Crispin Cowan wrote:
> Stephen Smalley wrote:
> > On Mon, 2008-04-14 at 21:59 -0700, Crispin Cowan wrote:
> >   
> >> Stephen Smalley wrote:
> >>     
> >>> On Sun, 2008-04-13 at 19:05 -0700, Crispin Cowan wrote:  
> >>>       
> >>>> Things that pathname-based access control is good at:
> >>>>
> >>>>     * *System Integrity:* Many of the vital components of a UNIX system
> >>>>       are stored in files with Well Known Names such as /etc/shadow,
> >>>>       /var/www/htdocs/index.html and /home/crispin/.ssh/known_hosts. The
> >>>>       contents of the actual data blocks is less important than the
> >>>>       integrity of what some random process gets when it asks for these
> >>>>       resources by name. Preserving the integrity of what responds to
> >>>>       the Well Known Name is thus easier if you restrict access based on
> >>>>       the name.
> >>>>         
> >>> I think some might argue that the integrity of the data in /etc/shadow
> >>> and your .ssh files is very important, not just their names.
> >>>       
> >> I understand how the confidentiality of secrets like the contents of 
> >> /etc/shadow and your .ssh files is important, but how can the integrity 
> >> of these data objects be important? Back them up if you care ...
> >>     
> > If you aren't concerned with unauthorized data flow into
> > your /etc/shadow and .ssh files, then I think we'll just have to stop
> > right there in our discussion, as we evidently don't have a common point
> > of reference in what we mean by "security".  Personally I'd be troubled
> > if an unauthorized entity can ultimately feed data to such files, even
> > if indirectly by tricking a privileged process into conveying the data
> > to its ultimate target, a not-so-uncommon pattern.
> >   
> Of *course* AppArmor protects the integrity of /etc/shadow, and 
> unauthorized parties are not permitted to feed data into that file 
> unless explicit access is granted. The difference is in how it is done:
> 
>     * SELinux marks the inode with a label, and only processes with the
>       right permissions can mess with the label.
>           o Residual problem: someone could rename the inode and drop a
>             new inode into place named "/etc/shadow". SELinux addresses
>             this with access control on the parent directory.
>     * AppArmor checks the name "/etc/shadow" so that you cannot access
>       that name without explicit permission.
>           o AppArmor cares about the integrity of what the OS returns
>             when you access the name "/etc/shadow" and does not care a
>             wit what happens to the inode that was *previously* named
>             "/etc/shadow".
> 
> Now, without running off into the weeds again, tell me again why I 
> should care about the *integrity* of an inode that was *previously* 
> known as "/etc/shadow"?

Jamie responded to this last question, but let me also touch on what you
still seem to be missing above.  You are only looking at the direct
check when a process tries to write to the file, not any chain of events
that led a process to write to the file and how that process and that
data it is writing might have been influenced by that chain of events.
It is precisely there that security problems often arise, and I think
you'll see that if you go looking at past flaws.  Both SELinux and
AppArmor mediate that direct write, but only SELinux allows you to
control the entire chain of events and help protect the process from
unsafe influence, because that relies on information flow control.

> >>>   And
> >>> ultimately data integrity requires information flow control to preserve.
> >>>   
> >>>       
> >> You've argued that before, and I've never been convinced. Rather, it 
> >> looked a lot like a stretched definition trying really hard to turn 
> >> integrity into an information flow problem.The most information flow 
> >> that I will buy in the integrity problem is taint analysis of software 
> >> inputs; that software should validate inputs before acting on it.
> >>     
> > In some cases, you can simply prohibit a security-relevant process from
> > taking untrustworthy inputs.  Like blocking privileged processes from
> > following untrustworthy symlinks to counter malicious symlink attacks or
> > from reading any files other than ones created by the admin.  In other
> > cases, you need to allow untrustworthy inputs to ultimately flow to the
> > security-relevant process, but you want to force them through some kind
> > of validation as you say above, which you can do by enforcing a
> > processing pipeline that forces the data to go through a subsystem that
> > performs validation and/or sanitization before it ever reaches the
> > security-relevant process.  That's how integrity is an information flow
> > problem.  And this isn't a new idea, btw, it is one that was expressed
> > long ago in the Biba model, a variant of which happens to be implemented
> > and used in Vista, and is more usefully achievable via Type Enforcement
> > since there we can control the processing flow precisely and bind the
> > validation/sanitization subsystem to specific code.
> >   
> Ok. I view the above as a marginal nice-to-have property that I don't 
> actually care much about, because it is a large amount of work to manage 
> for a small amount of integrity to gain. People who want that should use 
> some kind of information flow controlling policy system like SELinux.
> 
> IMHO people with that need are a small minority, which is why I think it 
> is over-strong to say that integrity "requires" information flow 
> control. No it doesn't; the particular form of integrity you are talking 
> about requires information flow control, but other forms do not.

I'd be curious to be pointed to any integrity model that doesn't have
the above "marginal" property.  It is rather fundamental - when it comes
to enforcing data integrity, you generally have some transform
(validation, sanitization, formatting, etc) that you want applied to the
data to move it from lower integrity to higher integrity, and there are
three properties you want to hold:
1) The subsystem that performs the transform or validation must be
protected against bypass and tampering,
2) The transformed/validated data at each stage must be protected
against tampering,
3) The transform must be correct.

Only the last property requires verification of the subsystem code; the
first two can be directly enforced by the underlying system.  But this
does require information flow control.

It shows up all the time in the form of protected subsystems.

> >>>  - anything further is misleading as the
> >>> server or device won't ensure any finer grained separation for us.
> >>>       
> >> I don't understand this issue. The enforcement here is t contain the 
> >> program executing on the NFS *client* to permit it to only mangle the 
> >> parts of the NFS mount that you want it to mangle. That the server won't 
> >> enforce anything for you is irrelevant when the threat is the confined 
> >> application.
> >>     
> > Except that you have to consider what is happening on the server too,
> > given that the files are visible to local processes there, and what
> > happens on all of the clients.
> You don't have to consider any such thing when you are *only* concerned 
> with confining the impact of the process running on the NFS client.
> 
> If you want to concern yourself with funny business coming from other 
> clients, then you need to apply policy to those other clients. If you 
> want to control funny business happening on the server, then you need to 
> apply security policy to the server. But this is all irrelevant to 
> secure confinement of the single NFS client process being confined.

It is relevant when looking at the overall threat model and what you
realistically have to take into account from a real adversary who isn't
going to be limited by your strawman threat model.

> > It isn't a strawman argument.  I know that AppArmor doesn't try to apply
> > pathnames to non-files.  Which leads it down the first case of
> > inconsistent" control - at the end of the day in looking at an AppArmor
> > policy you can't say anything about how information may have ultimately
> > flowed in violation of your confidentiality or integrity goals because
> > you have a lossy abstraction.  Whereas we can convey the same uniform
> > control over files, network IPC, local IPC, etc and make such
> > statements.
> >   
> Conversely, at the end of the day you can't say much about what your 
> SELinux policy enforces, because you can't understand it :)
> 
> Duality again: SELinux policy is easier for machines (semantic 
> analyzers) to understand. AppArmor is easier for humans to understand.

SELinux policy can be analyzed.  AppArmor policy gives the appearance of
being easily understood, but is actually meaningless in terms of any
higher level security goal.

> >>> - forcing policy to be written in terms of individual objects and
> >>> filesystem layout rather than security properties.  
> >>>       
> >> Note also that the SELinux restorecon mechanism also makes the 
> >> assumption that path names correspond to security properties: in fact, 
> >> that is precisely its function, to take a path name and use it to apply 
> >> a security property (a label). Naturally I have no objection to 
> >> inferring a security property from the path name :) I just object to the 
> >> racy way that restorecon does it, combined with the complaint that 
> >> AppArmor is wrong for doing exactly the same thing in a different way.
> >>     
> > Making that inference when a file is first installed (as from rpm) is
> > reasonable.  restorecon (the utility) is for the filesystem to the
> > initial install-time labeling state, which is why it uses the same
> > mapping.  Making that inference on every access in complete ignorance of
> > the actual runtime state of the system is what I object to.
> >   
> So associating a security property with a name is ok if you do it 
> statically at some arbitrary point in time, but not if you consider it 
> at the time of access? WtF? Isn't that a gigantic race condition?

I'll try and explain again.  When a file is installed onto the system
initially (and here we are talking about a file from a package, not a
runtime file being created by a user/application), we have no intrinsic
knowledge of its security properties from the installer's security
properties, and thus we must consult an external data source - in our
case, the file contexts configuration or package metadata.  This relies
on secure creation and distribution of the packages in the first place,
of course, which is a dependency that has to be addressed separately.
At that time, using the pathname of the file as a key for looking up the
context in which to install the file makes sense; it is already the key
for the file.

For runtime operation of the system, we want to use the runtime state of
the system to determine the security properties of data created by
applications and users rather than the pathnames.  And thus SELinux
relies on policy in most cases to automatically label files based on the
security properties of the creating process and related objects, and
when needed, on instrumentation in the applications to explicitly label
files based on more specific application knowledge.

Relabeling of any kind is never desirable - the goal is always to label
the data correctly at creation time and preserve that label for the
lifecycle of the object.  Relabeling is a practical accommodation to
incomplete coverage.  It should always be minimized, and as coverage
grows, it becomes less necessary.

restorecon (the utility) is an administrator's way of forcing a given
file or files back to the initial state, presumably based on his
knowledge that they ought to be in that initial state.  It is only for
fixing up mislabeled files, not for runtime enforcement of anything.

restorecond (the daemon) is entirely an accommodation to usability to
deal with the incomplete coverage of policy and/or application support,
although the latter has come a long way since SELinux was first
introduced.  restorecond isn't necessary for using SELinux, but can be
helpful.  It isn't a fundamental part of the SELinux enforcement
mechanism, and we agree that you shouldn't rely on it to enforce a
security property - it is to fix up gaps left by incomplete coverage.

I think perhaps your confusion is that you think we are advocating
restorecond as a fundamental mechanism for enforcement rather than a
practical accommodation for optional use until coverage is more
complete.  It is a practical solution to the problem you posed earlier.

> To the contrary, I argue that the *current* name of a file is vastly 
> more meaningful for security properties than the name the file had some 
> months ago when someone ran restorecon over the file system.
> 
> I've said this before too: SELinux works well if your IT systems are 
> static. AppArmor works better if your IT systems change, precisely 
> because it evaluates the access based on the name at the time of access, 
> rather than some historic name the file once had.

On the contrary, SELinux enables the system to correctly track the real
runtime state of the system over time and to thus enforce the right
security properties over time.

In any event, my main goal here isn't really to argue about pathnames
vs. labels, but rather just to explain the role of information flow
control in protecting integrity and to clarify some misconceptions you
seem to have about SELinux.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-17 11:58                         ` Tetsuo Handa
@ 2008-04-17 17:46                           ` Pavel Machek
  2008-04-18 13:21                             ` Serge E. Hallyn
  0 siblings, 1 reply; 74+ messages in thread
From: Pavel Machek @ 2008-04-17 17:46 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: casey, sds, crispin, serue, matthew, paul.moore, akpm,
	linux-kernel, linux-security-module, takedakn, linux-fsdevel

> 
> Pavel Machek wrote:
> > > It is true that namespace may differ between processes,
> > > but I think that that is the matter of how to restrict namespace manipulation operations.
> > > As I said, a system can't survive if namespace is madly manipulated.
> > > To keep the system workable, /bin/ must be the directory for binary programs,
> > > /etc/ must be the directory for configuration files, and so on in all namespaces.
> > 
> > Ehm? Where did you get those ideas?
> > 
> > I'm free to name my directories any way I want, and keep config files
> > in /pavlix_config, thank you... There is even distro that does
> > something like that, IIRC...
> > 
> We can make processes have different namespace by using clone() with CLONE_NEWNS.
> But even if some process got a different namespace, it need to follow conventional rules.
> Optional files (e.g. /pavlix_config) need not to follow conventional rules.
> What I'm talking about is essetial files (e.g. /bin/sh and
> /etc/passwd).

Why would I need to follow rules?

> Can your system continue running even if essetial files are not in place
> (like /bin/sh moved to /etc/sh and /etc/passwd moved to /bin/passwd) ?

Why not? They are just a names, they can be changed as log as you
change them everywhere. Which is rather easy for small
systems... think openembedded.
									Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

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

* Re: [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO.
  2008-04-17 17:46                           ` Pavel Machek
@ 2008-04-18 13:21                             ` Serge E. Hallyn
  0 siblings, 0 replies; 74+ messages in thread
From: Serge E. Hallyn @ 2008-04-18 13:21 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Tetsuo Handa, casey, sds, crispin, serue, matthew, paul.moore,
	akpm, linux-kernel, linux-security-module, takedakn,
	linux-fsdevel

Quoting Pavel Machek (pavel@ucw.cz):
> > 
> > Pavel Machek wrote:
> > > > It is true that namespace may differ between processes,
> > > > but I think that that is the matter of how to restrict namespace manipulation operations.
> > > > As I said, a system can't survive if namespace is madly manipulated.
> > > > To keep the system workable, /bin/ must be the directory for binary programs,
> > > > /etc/ must be the directory for configuration files, and so on in all namespaces.
> > > 
> > > Ehm? Where did you get those ideas?
> > > 
> > > I'm free to name my directories any way I want, and keep config files
> > > in /pavlix_config, thank you... There is even distro that does
> > > something like that, IIRC...
> > > 
> > We can make processes have different namespace by using clone() with CLONE_NEWNS.
> > But even if some process got a different namespace, it need to follow conventional rules.
> > Optional files (e.g. /pavlix_config) need not to follow conventional rules.
> > What I'm talking about is essetial files (e.g. /bin/sh and
> > /etc/passwd).
> 
> Why would I need to follow rules?
> 
> > Can your system continue running even if essetial files are not in place
> > (like /bin/sh moved to /etc/sh and /etc/passwd moved to /bin/passwd) ?
> 
> Why not? They are just a names, they can be changed as log as you
> change them everywhere. Which is rather easy for small
> systems... think openembedded.
> 									Pavel

This is getting silly - if you change them "everywhere" then you can
change them in your security profiles too :)

Anyway this thread has degenerated again.

Crispin, thanks for posting those use cases.

Now the next step IMO is for someone (Tetsuo?) to take one of those use
cases and run through each of the four (was it 4?) proposed methods for
handling the pathnames.  One paragraph describing how the use case in
general would be handled, then a list of shortcomings for the solution.

-serge

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

* Re: [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released
  2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
                   ` (29 preceding siblings ...)
  2008-04-04 12:23 ` [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO Tetsuo Handa
@ 2008-04-24  5:28 ` Toshiharu Harada
  2008-04-24 14:48   ` Serge E. Hallyn
  30 siblings, 1 reply; 74+ messages in thread
From: Toshiharu Harada @ 2008-04-24  5:28 UTC (permalink / raw)
  To: linux-kernel; +Cc: Tetsuo Handa, akpm, linux-security-module, Kentaro Takeda

After examining the feedback to our latest patches and some personal
suggestions sent via e-mail, we came to realize we took totally
wrong way to propose another access control implementation.
I apologize for that and thank people for comments and suggestions
from the bottom of my heart.

The following Jonathan's LWN article is a real *great*, in-depth
explanation of TOMOYO Linux and pathname-based security history.

http://lwn.net/Articles/277833/

As was described in the article, I am planning to take the Stephen's
"option 2" approach.

> 2) Submit patches to add new security hooks to the callers where the
> vfsmount is already available (some have suggested moving the existing
> security_inode hooks to the callers, but that would cause problems for
> SELinux as I've posted elsewhere, so adding new hooks is preferable, and
> then SELinux can just default to the dummy functions for those new
> hooks).

This approach should not require large impacts on VFS and filesystems.
The work is in progress. Our next patch will include:
- new LSM hooks for TOMOYO Linux
- the smallest set of hooks (to save reviewers' time)

BTW, we have attended the ELC2008 and listened to the Andrew's keynote.
Yes, we were encouraged deeply, again. We, TOMOYO Linux developers,
are serious about getting our code into the mainline, we will
continue working like Casey (and "ask him" in case of trouble).

We will be back (with our new patches). :)

Cheers,
Toshiharu Harada
NTT DATA CORPORATION

On 4/4/2008 9:22 PM, Tetsuo Handa wrote:
> What is TOMOYO Linux for?
> 
>   It is userland applications' charge to keep processes under control.
>   This includes that userland applications restrict what programs are
>   permitted to execute and what files are permitted to read/write
>   by the application.
>   Thus, the kernel does whatever asked by the userland applications.
> 
>   But userland applications often make mistakes such as buffer overflow,
>   OS command injection, directory traversal etc.
>   As a result, they can't always keep processes under control.
> 
>   TOMOYO Linux is developed to take partial charge of keeping processes
>   under control, by teaching the kernel what programs are
>   permitted to execute and what files are permitted to read/write
>   by the application and letting the kernel check it once again.
> 
> Can TOMOYO Linux coexist with SELinux, SMACK, AppArmor etc. ?
> 
>   Yes. TOMOYO Linux 1.x is not using LSM because I want don't want to disable
>   different access control implementations.
> 
>   TOMOYO Linux is good at dealing with the subject's request chains,
>   while LSM is good at dealing with object's life-cycle.
> 
> What are new features of this release?
> 
>   This release reinforced execution parameter checks.
> 
>   Introduced execute handler mechanism.
> 
>     The usage of TOMOYO Linux is that understand what programs are executed and
>     what files are opened by individual applications and let the kernel
>     enforce them. Thus, assuming that all programs needed by individual
>     applications are known by the time of enforcing, attempts to execute
>     not ever observed application (e.g. /bin/sh) are considered to be attacks.
> 
>     Some shellcode (an exploit code used to execute /bin/sh) attempts to
>     execute /bin/sh until the execution succeeds. One of such shellcodes is
>     Samba's trans2open exploit. It attempts to execute /bin/sh from infinite
>     loop. As a result, just rejecting execute request of /bin/sh triggers
>     CPU power consumption problem (all CPU powers are eaten by the shellcode's
>     infinite loop).
> 
>     But by using execute handler, you can run different programs instead of
>     just rejecting execute request to redirect the process to somewhere else.
> 
>     This redirection mechanism is useful for deploying on demand honey pot.
>     You can provide regular service in peacetime, and you can redirect
>     the attacker trying to start /bin/sh to honey pot in wartime.
> 
>     Also, since the fact that a program which is different from the requested
>     one is executed by execute handler is not notified to the caller process,
>     you can use execute handler as a transparent validation interface
>     like "Web Application Firewall" or "AntiVirus". You can monitor
>     parameters passed to execve() and stdio to drop unwanted parameters or
>     reject the execution of the program requested by the caller process.
> 
>   Introduced "struct linux_bprm" checks.
> 
>     Proper codes tends to setting up argv[] and envp[] when executing
>     a new program. But shellcodes tend to not setting up argv[] and envp[]
>     when executed using buffer overflows.
>     Thus, it became possible to require specific argv[] and envp[] values
>     when executing a program.
> 
>   Introduced environment variable names checks.
> 
>     Some environment variables (e.g. LD_PRELOAD) are used by attackers to make
>     the application behave differently.
>     Thus, it became possible to restrict acceptable environment variable's
>     names passed to execve().
> 
>   And various usability enhancement like more detailed access logs and
>   policy management by non root user.
> 
> Is TOMOYO Linux architecture independent?
> 
>   Yes. I think TOMOYO Linux doesn't contain architecture dependent code.
>   TOMOYO Linux touches only surface of the kernel code.
> 
> Are older kernels supported?
> 
>   Yes. From 2.4.30 to 2.4.36 and from 2.6.11 to 2.6.25-rc7 are supported.
>   Also, various distributions' latest kernels are supported.
> 
> Where's the web page?
> 
>   http://elinux.org/TomoyoLinux
>   http://tomoyo.sourceforge.jp/wiki-e/?WhatIs
>   http://sourceforge.jp/projects/tomoyo/document/fosdem2008.pdf
> 
>   TOMOYO Linux is compact and suits well to PC servers and embedded systems.
>   There is a TOMOYO Linux presentation at CELF Embedded Linux Conference 2008
>   held in California from April 15th to the 17th.
> 
> Thank you.


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

* Re: [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released
  2008-04-24  5:28 ` [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Toshiharu Harada
@ 2008-04-24 14:48   ` Serge E. Hallyn
  0 siblings, 0 replies; 74+ messages in thread
From: Serge E. Hallyn @ 2008-04-24 14:48 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: linux-kernel, Tetsuo Handa, akpm, linux-security-module, Kentaro Takeda

Quoting Toshiharu Harada (haradats@nttdata.co.jp):
> After examining the feedback to our latest patches and some personal
> suggestions sent via e-mail, we came to realize we took totally
> wrong way to propose another access control implementation.
> I apologize for that and thank people for comments and suggestions
> from the bottom of my heart.
>
> The following Jonathan's LWN article is a real *great*, in-depth
> explanation of TOMOYO Linux and pathname-based security history.
>
> http://lwn.net/Articles/277833/
>
> As was described in the article, I am planning to take the Stephen's
> "option 2" approach.
>
>> 2) Submit patches to add new security hooks to the callers where the
>> vfsmount is already available (some have suggested moving the existing
>> security_inode hooks to the callers, but that would cause problems for
>> SELinux as I've posted elsewhere, so adding new hooks is preferable, and
>> then SELinux can just default to the dummy functions for those new
>> hooks).

Sounds good.  I suspect to satisfy some critics you'll still need to go
through the exercise of showing that one of TOMOYO's (valid) security
goals can't be met with the other options, but for me personally just
following this approach suffices.

thanks,
-serge

> This approach should not require large impacts on VFS and filesystems.
> The work is in progress. Our next patch will include:
> - new LSM hooks for TOMOYO Linux
> - the smallest set of hooks (to save reviewers' time)
>
> BTW, we have attended the ELC2008 and listened to the Andrew's keynote.
> Yes, we were encouraged deeply, again. We, TOMOYO Linux developers,
> are serious about getting our code into the mainline, we will
> continue working like Casey (and "ask him" in case of trouble).
>
> We will be back (with our new patches). :)
>
> Cheers,
> Toshiharu Harada
> NTT DATA CORPORATION
>
> On 4/4/2008 9:22 PM, Tetsuo Handa wrote:
>> What is TOMOYO Linux for?
>>   It is userland applications' charge to keep processes under control.
>>   This includes that userland applications restrict what programs are
>>   permitted to execute and what files are permitted to read/write
>>   by the application.
>>   Thus, the kernel does whatever asked by the userland applications.
>>   But userland applications often make mistakes such as buffer overflow,
>>   OS command injection, directory traversal etc.
>>   As a result, they can't always keep processes under control.
>>   TOMOYO Linux is developed to take partial charge of keeping processes
>>   under control, by teaching the kernel what programs are
>>   permitted to execute and what files are permitted to read/write
>>   by the application and letting the kernel check it once again.
>> Can TOMOYO Linux coexist with SELinux, SMACK, AppArmor etc. ?
>>   Yes. TOMOYO Linux 1.x is not using LSM because I want don't want to 
>> disable
>>   different access control implementations.
>>   TOMOYO Linux is good at dealing with the subject's request chains,
>>   while LSM is good at dealing with object's life-cycle.
>> What are new features of this release?
>>   This release reinforced execution parameter checks.
>>   Introduced execute handler mechanism.
>>     The usage of TOMOYO Linux is that understand what programs are 
>> executed and
>>     what files are opened by individual applications and let the kernel
>>     enforce them. Thus, assuming that all programs needed by individual
>>     applications are known by the time of enforcing, attempts to execute
>>     not ever observed application (e.g. /bin/sh) are considered to be 
>> attacks.
>>     Some shellcode (an exploit code used to execute /bin/sh) attempts to
>>     execute /bin/sh until the execution succeeds. One of such shellcodes 
>> is
>>     Samba's trans2open exploit. It attempts to execute /bin/sh from 
>> infinite
>>     loop. As a result, just rejecting execute request of /bin/sh triggers
>>     CPU power consumption problem (all CPU powers are eaten by the 
>> shellcode's
>>     infinite loop).
>>     But by using execute handler, you can run different programs instead 
>> of
>>     just rejecting execute request to redirect the process to somewhere 
>> else.
>>     This redirection mechanism is useful for deploying on demand honey 
>> pot.
>>     You can provide regular service in peacetime, and you can redirect
>>     the attacker trying to start /bin/sh to honey pot in wartime.
>>     Also, since the fact that a program which is different from the 
>> requested
>>     one is executed by execute handler is not notified to the caller 
>> process,
>>     you can use execute handler as a transparent validation interface
>>     like "Web Application Firewall" or "AntiVirus". You can monitor
>>     parameters passed to execve() and stdio to drop unwanted parameters or
>>     reject the execution of the program requested by the caller process.
>>   Introduced "struct linux_bprm" checks.
>>     Proper codes tends to setting up argv[] and envp[] when executing
>>     a new program. But shellcodes tend to not setting up argv[] and envp[]
>>     when executed using buffer overflows.
>>     Thus, it became possible to require specific argv[] and envp[] values
>>     when executing a program.
>>   Introduced environment variable names checks.
>>     Some environment variables (e.g. LD_PRELOAD) are used by attackers to 
>> make
>>     the application behave differently.
>>     Thus, it became possible to restrict acceptable environment variable's
>>     names passed to execve().
>>   And various usability enhancement like more detailed access logs and
>>   policy management by non root user.
>> Is TOMOYO Linux architecture independent?
>>   Yes. I think TOMOYO Linux doesn't contain architecture dependent code.
>>   TOMOYO Linux touches only surface of the kernel code.
>> Are older kernels supported?
>>   Yes. From 2.4.30 to 2.4.36 and from 2.6.11 to 2.6.25-rc7 are supported.
>>   Also, various distributions' latest kernels are supported.
>> Where's the web page?
>>   http://elinux.org/TomoyoLinux
>>   http://tomoyo.sourceforge.jp/wiki-e/?WhatIs
>>   http://sourceforge.jp/projects/tomoyo/document/fosdem2008.pdf
>>   TOMOYO Linux is compact and suits well to PC servers and embedded 
>> systems.
>>   There is a TOMOYO Linux presentation at CELF Embedded Linux Conference 
>> 2008
>>   held in California from April 15th to the 17th.
>> Thank you.
>
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-security-module" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

end of thread, other threads:[~2008-04-24 14:48 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-04-04 12:22 [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 01/30] TOMOYO Linux documentation Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA and TOMOYO Tetsuo Handa
2008-04-04 15:29   ` Daniel Walker
2008-04-07 13:56     ` [TOMOYO #7 02/30] Internal functions prototypes for SAKURA andTOMOYO Tetsuo Handa
2008-04-07 15:24       ` Daniel Walker
2008-04-04 12:22 ` [TOMOYO #7 03/30] Constants for /proc/ccs/ interface Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 04/30] Prototypes of realpath Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 05/30] External functions prototypes for SAKURA Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 06/30] External functions prototypes for TOMOYO Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 07/30] Some wrapper functions for socket operation Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 08/30] Some of permission checks from VFS helper functions Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 09/30] Access control part of tamper-proof device filesystem Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 10/30] Common functions for SAKURA and TOMOYO Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 11/30] /proc/ccs/ interface for policy management Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 12/30] Memory and pathname management functions Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 13/30] mount restriction part Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 14/30] Shadow mount prevention part Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 15/30] Automatic bind port selection control part Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 16/30] Unmount restriction part Tetsuo Handa
2008-04-04 12:22 ` [TOMOYO #7 17/30] chroot " Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 18/30] pivot_root " Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 19/30] Auditing functions for TOMOYO Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 20/30] Socket operation restriction part Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 21/30] Capability " Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 22/30] Conditional ACL support functions Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 23/30] argvrestriction part Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 24/30] File operation restriction part Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 25/30] Signal " Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 26/30] Domain transition handler Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 27/30] Environment variable restriction part Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 28/30] Filesystem part of tamper-proof device filesystem Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 29/30] Kconfig and Makefile Tetsuo Handa
2008-04-04 12:23 ` [TOMOYO #7 30/30] Hooks for SAKURA and TOMOYO Tetsuo Handa
2008-04-04 16:29   ` Daniel Walker
2008-04-07 13:56     ` Tetsuo Handa
2008-04-07 15:39       ` Daniel Walker
2008-04-07 15:40   ` Paul Moore
2008-04-07 22:57     ` Casey Schaufler
2008-04-09  8:37     ` Toshiharu Harada
2008-04-09 12:49       ` Stephen Smalley
2008-04-10  5:57         ` Toshiharu Harada
2008-04-10 12:51           ` Stephen Smalley
2008-04-11 11:48             ` Toshiharu Harada
2008-04-09 13:11       ` Matthew Wilcox
2008-04-09 13:26         ` Stephen Smalley
2008-04-11 14:12         ` Tetsuo Handa
2008-04-11 14:30           ` Matthew Wilcox
2008-04-12 11:33             ` Tetsuo Handa
2008-04-13 16:36             ` Serge E. Hallyn
2008-04-14  2:05               ` Crispin Cowan
2008-04-14 14:17                 ` Stephen Smalley
2008-04-14 17:05                   ` Casey Schaufler
2008-04-15 11:14                     ` Tetsuo Handa
2008-04-15 16:32                       ` Casey Schaufler
2008-04-17  7:24                         ` Crispin Cowan
2008-04-16 19:13                       ` Pavel Machek
2008-04-17 11:58                         ` Tetsuo Handa
2008-04-17 17:46                           ` Pavel Machek
2008-04-18 13:21                             ` Serge E. Hallyn
2008-04-15  4:59                   ` Crispin Cowan
2008-04-16 16:31                     ` Stephen Smalley
2008-04-17  7:49                       ` Crispin Cowan
2008-04-17  8:45                         ` Jamie Lokier
2008-04-17 12:42                         ` Stephen Smalley
2008-04-15 13:00                 ` Toshiharu Harada
2008-04-14  1:41             ` Crispin Cowan
2008-04-14 13:48               ` Matthew Wilcox
2008-04-15  3:21                 ` Crispin Cowan
2008-04-15  4:57                   ` Al Viro
2008-04-09 13:22       ` Serge E. Hallyn
2008-04-11  3:57         ` Toshiharu Harada
2008-04-24  5:28 ` [TOMOYO #7 00/30] TOMOYO Linux 1.6.0 released Toshiharu Harada
2008-04-24 14:48   ` Serge E. Hallyn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).