"TOMOYO Linux" is our work in the field of security enhanced Linux.
This is the repeated post of second submission of TOMOYO Linux
( http://lkml.org/lkml/2007/8/24/116 ).
Unfortunately it was just before the Kernel Summit.
We could not receive any suggestions about our codes.
Before this posting, we changed the program to use
securityfs (Kyle's comment) and re-generated patches
with the current git tree.
When we posted our first proposal to LKML, TOMOYO Linux's MAC was
limited to file access control. Now TOMOYO Linux has access control
functionality not only for files but also for networking, signal
transmission and namespace manipulation and we got the source code
cleaned-up.
Patches consist of four types.
* [TOMOYO 01/15]: Mandatory modifications against standard kernel.
* [TOMOYO 02-13/15]: LSM implementation of TOMOYO Linux.
* [TOMOYO 14/15]: Optional modifications against standard kernel.
* [TOMOYO 15/15]: Makefile and Kconfig .
<<What you can do with TOMOYO Linux.>>
The fundamental concept of TOMOYO Linux is "tracking process
invocation history".
The "struct task_struct"->security member holds a pointer to the
"process invocation history". Thus, every process (the kernel,
/sbin/init process and any children/descendant of /sbin/init) knows
its "process invocation history" (or ancestors). Since every process
knows its ancestors, TOMOYO Linux can enforce access control over all
processes.
TOMOYO Linux splits domains using "process invocation history" and the
process transits to a different domain whenever execution of a program
(i.e. do_execve()) is requested. By transiting to a different domain
whenever execution of a program is requested, each domain will have
the minimal permissions that are essential for processes in that
domain to do their roles.
You don't need to define domains beforehand. TOMOYO Linux kernel will
automatically define new domains whenever execution of a program is
requested, and the process will automatically transit to the new
domain. (If the process's domain is in enforcing mode, TOMOYO Linux
kernel will not define new domains to avoid memory consumption attack.)
TOMOYO Linux can restrict the following requests on a per-a-domain basis:
* opening files
* communicating via PF_INET sockets
* sending signals
TOMOYO Linux can also restrict the following namespace manipulation requests.
* mounting filesystems
* unmounting filesystems
* using pivot_root
<<Example>>
The tree below shows a part of domain transitions generated using
Debian Etch. The domain a process is in is determined based on the
process's "process invocation history". Each domain has permissions
(or ACL), and the behavior (or requests shown above) of a process is
restricted by the ACL of the domain that the process is in.
<kernel>
+ /sbin/init
+ /etc/init.d/rc
+ /etc/init.d/apache2
+ /usr/bin/env
+ /usr/sbin/apache2ctl
+ /usr/sbin/apache2
You can assign different access control modes (or profiles) on a
per-a-domain basis. Thus, you can enforce access control partially or
entirely. Also, by assigning "learning mode" to a domain, ACL that are
requested by processes in that domain are automatically (i.e. in
real-time) accumulated. The following ACL are an excerpt from
/usr/sbin/apache2 domain generated using "learning mode".
4 /etc/apache2/apache2.conf
4 /var/www/apache2-default/index.html
allow_create /var/run/apache2.pid
allow_unlink /var/run/apache2.pid
allow_network TCP bind 192.168.1.135 80
allow_network TCP listen 192.168.1.135 80
allow_network TCP accept 192.168.1.1 2389
The above ACL allows Apache to do the following behavior.
* Opening /etc/apache2/apache2.conf and
/var/www/apache2-default/index.html for reading.
* Creating and deleting /var/run/apache2.pid .
* Binding to local address (IP = 192.168.1.135, port = 80).
* Listening at local address (IP = 192.168.1.135, port = 80).
* Accepting from remote address (IP = 192.168.1.1, port = 2389).
You may use wildcards for pathnames, ranges for IP addresses and port
numbers, groups of pathnames and IP addresses for flexible definition.
<<Features of TOMOYO Linux.>>
TOMOYO Linux is an implementation of MAC, but you can use TOMOYO Linux
not only for MAC, but also to analyze a system's behavior, since
TOMOYO Linux can accumulate access requests raised by applications
sorted by each "process invocation history".
TOMOYO Linux and AppArmor are alike from the point of view of pathname
based access control, but TOMOYO Linux has differences in the
following points:
* TOMOYO Linux can apply access control over the whole process
(from the execution of /sbin/init at the startup procedure,
till the power failure at the shutdown procedure).
* TOMOYO Linux can apply access control not only over files but
also over networking, signals, namespace manipulations.
* TOMOYO Linux can accumulate ACL in real-time using
"learning mode".
* TOMOYO Linux allows the administrator to switch the access
control mode on a per-domain and per-functionality basis.
* TOMOYO Linux allows the administrator to judge (grant/reject)
requests which that violated ACL manually while operating in
"enforcing mode" without once rejecting these requests.
* TOMOYO Linux supports conditional ACL (e.g. owner of
process/files etc.).
Documents about installing and experiencing TOMOYO Linux are available
at http://tomoyo.sourceforge.jp/en/2.1.x/ . Please try TOMOYO Linux.
Feedbacks are most welcome.
<<Related links>>
OLS BoF material: http://sourceforge.jp/projects/tomoyo/document/ols2007-tomoyo-20070629.pdf
Previous submissions: http://lkml.org/lkml/2007/6/13/58 , http://lkml.org/lkml/2007/6/14/55, http://lkml.org/lkml/2007/8/24/116
Kentaro Takeda
NTT DATA CORPORATION
Common functions for TOMOYO Linux.
TOMOYO Linux uses /sys/kernel/security/tomoyo interface for configuration.
/sys/kernel/security/tomoyo/domain_policy is the domain-based access policy.
Access control list for files, networks, argv[0] and signal is
stored in domain_policy.
/sys/kernel/security/tomoyo/system_policy is the system-wide access policy.
Access control list for mount, umount and pivot_root is
stored in system_policy.
/sys/kernel/security/tomoyo/exception_policy is the other settings such as
globally readable files, domain transition configurations
or pre-defined patterned pathnames.
/sys/kernel/security/tomoyo/profile has some profiles, which configure
the access control level of TOMOYO Linux. A profile is assigned to a domain.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/common.c | 2406 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 2406 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/common.c 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,2406 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO Linux.
+ */
+
+#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/security.h>
+#include "realpath.h"
+#include "tomoyo.h"
+
+#define MAX_ACCEPT_ENTRY 2048
+
+static int tmy_read_control(struct file *file,
+ char __user *buffer,
+ const int buffer_len);
+
+/************************* VARIABLES *************************/
+
+/* /sbin/init started? */
+int sbin_init_started;
+
+static struct {
+ const char *keyword;
+ unsigned int current_value;
+ const unsigned int max_value;
+} tmy_control[TMY_MAX_CONTROL_INDEX] = {
+ [TMY_COMMENT] = { "COMMENT", 0, 0 },
+ [TMY_MAC_FOR_FILE] = { "MAC_FOR_FILE", 0, 3 },
+ [TMY_MAC_FOR_ARGV0] = { "MAC_FOR_ARGV0", 0, 3 },
+ [TMY_MAC_FOR_NETWORK] = { "MAC_FOR_NETWORK", 0, 3 },
+ [TMY_MAC_FOR_SIGNAL] = { "MAC_FOR_SIGNAL", 0, 3 },
+ [TMY_DENY_CONCEAL_MOUNT] = { "DENY_CONCEAL_MOUNT", 0, 3 },
+ [TMY_RESTRICT_MOUNT] = { "RESTRICT_MOUNT", 0, 3 },
+ [TMY_RESTRICT_UMOUNT] = { "RESTRICT_UNMOUNT", 0, 3 },
+ [TMY_RESTRICT_PIVOT_ROOT] = { "RESTRICT_PIVOT_ROOT", 0, 3 },
+ [TMY_MAX_ACCEPT_ENTRY] =
+ { "MAX_ACCEPT_ENTRY", MAX_ACCEPT_ENTRY, INT_MAX },
+ [TMY_AUDIT_GRANT] = { "AUDIT_GRANT", 0, 1 },
+ [TMY_AUDIT_REJECT] = { "AUDIT_REJECT", 1, 1 },
+ [TMY_ALLOW_ENFORCE_GRACE] = { "ALLOW_ENFORCE_GRACE", 0, 1 },
+};
+
+struct profile {
+ unsigned int value[TMY_MAX_CONTROL_INDEX];
+ const struct path_info *comment;
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/* Is the current process running as root? */
+static int tmy_is_root(void)
+{
+ return !current->uid && !current->euid;
+}
+
+/**
+ * tmy_normalize_line - make a line tidy.
+ * @buffer: the line to make tidy.
+ *
+ * All tokens (such as pathnames) used in TOMOYO Linux contains
+ * only ASCII printable (i.e. 0x21-0x7E) range characters.
+ * This allows policy files and auditing logs split monotonically
+ * using space (i.e. ' ') and new line (i.e. '\n') characters.
+ *
+ * Remove leading and trailing non ASCII printable chracters and
+ * replace one or more non ASCII printable chracters with single space.
+ */
+static void tmy_normalize_line(unsigned char *buffer)
+{
+ unsigned char *sp = buffer;
+ unsigned char *dp = buffer;
+ int first = 1;
+
+ while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+ sp++;
+
+ while (*sp) {
+ if (!first)
+ *dp++ = ' ';
+ first = 0;
+ while (*sp > 0x20 && *sp < 0x7F)
+ *dp++ = *sp++;
+ while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+ sp++;
+ }
+
+ *dp = '\0';
+}
+
+/* Is @c the first letter of "\ooo" expression? */
+static int tmy_char_is_0to3(const unsigned char c)
+{
+ return c >= '0' && c <= '3';
+}
+
+/* Is @c the second or third letter of "\ooo" expression? */
+static int tmy_char_is_0to7(const unsigned char c)
+{
+ return c >= '0' && c <= '7';
+}
+
+/* Is @c a decimal letter? */
+static int tmy_char_is_0to9(const unsigned char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+/* Is @c a hexadecimal letter? */
+static int tmy_char_is_hex(const unsigned char c)
+{
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')
+ || (c >= 'a' && c <= 'f');
+}
+
+/* Is @c an alphabet letter? */
+static int tmy_char_is_alpha(const unsigned char c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/* Convert \ooo style expression to a byte. */
+static unsigned char tmy_str2chr(const unsigned char c,
+ const unsigned char d,
+ const unsigned char e)
+{
+ return (((unsigned char) (c - '0')) << 6) +
+ (((unsigned char) (d - '0')) << 3) +
+ (((unsigned char) (e - '0')));
+}
+
+/* Does the @src starts with @find? */
+static int tmy_strstarts(char **src, const char *find)
+{
+ const int len = strlen(find);
+ char *tmp = *src;
+
+ if (strncmp(tmp, find, len) == 0) {
+ tmp += len;
+ *src = tmp;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_correct_path - validate a pathname.
+ * @filename: the pathname to check
+ * @start_type: 1 if the pathname must start with '/'
+ * -1 if the pathname must not start with '/'
+ * 0 if it does not matter
+ * @pattern_type: 1 if the pathname must contain patterns
+ * -1 if the pathname must not contain patterns
+ * 0 if it does not matter
+ * @end_type: 1 if the pathname must end with '/'
+ * -1 if the pathname must not end with '/'
+ * 0 if it does not matter
+ * @function: string to display if the @filename is invalid
+ *
+ * Returns nonzero if the pathname is valid.
+ * Returns zero otherwise.
+ *
+ * Check whether the given pathname follows the naming rules.
+ */
+int tmy_correct_path(const char *filename,
+ const int start_type,
+ const int pattern_type,
+ const int end_type,
+ const char *function)
+{
+ int contains_pattern = 0;
+ char c;
+ char d;
+ 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 == '\\') {
+ unsigned char f;
+ 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)
+ goto out; /* Must not contain pattern */
+ contains_pattern = 1;
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ d = *filename++;
+ if (!tmy_char_is_0to7(d))
+ goto out;
+ e = *filename++;
+ if (!tmy_char_is_0to7(e))
+ goto out;
+ f = tmy_str2chr(c, d, e);
+ if (f && (f <= 0x20 || f >= 0x7F))
+ /* valid \ooo expression */
+ continue;
+ }
+ goto out;
+ } else if (c <= 0x20 || c >= 0x7F)
+ goto out;
+ }
+
+ if (pattern_type == 1) { /* Must contain pattern */
+ if (!contains_pattern)
+ goto out;
+ }
+
+ return 1;
+
+out: ;
+ printk(KERN_DEBUG "%s: Invalid pathname '%s'\n",
+ function, original_filename);
+ return 0;
+}
+
+/**
+ * tmy_is_correct_domain - validate a domainname.
+ * @domainname: the domainname to check.
+ * @function: string to display if the @domainname is invalid.
+ *
+ * Returns nonzero if the domainname is valid.
+ * Returns zero otherwise.
+ *
+ * Check whether the given domainname follows the naming rules.
+ */
+int tmy_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 || !tmy_strstarts((char **) &domainname, TMY_ROOT_NAME))
+ goto out;
+
+ if (!*domainname)
+ return 1;
+
+ do {
+ /* 0x20 is a domainname separator. */
+ if (*domainname++ != ' ')
+ goto out;
+ /* Must starts with '/'. */
+ if (*domainname++ != '/')
+ goto out;
+ while ((c = *domainname) != '\0' && c != ' ') {
+ domainname++;
+ if (c == '\\') {
+ unsigned char f;
+ switch ((c = *domainname++)) {
+ case '\\': /* "\\" */
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ d = *domainname++;
+ if (!tmy_char_is_0to7(d))
+ goto out;
+ e = *domainname++;
+ if (!tmy_char_is_0to7(e))
+ goto out;
+ f = tmy_str2chr(c, d, e);
+ if (f && (f <= 0x20 || f >= 0x7F))
+ continue;
+ }
+ goto out;
+ } else if (c < 0x20 || c >= 0x7F)
+ /* 0x20 is a domainname separator. */
+ goto out;
+ }
+ } while (*domainname);
+
+ return 1;
+
+out: ;
+ printk(KERN_DEBUG "%s: Invalid domainname '%s'\n",
+ function, org_domainname);
+ return 0;
+}
+
+/**
+ * tmy_path_depth - evaluate the number of '/' characters in a token.
+ * @pathname: the token to evaluate.
+ *
+ * Each '/' character but the trailing '/' scores 2.
+ * The trailing '/' scores 1.
+ *
+ * If @pathname ends with '/', the return value is an odd integer.
+ * If @pathname does not end with '/', the return value is an even integer.
+ */
+static int tmy_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;
+}
+
+/**
+ * tmy_const_part_length - calculate the constant part in a token.
+ * @filename: the token to calculate.
+ *
+ * Returns leading length of @filename that can be compared using strncmp().
+ */
+static int tmy_const_part_length(const char *filename)
+{
+ int len = 0;
+
+ if (filename) {
+ char c;
+
+ while ((c = *filename++) != '\0') {
+ if (c != '\\') {
+ len++;
+ continue;
+ }
+ switch (c = *filename++) {
+ case '\\': /* "\\" */
+ len += 2;
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ if (!tmy_char_is_0to7(*filename++))
+ break;
+ if (!tmy_char_is_0to7(*filename++))
+ break;
+ len += 4;
+ continue;
+ }
+ break;
+ }
+ }
+
+ return len;
+}
+
+/**
+ * tmy_fill_path_info - fill "struct path_info" entry.
+ * @ptr: pointer to "struct path_info".
+ *
+ * Caller stores a token in "struct path_info"->name .
+ * This function will fill rest of "struct path_info" members.
+ */
+void tmy_fill_path_info(struct path_info *ptr)
+{
+ const char *name = ptr->name;
+ const int len = strlen(name);
+ ptr->total_len = len;
+ ptr->const_len = tmy_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 = tmy_path_depth(name);
+}
+
+/**
+ * tmy_file_match2 - internal function for tmy_path_match().
+ * @filename: start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern: start address of the pattern.
+ * @pattern_end: end address of the pattern.
+ *
+ * Handle all patterns other than '\-' pattern.
+ * Returns nonzero if matches.
+ * Returns zero otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ * @pattern does not contain '\-'.
+ */
+static int tmy_file_match2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ while (filename < filename_end && pattern < pattern_end) {
+ char c;
+ int i;
+ int j;
+
+ if (*pattern != '\\') {
+ if (*filename++ != *pattern++)
+ goto out;
+ continue;
+ }
+
+ c = *filename;
+ pattern++;
+
+ switch (*pattern) {
+ case '?':
+ if (c == '/')
+ goto out;
+ if (c != '\\')
+ break;
+ /* filename[0] != '\0' */
+ c = filename[1];
+ if (c == '\\')
+ filename++;
+ else if (tmy_char_is_0to3(c) &&
+ /* filename[1] != '\0' */
+ tmy_char_is_0to7(filename[2]) &&
+ /* filename[2] != '\0' */
+ tmy_char_is_0to7(filename[3]))
+ /* filename[3] != '\0' */
+ filename += 3;
+ /*
+ * Why not "filename += 4;" here
+ * because it processed 4 (i.e. "\ooo") bytes?
+ * - Because "filename++;" is done
+ * after "break;".
+ */
+ else
+ goto out;
+ break;
+ case '\\':
+ if (c != '\\')
+ goto out;
+ /* filename[0] != '\0' */
+ if (*++filename != '\\')
+ goto out;
+ break;
+ case '+':
+ if (!tmy_char_is_0to9(c))
+ goto out;
+ break;
+ case 'x':
+ if (!tmy_char_is_hex(c))
+ goto out;
+ break;
+ case 'a':
+ if (!tmy_char_is_alpha(c))
+ goto out;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ if (c != '\\')
+ goto out;
+ /* filename[0] != '\0' */
+ c = filename[1];
+ if (!tmy_char_is_0to3(c) || c != *pattern)
+ goto out;
+ /* filename[1] != '\0' */
+ c = filename[2];
+ /* pattern[0] != '\0' */
+ if (!tmy_char_is_0to7(c) || c != pattern[1])
+ goto out;
+ /* filename[2] != '\0' */
+ c = filename[3];
+ /* pattern[1] != '\0' */
+ if (!tmy_char_is_0to7(c) || c != pattern[2])
+ goto out;
+ /* filename[3] != '\0' */
+ filename += 3;
+ /* pattern[2] != '\0' */
+ pattern += 2;
+ break;
+ case '*':
+ case '@':
+ for (i = 0; i <= filename_end - filename; i++) {
+ if (tmy_file_match2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return 1;
+ c = filename[i];
+ if (c == '.' && *pattern == '@')
+ break;
+ if (c != '\\')
+ continue;
+ /* filename[i] != '\0' */
+ c = filename[i + 1];
+ if (c == '\\')
+ /* filename[i + 1] != '\0' */
+ i++;
+ else if (tmy_char_is_0to3(c) &&
+ /* filename[i + 1] != '\0' */
+ tmy_char_is_0to7(filename[i + 2]) &&
+ /* filename[i + 2] != '\0' */
+ tmy_char_is_0to7(filename[i + 3]))
+ /* filename[i + 3] != '\0' */
+ i += 3;
+ else
+ break; /* Bad pattern. */
+ }
+ goto out;
+ default:
+ j = 0;
+ /*
+ * If j == 0, for() loop does nothing.
+ * So I can use while() without checking first letter.
+ */
+ c = *pattern;
+ if (c == '$')
+ while (tmy_char_is_0to9(filename[j]))
+ j++;
+ else if (c == 'X')
+ while (tmy_char_is_hex(filename[j]))
+ j++;
+ else if (c == 'A')
+ while (tmy_char_is_alpha(filename[j]))
+ j++;
+ for (i = 1; i <= j; i++)
+ if (tmy_file_match2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return 1;
+ goto out; /* Not matched or bad pattern. */
+ }
+ filename++; /* filename[0] != '\0' */
+ pattern++; /* pattern[0] != '\0' */
+ }
+
+ /* Skip trailing "\*" and "\@" patterns. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' ||
+ *(pattern + 1) == '@'))
+ pattern += 2;
+
+ /* If both are at ending position, they are equals. */
+ return (filename == filename_end && pattern == pattern_end);
+out: ;
+ return 0;
+}
+
+/**
+ * tmy_file_match - internal function for tmy_path_match().
+ * @filename: start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern: start address of the pattern.
+ * @pattern_end: end address of the pattern.
+ *
+ * Handle patterns containing '\-' pattern.
+ * Returns nonzero if matches.
+ * Returns zero otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ */
+static int tmy_file_match(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ const char *pattern_start = pattern;
+ int first = 1;
+ int result;
+
+ while (pattern < pattern_end - 1) {
+ /* Split at "\-" pattern. */
+ if (*pattern++ != '\\' || *pattern++ != '-')
+ continue;
+ result = tmy_file_match2(filename,
+ filename_end,
+ pattern_start,
+ pattern - 2);
+ /* If before "\-" pattern, invert the result. */
+ if (first)
+ result = !result;
+ /*
+ * If not matched before first "\-" pattern, return 0.
+ * If matched after first "\-" pattern, return 0.
+ */
+ if (result)
+ return 0;
+ first = 0;
+ pattern_start = pattern;
+ }
+
+ result = tmy_file_match2(filename,
+ filename_end, pattern_start, pattern_end);
+ /* If after first "\-" pattern, invert the result. */
+ return first ? result : !result;
+}
+
+/**
+ * tmy_path_match - do a pattern matching.
+ * @pathname0: pointer to a token to compare.
+ * @pattern0: pointer to a pattern to compare.
+ *
+ * Returns nonzero if @pathname0 matches to @pattern0 .
+ * Returns zero otherwise.
+ *
+ *
+ * Check whether the given token matches to the given pattern.
+ *
+ * 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.
+ */
+int tmy_path_match(const struct path_info *pathname0,
+ const struct path_info *pattern0)
+{
+ const char *pathname = pathname0->name;
+ const char *pattern = pattern0->name;
+ const int len = pattern0->const_len;
+
+ /* If it doesn't contains patterns, I can use tmy_pathcmp() */
+ if (!pattern0->is_patterned)
+ return !tmy_pathcmp(pathname0, pattern0);
+ /* Check the depth of directory. */
+ if (pathname0->depth != pattern0->depth)
+ return 0;
+ /* Use strncmp() for constant part. */
+ if (strncmp(pathname, pattern, len))
+ return 0;
+
+ pathname += len;
+ pattern += len;
+
+ /* Split at '/' character, and compare. */
+ while (*pathname && *pattern) {
+ const char *pathname_delimiter = strchr(pathname, '/');
+ const char *pattern_delimiter = strchr(pattern, '/');
+
+ if (!pathname_delimiter)
+ pathname_delimiter = strchr(pathname, '\0');
+ if (!pattern_delimiter)
+ pattern_delimiter = strchr(pattern, '\0');
+ if (!tmy_file_match(pathname,
+ pathname_delimiter,
+ pattern,
+ pattern_delimiter))
+ return 0;
+
+ pathname = *pathname_delimiter ?
+ pathname_delimiter + 1 :
+ pathname_delimiter;
+ pattern = *pattern_delimiter ?
+ pattern_delimiter + 1 :
+ pattern_delimiter;
+ }
+
+ /* Skip trailing "\*" and "\@" patterns. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' ||
+ *(pattern + 1) == '@'))
+ pattern += 2;
+
+ /* If both are at '\0' position, they are equals. */
+ return (!*pathname && !*pattern);
+}
+
+/**
+ * tmy_io_printf - transactional printf() for "struct io_buffer".
+ * @head: pointer to "struct io_buffer".
+ * @fmt: format strings for printf().
+ *
+ * Returns zero on success.
+ * Returns nonzero otherwise.
+ *
+ * Transactional printf() to "struct io_buffer" structure.
+ * snprintf() will truncate, but tmy_io_printf() won't.
+ * This is needed because dumping partially truncated tokens
+ * is not acceptable for TOMOYO Linux.
+ */
+int tmy_io_printf(struct 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 -ENOMEM;
+
+ va_start(args, fmt);
+ len = vsnprintf(head->read_buf + pos, size, fmt, args);
+ va_end(args);
+
+ if (pos + len >= head->readbuf_size)
+ return -ENOMEM;
+
+ head->read_avail += len;
+
+ return 0;
+}
+
+/**
+ * tmy_get_exe - get realpath of current process.
+ *
+ * Returns realpath(3) of current process on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must tmy_free()
+ * if this function didn't return NULL.
+ */
+const char *tmy_get_exe(void)
+{
+ struct vm_area_struct *vma;
+
+ if (!current->mm)
+ return NULL;
+ for (vma = current->mm->mmap; vma; vma = vma->vm_next)
+ if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file)
+ return tmy_realpath_dentry(vma->vm_file->f_dentry,
+ vma->vm_file->f_vfsmnt);
+
+ return NULL;
+}
+
+/************************* DOMAIN POLICY HANDLER *************************/
+
+/**
+ * tmy_flags - get flags of given access control.
+ * @index: index to retrieve flags.
+ *
+ * Returns current value of given access control.
+ */
+unsigned int tmy_flags(const unsigned int index)
+{
+ const u8 profile =
+ TMY_SECURITY->domain->profile;
+
+ /* All operations might sleep. See tmy_supervisor(). */
+ might_sleep();
+
+ return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
+ && profile_ptr[profile] ?
+ profile_ptr[profile]->value[index] :
+ 0;
+}
+
+/**
+ * tmy_audit_grant - get flags of auditing grant logs.
+ *
+ * Returns current value of auditing grant log flags.
+ */
+unsigned int tmy_audit_grant(void)
+{
+ return tmy_flags(TMY_AUDIT_GRANT);
+}
+
+/**
+ * tmy_audit_reject - get flags of auditing reject logs.
+ *
+ * Returns current value of auditing reject log flags.
+ */
+unsigned int tmy_audit_reject(void)
+{
+ return tmy_flags(TMY_AUDIT_REJECT);
+}
+
+/**
+ * tmy_enforce - check whether the given access control is enforce mode.
+ * @index: index to check.
+ *
+ * Returns nonzero if given access control is enforce mode.
+ * Returns zero otherwise.
+ */
+u8 tmy_enforce(const unsigned int index)
+{
+ return tmy_flags(index) == 3;
+}
+
+/**
+ * tmy_accept - check whether the given access control is learning mode.
+ * @index: index to check.
+ * @domain: pointer to "struct domain_info" to examine.
+ *
+ * Returns nonzero if given access control is learning mode
+ * and quota has not exceeded.
+ * Returns zero otherwise.
+ *
+ * This is a safeguard for "learning mode", for appending entries
+ * without limit dulls the system response and consumes much memory.
+ */
+u8 tmy_accept(const unsigned int index,
+ struct domain_info * const domain)
+{
+ unsigned int count = 0;
+ struct acl_info *ptr;
+ if (tmy_flags(index) != 1)
+ return 0;
+ if (!domain)
+ return 1;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted)
+ count++;
+ }
+ /* If there are so many entries, don't append any more. */
+ if (count < tmy_flags(TMY_MAX_ACCEPT_ENTRY))
+ return 1;
+ if (!domain->quota_warned) {
+ domain->quota_warned = 1;
+ printk(KERN_INFO
+ "TOMOYO-WARNING: Domain '%s' has so many ACLs "
+ "to hold. Stopped learning mode.\n",
+ domain->domainname->name);
+ }
+ return 0;
+}
+
+/* Create a new profile. */
+static struct profile *tmy_find_new_profile(const unsigned int profile)
+{
+ static DECLARE_MUTEX(profile_lock);
+ struct profile *ptr = NULL;
+
+ down(&profile_lock);
+
+ ptr = profile_ptr[profile];
+ if (profile < TMY_MAX_PROFILES && ptr == NULL) {
+ ptr = tmy_alloc_element(sizeof(*ptr));
+ if (ptr != NULL) {
+ int i;
+ for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
+ ptr->value[i] = tmy_control[i].current_value;
+ mb(); /* Instead of using spinlock. */
+ profile_ptr[profile] = ptr;
+ }
+ }
+
+ up(&profile_lock);
+
+ return ptr;
+}
+
+/* Loading policy done? */
+static int profile_loaded;
+
+/* Update profile values. */
+static int tmy_set_profile(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ unsigned int i;
+ unsigned int value;
+ char *cp;
+ struct profile *profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ /* If profile index is not given, assume 0. */
+ i = simple_strtoul(data, &cp, 10);
+ if (data != cp) {
+ if (*cp != '-')
+ return -EINVAL;
+ data = cp + 1;
+ }
+
+ profile = tmy_find_new_profile(i);
+ if (!profile)
+ return -EINVAL;
+ cp = strchr(data, '=');
+ if (!cp)
+ return -EINVAL;
+
+ *cp = '\0';
+ profile_loaded = 1;
+ tmy_update_counter(TMY_UPDATE_PROFILE);
+ if (strcmp(data, tmy_control[TMY_COMMENT].keyword) == 0) {
+ profile->comment = tmy_save_name(cp + 1);
+ return 0;
+ }
+
+ if (sscanf(cp + 1, "%u", &value) != 1)
+ return -EINVAL;
+
+ for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
+ if (strcmp(data, tmy_control[i].keyword))
+ continue;
+ if (value > tmy_control[i].max_value)
+ value = tmy_control[i].max_value;
+ profile->value[i] = value;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* Read profile values. */
+static int tmy_read_profile(struct io_buffer *head)
+{
+ int step;
+
+ if (head->read_eof)
+ return 0;
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (step = head->read_step;
+ step < TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX;
+ step++) {
+ const int i = step / TMY_MAX_CONTROL_INDEX;
+ const int j = step % TMY_MAX_CONTROL_INDEX;
+ const struct profile *profile = profile_ptr[i];
+
+ head->read_step = step;
+ if (!profile)
+ continue;
+ if (j != TMY_COMMENT)
+ goto non_comment;
+ if (tmy_io_printf(head, "%u-%s=%s\n",
+ i, tmy_control[TMY_COMMENT].keyword,
+ profile->comment ?
+ profile->comment->name : ""))
+ break;
+ goto comment_ok;
+non_comment: ;
+ if (tmy_io_printf(head, "%u-%s=%u\n",
+ i, tmy_control[j].keyword,
+ profile->value[j]))
+ break;
+comment_ok: ;
+ }
+
+ if (step == TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX)
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/************************* POLICY MANAGER HANDLER *************************/
+
+struct policy_manager_entry {
+ struct policy_manager_entry *next;
+ const struct path_info *manager;
+ u8 is_domain;
+ u8 is_deleted;
+};
+
+static struct policy_manager_entry *policy_manager_list;
+
+/* Update manager list. */
+static int tmy_add_manager_entry(const char *manager, const u8 is_delete)
+{
+ struct policy_manager_entry *new_entry;
+ struct policy_manager_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_manager;
+ int error = -ENOMEM;
+
+ u8 is_domain = 0;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (tmy_is_domain_def(manager)) {
+ if (!tmy_is_correct_domain(manager, __FUNCTION__))
+ return -EINVAL;
+ is_domain = 1;
+ } else {
+ if (!tmy_correct_path(manager, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+ }
+
+ saved_manager = tmy_save_name(manager);
+ if (saved_manager == NULL)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (ptr->manager == saved_manager) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (new_entry == NULL)
+ goto out;
+ new_entry->manager = saved_manager;
+ new_entry->is_domain = is_domain;
+ mb(); /* Instead of using spinlock. */
+ ptr = policy_manager_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ policy_manager_list = new_entry;
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ if (!error)
+ tmy_update_counter(TMY_UPDATE_MANAGER);
+ return error;
+}
+
+/* Update manager list. */
+static int tmy_add_manager_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ u8 is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ return tmy_add_manager_entry(data, is_delete);
+}
+
+/* Read manager list.*/
+static int tmy_read_manager_policy(struct io_buffer *head)
+{
+ struct policy_manager_entry *ptr = head->read_var2;
+ if (head->read_eof)
+ return 0;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (!ptr)
+ ptr = policy_manager_list;
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, "%s\n", ptr->manager->name))
+ break;
+ ptr = ptr->next;
+ }
+ if (!ptr)
+ head->read_eof = 1;
+ return 0;
+}
+
+/**
+ * tmy_is_policy_manager - check whether modifying policy is permitted.
+ *
+ * Returns nonzero if modifying policy is permitted.
+ * Returns zero otherwise.
+ *
+ * Only programs or domains registers as manager are permitted to modify
+ * policy via /sys/kernel/security/tomoyo/ interface.
+ * This function checks whether the current process is a policy manager.
+ * But policy manager is not the only processes that can modify policy;
+ * other process are permitted to add policy
+ * if their domains are assigned a profile for learning mode.
+ */
+static int tmy_is_policy_manager(void)
+{
+ struct policy_manager_entry *ptr;
+ const char *exe;
+ const struct path_info *domainname =
+ TMY_SECURITY->domain->domainname;
+
+ /* Everyone can modify policy before /sbin/init starts. */
+ if (!sbin_init_started)
+ return 1;
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ ptr->is_domain &&
+ !tmy_pathcmp(domainname, ptr->manager))
+ return 1;
+ }
+
+ exe = tmy_get_exe();
+ if (!exe)
+ return 0;
+
+ for (ptr = policy_manager_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ !ptr->is_domain &&
+ !strcmp(exe, ptr->manager->name))
+ break;
+ }
+
+ if (!ptr) { /* Reduce error messages. */
+ static pid_t last_pid;
+ const pid_t pid = current->pid;
+ if (last_pid != pid) {
+ printk(KERN_INFO
+ "%s is not permitted to update policies.\n",
+ exe);
+ last_pid = pid;
+ }
+ }
+
+ tmy_free(exe);
+ return ptr ? 1 : 0;
+}
+
+/************************* DOMAIN POLICY HANDLER *************************/
+
+/* Update domain policy. */
+static int tmy_add_domain_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ char *cp;
+ const struct condition_list *cond = NULL;
+ struct domain_info *domain = head->write_var1;
+ u8 is_delete = 0;
+ u8 is_select = 0;
+ u8 is_undelete = 0;
+ unsigned int profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ else if (tmy_strstarts(&data, TMY_SELECT))
+ is_select = 1;
+ else if (tmy_strstarts(&data, TMY_UNDELETE))
+ is_undelete = 1;
+
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+ if (tmy_is_domain_def(data)) {
+ if (is_delete) {
+ tmy_delete_domain(data);
+ domain = NULL;
+ } else if (is_select)
+ domain = tmy_find_domain(data);
+ else if (is_undelete)
+ domain = tmy_undelete_domain(data);
+ else
+ domain = tmy_new_domain(data, 0);
+ head->write_var1 = domain;
+ return 0;
+ }
+
+ if (!domain)
+ return -EINVAL;
+
+ if (sscanf(data, TMY_USE_PROFILE "%u", &profile) == 1 &&
+ profile < TMY_MAX_PROFILES) {
+ if (profile_ptr[profile] || !sbin_init_started)
+ domain->profile = (u8) profile;
+ return 0;
+ }
+ cp = tmy_find_condition_part(data);
+ if (cp) {
+ cond = tmy_assign_condition(cp);
+ if (!cond)
+ return -EINVAL;
+ }
+ if (tmy_strstarts(&data, TMY_ALLOW_NETWORK))
+ return tmy_add_network_policy(data, domain, cond, is_delete);
+ else if (tmy_strstarts(&data, TMY_ALLOW_ARGV0))
+ return tmy_add_argv0_policy(data, domain, cond, is_delete);
+ else if (tmy_strstarts(&data, TMY_ALLOW_SIGNAL))
+ return tmy_add_signal_policy(data, domain, cond, is_delete);
+ else
+ return tmy_add_file_policy(data, domain, cond, is_delete);
+
+ return -EINVAL;
+}
+
+/* Dump file's open ACL. */
+static inline int print_file_rwx_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct file_acl *ptr2 = (struct file_acl *) ptr;
+ const unsigned char b = ptr2->u_is_group;
+
+ if (tmy_io_printf(head, "%d %s%s",
+ ptr2->perm, b ? "@" : "",
+ b ? ptr2->u.group->group_name->name :
+ ptr2->u.filename->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump argv[0]'s ACL. */
+static inline int print_argv0_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct argv0_acl *ptr2 = (struct argv0_acl *) ptr;
+
+ if (tmy_io_printf(head, TMY_ALLOW_ARGV0 "%s %s",
+ ptr2->filename->name, ptr2->argv0->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump network's ACL. */
+static inline int print_network_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct net_acl *ptr2 = (struct net_acl *) ptr;
+ u8 record_type = ptr2->record_type;
+ u32 min_address;
+ u32 max_address;
+ char buf[64];
+ const u16 *min_address_ptr;
+ const u16 *max_address_ptr;
+ u16 min_port;
+ u16 max_port;
+
+ if (tmy_io_printf(head, TMY_ALLOW_NETWORK "%s ",
+ tmy_network2keyword(ptr2->operation_type)))
+ goto out;
+ if (record_type != TMY_TYPE_ADDRESS_GROUP)
+ goto non_address_group;
+
+ if (tmy_io_printf(head, "@%s", ptr2->u.group->group_name->name))
+ goto out;
+ goto print_port;
+
+non_address_group: ;
+ if (record_type != TMY_TYPE_IPv4)
+ goto ipv6_address;
+
+ min_address = ptr2->u.ipv4.min;
+ max_address = ptr2->u.ipv4.max;
+ if (tmy_io_printf(head, NIPQUAD_FMT, HIPQUAD(min_address)))
+ goto out;
+ if (min_address != max_address &&
+ tmy_io_printf(head, "-" NIPQUAD_FMT, HIPQUAD(max_address)))
+ goto out;
+ goto print_port;
+
+ipv6_address: ;
+ min_address_ptr = ptr2->u.ipv6.min;
+ max_address_ptr = ptr2->u.ipv6.max;
+ tmy_print_ipv6(buf, sizeof(buf), min_address_ptr);
+ if (tmy_io_printf(head, "%s", buf))
+ goto out;
+ if (memcmp(min_address_ptr, max_address_ptr, 16)) {
+ tmy_print_ipv6(buf, sizeof(buf), max_address_ptr);
+ if (tmy_io_printf(head, "-%s", buf))
+ goto out;
+ }
+
+print_port: ;
+ min_port = ptr2->min_port;
+ max_port = ptr2->max_port;
+ if (tmy_io_printf(head, " %u", min_port))
+ goto out;
+ if (min_port != max_port && tmy_io_printf(head, "-%u", max_port))
+ goto out;
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/* Dump signal's ACL. */
+static inline int print_signal_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ struct signal_acl *ptr2 = (struct signal_acl *) ptr;
+
+ if (tmy_io_printf(head, TMY_ALLOW_SIGNAL "%u %s",
+ ptr2->sig, ptr2->domainname->name))
+ return -ENOMEM;
+ return 0;
+}
+
+/* Dump file's non-open ACL. */
+static inline int print_file_other_acl(struct io_buffer *head,
+ struct acl_info *ptr)
+{
+ const u8 acl_type = ptr->type;
+ const char *keyword = tmy_acltype2keyword(acl_type);
+
+ if (!keyword)
+ return 0; /* This must not happen. */
+
+ if (tmy_acltype2paths(acl_type) == 2) {
+ struct double_acl *ptr2 = (struct double_acl *) ptr;
+ const u8 b1 = ptr2->u1_is_group;
+ const u8 b2 = ptr2->u2_is_group;
+
+ if (tmy_io_printf(head,
+ "allow_%s %s%s %s%s",
+ keyword,
+ b1 ? "@" : "",
+ b1 ? ptr2->u1.group1->group_name->name :
+ ptr2->u1.filename1->name,
+ b2 ? "@" : "",
+ b2 ? ptr2->u2.group2->group_name->name :
+ ptr2->u2.filename2->name))
+ return -ENOMEM;
+ } else {
+ struct single_acl *ptr2 = (struct single_acl *) ptr;
+ const u8 b = ptr2->u_is_group;
+
+ if (tmy_io_printf(head,
+ "allow_%s %s%s",
+ keyword,
+ b ? "@" : "",
+ b ? ptr2->u.group->group_name->name :
+ ptr2->u.filename->name))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Read domain policy. */
+static int tmy_read_domain_policy(struct io_buffer *head)
+{
+ struct domain_info *domain = head->read_var1;
+
+ if (head->read_eof)
+ return 0;
+ switch (head->read_step) {
+ case 0: break; /* Resume from printing domainname. */
+ case 1: goto step1; /* Resume from printing profile number. */
+ case 2: goto step2; /* Resume from printing ACL entries. */
+ case 3: goto step3; /* Resume from printing trailing '\n'. */
+ default: return -EINVAL;
+ }
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ struct acl_info *ptr;
+ if (domain->is_deleted)
+ continue;
+ head->read_var1 = domain;
+ head->read_var2 = NULL;
+ head->read_step = 1;
+
+step1: ;
+ if (tmy_io_printf(head,
+ "%s\n" TMY_USE_PROFILE "%u\n%s\n",
+ domain->domainname->name,
+ domain->profile,
+ domain->quota_warned ?
+ "quota_exceeded\n" : ""))
+ break;
+ head->read_var2 = domain->first_acl_ptr;
+ head->read_step = 2;
+
+step2: ;
+ for (ptr = head->read_var2; ptr; ptr = ptr->next) {
+ const u8 acl_type = ptr->type;
+ const int pos = head->read_avail;
+ head->read_var2 = ptr;
+ if (ptr->is_deleted)
+ continue;
+ if (acl_type == TMY_TYPE_FILE_ACL) {
+ if (print_file_rwx_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_ARGV0_ACL) {
+ if (print_argv0_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_IP_NETWORK_ACL) {
+ if (print_network_acl(head, ptr))
+ goto print_acl_rollback;
+ } else if (acl_type == TMY_TYPE_SIGNAL_ACL) {
+ if (print_signal_acl(head, ptr))
+ goto print_acl_rollback;
+ } else
+ if (print_file_other_acl(head, ptr))
+ goto print_acl_rollback;
+ if (tmy_dump_condition(head, ptr->cond)) {
+print_acl_rollback: ;
+ head->read_avail = pos;
+ break;
+ }
+ }
+ if (ptr)
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 3;
+
+step3: ;
+ if (tmy_io_printf(head, "\n"))
+ break;
+ }
+
+ if (!domain)
+ head->read_eof = 1;
+ return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_domain_profile(struct io_buffer *head)
+{
+ struct domain_info *domain;
+
+ if (head->read_eof)
+ return 0;
+
+ if (head->read_step == 0) {
+ head->read_var1 = &KERNEL_DOMAIN;
+ head->read_step = 1;
+ }
+ if (!tmy_is_root())
+ return -EPERM;
+
+ for (domain = head->read_var1; domain; domain = domain->next) {
+ if (domain->is_deleted)
+ continue;
+ head->read_var1 = domain;
+ if (tmy_io_printf(head, "%u %s\n",
+ domain->profile, domain->domainname->name))
+ break;
+ }
+ if (!domain)
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Set PID to report. Non manager process is permitted to call this function. */
+static int tmy_write_pid(struct io_buffer *head)
+{
+ head->read_step = (int) simple_strtoul(head->write_buf, NULL, 10);
+ head->read_eof = 0;
+ return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_pid(struct io_buffer *head)
+{
+ const int pid = head->read_step;
+ struct task_struct *p;
+ struct domain_info *domain = NULL;
+
+ /* If PID is not set via tmy_write_pid(), do nothing. */
+ if (head->read_avail || head->read_eof)
+ return 0;
+
+ /***** CRITICAL SECTION START *****/
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(pid);
+ if (p) {
+ /* "struct task_struct"->security is not NULL. */
+ domain = ((struct tmy_security *) p->security)->domain;
+ }
+ read_unlock(&tasklist_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (domain)
+ tmy_io_printf(head, "%d %u %s",
+ pid, domain->profile, domain->domainname->name);
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Update profile values for domains. */
+static int tmy_update_domain_profile(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ char *cp = strchr(data, ' ');
+ struct domain_info *domain;
+ unsigned int profile;
+
+ if (!tmy_is_root())
+ return -EPERM;
+ if (!cp)
+ return -EINVAL;
+
+ *cp = '\0';
+ domain = tmy_find_domain(cp + 1);
+ profile = simple_strtoul(data, NULL, 10);
+ if (domain && profile < TMY_MAX_PROFILES &&
+ (profile_ptr[profile] || !sbin_init_started))
+ domain->profile = (u8) profile;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+ return 0;
+}
+
+/************************* SYSTEM POLICY HANDLER *************************/
+
+/* Update system policy. */
+static int tmy_add_system_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ u8 is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ if (tmy_strstarts(&data, TMY_ALLOW_MOUNT))
+ return tmy_add_mount_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_DENY_UNMOUNT))
+ return tmy_add_no_umount_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ALLOW_PIVOT_ROOT))
+ return tmy_add_pivot_root_policy(data, is_delete);
+
+ return -EINVAL;
+}
+
+/* Read system policy. */
+static int tmy_read_system_policy(struct io_buffer *head)
+{
+ if (head->read_eof)
+ return 0;
+
+ switch (head->read_step) {
+ case 0:
+ if (!tmy_is_root())
+ return -EPERM;
+ head->read_var2 = NULL; head->read_step = 1;
+ case 1:
+ if (tmy_read_mount_policy(head))
+ break;
+ head->read_var2 = NULL; head->read_step = 2;
+ case 2:
+ if (tmy_read_no_umount_policy(head))
+ break;
+ head->read_var2 = NULL; head->read_step = 3;
+ case 3:
+ if (tmy_read_pivot_root_policy(head))
+ break;
+ head->read_eof = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/************************* EXCEPTION POLICY HANDLER *************************/
+/* Update exception policy. */
+static int tmy_add_exception_policy(struct io_buffer *head)
+{
+ char *data = head->write_buf;
+ u8 is_delete = 0;
+
+ if (!tmy_is_root())
+ return -EPERM;
+
+ tmy_update_counter(TMY_UPDATE_EXCEPTIONPOLICY);
+
+ if (tmy_strstarts(&data, TMY_DELETE))
+ is_delete = 1;
+ if (tmy_strstarts(&data, TMY_KEEP_DOMAIN))
+ return tmy_add_domain_keeper_policy(data, 0, is_delete);
+ if (tmy_strstarts(&data, TMY_NO_KEEP_DOMAIN))
+ return tmy_add_domain_keeper_policy(data, 1, is_delete);
+ if (tmy_strstarts(&data, TMY_INITIALIZE_DOMAIN))
+ return tmy_add_domain_initializer_policy(data, 0, is_delete);
+ if (tmy_strstarts(&data, TMY_NO_INITIALIZE_DOMAIN))
+ return tmy_add_domain_initializer_policy(data, 1, is_delete);
+ if (tmy_strstarts(&data, TMY_ALIAS))
+ return tmy_add_alias_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_AGGREGATOR))
+ return tmy_add_aggregator_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ALLOW_READ))
+ return tmy_add_globally_readable_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_FILE_PATTERN))
+ return tmy_add_pattern_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_PATH_GROUP))
+ return tmy_add_group_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_DENY_REWRITE))
+ return tmy_add_no_rewrite_policy(data, is_delete);
+ if (tmy_strstarts(&data, TMY_ADDRESS_GROUP))
+ return tmy_add_addr_group_policy(data, is_delete);
+
+ return -EINVAL;
+}
+
+/* Read exception policy. */
+static int tmy_read_exception_policy(struct io_buffer *head)
+{
+ if (head->read_eof)
+ return 0;
+
+ switch (head->read_step) {
+ case 0:
+ if (!tmy_is_root())
+ return -EPERM;
+ head->read_var2 = NULL;
+ head->read_step = 1;
+ case 1:
+ if (tmy_read_domain_keeper_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 2;
+ case 2:
+ if (tmy_read_globally_readable_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 3;
+ case 3:
+ if (tmy_read_domain_initializer_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 4;
+ case 4:
+ if (tmy_read_alias_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 5;
+ case 5:
+ if (tmy_read_aggregator_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 6;
+ case 6:
+ if (tmy_read_pattern_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 7;
+ case 7:
+ if (tmy_read_no_rewrite_policy(head))
+ break;
+ head->read_var2 = NULL;
+ head->read_step = 8;
+ case 8:
+ if (tmy_read_group_policy(head))
+ break;
+ head->read_var1 = NULL;
+ head->read_var2 = NULL;
+ head->read_step = 9;
+ case 9:
+ if (tmy_read_addr_group_policy(head))
+ break;
+ head->read_eof = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/************************* POLICY LOADER *************************/
+
+/**
+ * tmy_load_policy - load policy and activate access control.
+ * @filename: program to be executed.
+ *
+ * This function calls /sbin/tomoyo-init using call_usermodehelper()
+ * to load policy
+ * if "execution of /sbin/init is requested" and "/sbin/tomoyo-init exists".
+ */
+void tmy_load_policy(const char *filename)
+{
+ if (sbin_init_started)
+ return;
+ if (strcmp(filename, "/sbin/init") != 0)
+ return;
+
+ /*
+ * Don't activate MAC if the path '/sbin/tomoyo-init' doesn't exist.
+ * If initrd.img includes /sbin/init but real-root-dev has not
+ * mounted on / yet, activating MAC will block the system
+ * since policies are not loaded yet.
+ * So let do_execve() call this function everytime.
+ */
+ if (!profile_loaded) {
+ const char *tmy_loader = "/sbin/tomoyo-init";
+ struct nameidata nd;
+ char *argv[2];
+ char *envp[3];
+
+ if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
+ printk("TOMOYO: Not activating Mandatory Access Control"
+ " now since %s doesn't exist.\n", tmy_loader);
+ return;
+ }
+ path_release(&nd);
+ argv[0] = (char *) tmy_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);
+ }
+
+ printk(KERN_INFO "TOMOYO: %s 2007/08/21\n", TOMOYO_VERSION_CODE);
+
+ if (!profile_loaded)
+ panic("TOMOYO: No profiles loaded.\n");
+
+ printk(KERN_INFO "TOMOYO: Mandatory Access Control activated.\n");
+ sbin_init_started = 1;
+
+ { /* Check all profiles currently assigned to domains are defined. */
+ struct domain_info *domain;
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ const u8 profile = domain->profile;
+ if (profile_ptr[profile])
+ continue;
+ panic("TOMOYO: Profile %u (used by '%s') "
+ "not defined.\n",
+ profile,
+ domain->domainname->name);
+ }
+ }
+}
+
+
+/************************* MAC Decision Delayer *************************/
+
+static DECLARE_WAIT_QUEUE_HEAD(query_wait);
+
+static DEFINE_SPINLOCK(query_lock);
+
+struct query_entry {
+ struct list_head list;
+ char *query;
+ int query_len;
+ unsigned int serial;
+ int timer;
+ int answer;
+};
+
+static LIST_HEAD(query_list);
+static atomic_t queryd_watcher = ATOMIC_INIT(0);
+
+/**
+ * tmy_supervisor - ask for supervisors decision.
+ * @fmt: format strings for printf().
+ *
+ * Returns zero if administrator allowed.
+ * Returns nonzero otherwise.
+ *
+ * This is one of TOMOYO Linux's feature.
+ * Normally, access requests that violates policy is rejected immediately.
+ * But this behavior is inconvenient when developing policy.
+ * TOMOYO Linux allows administrators handle access requests that violated
+ * policy in enforce mode to adjust policy.
+ *
+ * This function blocks a process who is requesting access that violated policy
+ * and tell it via /sys/kernel/security/tomoyo/query and wait for supervisor's
+ * decision.
+ */
+int tmy_supervisor(const char *fmt, ...)
+{
+ va_list args;
+ int error = -EPERM;
+ int pos;
+ int len;
+ static unsigned int serial;
+ struct query_entry *query_entry;
+
+ if (!tmy_flags(TMY_ALLOW_ENFORCE_GRACE))
+ return -EPERM;
+ if (!atomic_read(&queryd_watcher))
+ return -EPERM;
+
+ va_start(args, fmt);
+ len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32;
+ va_end(args);
+
+ query_entry = tmy_alloc(sizeof(*query_entry));
+ if (!query_entry)
+ goto out;
+ query_entry->query = tmy_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 *****/
+
+ tmy_update_counter(TMY_UPDATE_QUERY);
+
+ /* Give 10 seconds for supervisor's opinion. */
+ for (query_entry->timer = 0;
+ atomic_read(&queryd_watcher) &&
+ tmy_flags(TMY_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;
+ }
+
+ tmy_update_counter(TMY_UPDATE_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)
+ tmy_free(query_entry->query);
+ tmy_free(query_entry);
+ return error;
+}
+
+/* Check for pending access requests. */
+static int tmy_poll_query(struct file *file, poll_table *wait)
+{
+ int 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 pending access requests. */
+static int tmy_read_query(struct 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) {
+ tmy_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) {
+ len = ptr->query_len;
+ break;
+ }
+ }
+ spin_unlock(&query_lock);
+ /***** CRITICAL SECTION END *****/
+
+ if (!len) {
+ head->read_step = 0;
+ return 0;
+ }
+ buf = tmy_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) {
+ /* Some query can be skiipped 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->readbuf_size = len;
+ head->read_avail = len;
+ head->read_buf = buf;
+ head->read_step++;
+ } else
+ tmy_free(buf);
+ }
+
+ return 0;
+}
+
+/* Reply to pending access requests. */
+static int tmy_write_answer(struct 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;
+}
+
+/****************** /sys/kernel/security INTERFACE HANDLER ******************/
+
+/* Policy updates counter. */
+static unsigned int updates_counter[TMY_MAX_UPDATES_COUNTER];
+static DEFINE_SPINLOCK(updates_counter_lock);
+
+/**
+ * tmy_update_counter - notify userland that policy is changed.
+ * @index: index to update counter.
+ *
+ * This is for userland process who is monitoring policy changes.
+ */
+void tmy_update_counter(const unsigned char index)
+{
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&updates_counter_lock);
+ if (index < TMY_MAX_UPDATES_COUNTER)
+ updates_counter[index]++;
+ spin_unlock(&updates_counter_lock);
+ /***** CRITICAL SECTION END *****/
+}
+
+/* Read policy update counter. */
+static int tmy_read_updates_counter(struct io_buffer *head)
+{
+ unsigned int counter[TMY_MAX_UPDATES_COUNTER];
+ if (head->read_eof)
+ return 0;
+
+ /***** 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 *****/
+
+ tmy_io_printf(head,
+ "/sys/kernel/security/tomoyo/domain_policy: %10u\n"
+ "/sys/kernel/security/tomoyo/system_policy: %10u\n"
+ "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
+ "/sys/kernel/security/tomoyo/profile: %10u\n"
+ "/sys/kernel/security/tomoyo/query: %10u\n"
+ "/sys/kernel/security/tomoyo/manager: %10u\n",
+ counter[TMY_UPDATE_DOMAINPOLICY],
+ counter[TMY_UPDATE_SYSTEMPOLICY],
+ counter[TMY_UPDATE_EXCEPTIONPOLICY],
+ counter[TMY_UPDATE_PROFILE],
+ counter[TMY_UPDATE_QUERY],
+ counter[TMY_UPDATE_MANAGER]);
+ head->read_eof = 1;
+
+ return 0;
+}
+
+/* Read how much memory is used. */
+static int tmy_read_memory_counter(struct io_buffer *head)
+{
+ int shared;
+ int private;
+ int dynamic;
+
+ if (head->read_eof)
+ return 0;
+ shared = tmy_get_memory_used_for_save_name();
+ private = tmy_get_memory_used_for_elements();
+ dynamic = tmy_get_memory_used_for_dynamic();
+ if (tmy_io_printf(head,
+ "Shared: %10u\n"
+ "Private: %10u\n"
+ "Dynamic: %10u\n"
+ "Total: %10u\n",
+ shared,
+ private,
+ dynamic,
+ shared + private + dynamic) == 0)
+ head->read_eof = 1;
+ return 0;
+}
+
+/* Read TOMOYO Linux's version. */
+static int tmy_read_version(struct io_buffer *head)
+{
+ if (!head->read_eof) {
+ tmy_io_printf(head, TOMOYO_VERSION_CODE "\n");
+ head->read_eof = 1;
+ }
+ return 0;
+}
+
+/* Read current process's domainname. */
+static int tmy_read_self_domain(struct io_buffer *head)
+{
+ if (!head->read_eof) {
+ tmy_io_printf(head,
+ "%s",
+ TMY_SECURITY->domain->domainname->name);
+ head->read_eof = 1;
+ }
+
+ return 0;
+}
+
+/* This is /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open_control(const int type, struct file *file)
+{
+ struct io_buffer *head = tmy_alloc(sizeof(*head));
+
+ if (!head)
+ return -ENOMEM;
+ init_MUTEX(&head->read_sem);
+ init_MUTEX(&head->write_sem);
+
+ switch (type) {
+ case TMY_POLICY_DOMAINPOLICY:
+ head->write = tmy_add_domain_policy;
+ head->read = tmy_read_domain_policy;
+ break;
+ case TMY_POLICY_SYSTEMPOLICY:
+ head->write = tmy_add_system_policy;
+ head->read = tmy_read_system_policy;
+ break;
+ case TMY_POLICY_EXCEPTIONPOLICY:
+ head->write = tmy_add_exception_policy;
+ head->read = tmy_read_exception_policy;
+ break;
+ case TMY_POLICY_DOMAIN_STATUS:
+ head->write = tmy_update_domain_profile;
+ head->read = tmy_read_domain_profile;
+ break;
+ case TMY_INFO_PROCESS_STATUS:
+ head->write = tmy_write_pid;
+ head->read = tmy_read_pid;
+ break;
+ case TMY_INFO_SELFDOMAIN:
+ head->read = tmy_read_self_domain;
+ break;
+ case TMY_INFO_MEMINFO:
+ head->read = tmy_read_memory_counter;
+ head->readbuf_size = 128;
+ break;
+ case TMY_PROFILE:
+ head->write = tmy_set_profile;
+ head->read = tmy_read_profile;
+ break;
+ case TMY_POLICY_QUERY:
+ head->poll = tmy_poll_query;
+ head->write = tmy_write_answer;
+ head->read = tmy_read_query;
+ break;
+ case TMY_POLICY_MANAGER:
+ head->write = tmy_add_manager_policy;
+ head->read = tmy_read_manager_policy;
+ break;
+ case TMY_INFO_UPDATESCOUNTER:
+ head->read = tmy_read_updates_counter;
+ break;
+ case TMY_VERSION:
+ head->read = tmy_read_version;
+ break;
+ }
+
+ if (type != TMY_POLICY_QUERY) {
+ if (!head->readbuf_size)
+ head->readbuf_size = PAGE_SIZE * 2;
+ head->read_buf = tmy_alloc(head->readbuf_size);
+ if (!head->read_buf) {
+ tmy_free(head);
+ return -ENOMEM;
+ }
+ }
+
+ if (head->write) {
+ head->writebuf_size = PAGE_SIZE * 2;
+ head->write_buf = tmy_alloc(head->writebuf_size);
+ if (!head->write_buf) {
+ tmy_free(head->read_buf);
+ tmy_free(head);
+ return -ENOMEM;
+ }
+ }
+
+ file->private_data = head;
+
+ if (type == TMY_INFO_SELFDOMAIN)
+ tmy_read_control(file, NULL, 0);
+ else if (head->write == tmy_write_answer)
+ atomic_inc(&queryd_watcher);
+
+ return 0;
+}
+
+/* Copy read data to userland buffer. */
+static int tmy_copy_to_user(struct io_buffer *head, char __user *buffer,
+ int buffer_len)
+{
+ int len = head->read_avail;
+ char *cp = head->read_buf;
+
+ if (len > buffer_len)
+ len = buffer_len;
+ if (len) {
+ if (copy_to_user(buffer, cp, len))
+ return -EFAULT;
+ head->read_avail -= len;
+ memmove(cp, cp + len, head->read_avail);
+ }
+
+ return len;
+}
+
+/* Check for pending requests. */
+static int tmy_poll_control(struct file *file, poll_table *wait)
+{
+ struct io_buffer *head = (struct io_buffer *) file->private_data;
+ if (!head->poll)
+ return -ENOSYS;
+ return head->poll(file, wait);
+}
+
+/* Read policy. */
+static int tmy_read_control(struct file *file, char __user *buffer,
+ const int buffer_len)
+{
+ int len = 0;
+ struct io_buffer *head = (struct io_buffer *) file->private_data;
+
+ if (!head->read)
+ return -ENOSYS;
+ if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+ return -EFAULT;
+ if (down_interruptible(&head->read_sem))
+ return -EINTR;
+ len = head->read(head);
+ if (len >= 0)
+ len = tmy_copy_to_user(head, buffer, buffer_len);
+ up(&head->read_sem);
+
+ return len;
+}
+
+/* Update policy. */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+ const int buffer_len)
+{
+ struct io_buffer *head = (struct io_buffer *) 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;
+ if (!tmy_is_root())
+ return -EPERM;
+ if (head->write != tmy_write_pid && !tmy_is_policy_manager())
+ /* Forbid updating policies for non manager programs. */
+ return -EPERM;
+
+ if (down_interruptible(&head->write_sem))
+ return -EINTR;
+ 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;
+ tmy_normalize_line(cp0);
+ head->write(head);
+ }
+ up(&head->write_sem);
+
+ return error;
+}
+
+/* Close /sys/kernel/security/tomoyo/ interface. */
+static int tmy_close_control(struct file *file)
+{
+ struct io_buffer *head = file->private_data;
+
+ if (head->write == tmy_write_answer)
+ atomic_dec(&queryd_watcher);
+
+ tmy_free(head->read_buf);
+ head->read_buf = NULL;
+ tmy_free(head->write_buf);
+ head->write_buf = NULL;
+ tmy_free(head);
+ head = NULL;
+ file->private_data = NULL;
+ return 0;
+}
+
+/* open() operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open(struct inode *inode, struct file *file)
+{
+ return tmy_open_control(((u8 *) file->f_dentry->d_inode->i_private)
+ - ((u8 *) NULL), file);
+}
+
+/* close() operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_release(struct inode *inode, struct file *file)
+{
+ return tmy_close_control(file);
+}
+
+/* poll() operation for /sys/kernel/security/tomoyo/ interface. */
+static unsigned int tmy_poll(struct file *file, poll_table *wait)
+{
+ return tmy_poll_control(file, wait);
+}
+
+/* read() operation for /sys/kernel/security/tomoyo/ interface. */
+static ssize_t tmy_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return tmy_read_control(file, buf, count);
+}
+
+/* write() operation for /sys/kernel/security/tomoyo/ interface. */
+static ssize_t tmy_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return tmy_write_control(file, buf, count);
+}
+
+static struct file_operations tmy_operations = {
+ .open = tmy_open,
+ .release = tmy_release,
+ .poll = tmy_poll,
+ .read = tmy_read,
+ .write = tmy_write
+};
+
+/* Associate /sys/kernel/security/tomoyo/ interface with key. */
+static void __init tmy_create_entry(const char *name,
+ const mode_t mode,
+ struct dentry *parent,
+ const int key)
+{
+ securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
+ &tmy_operations);
+}
+
+/**
+ * tmy_interface_init - initialize /sys/kernel/security/tomoyo/ interface.
+ */
+static int __init tmy_interface_init(void)
+{
+ struct dentry *tmy_dir;
+
+ tmy_dir = securityfs_create_dir("tomoyo", NULL);
+ tmy_realpath_init();
+ tmy_find_domain(""); /* Set domainname of KERNEL domain. */
+ tmy_create_entry("query", 0600, tmy_dir,
+ TMY_POLICY_QUERY);
+ tmy_create_entry("domain_policy", 0600, tmy_dir,
+ TMY_POLICY_DOMAINPOLICY);
+ tmy_create_entry("system_policy", 0600, tmy_dir,
+ TMY_POLICY_SYSTEMPOLICY);
+ tmy_create_entry("exception_policy", 0600, tmy_dir,
+ TMY_POLICY_EXCEPTIONPOLICY);
+ tmy_create_entry(".domain_status", 0600, tmy_dir,
+ TMY_POLICY_DOMAIN_STATUS);
+ tmy_create_entry(".process_status", 0400, tmy_dir,
+ TMY_INFO_PROCESS_STATUS);
+ tmy_create_entry("self_domain", 0400, tmy_dir,
+ TMY_INFO_SELFDOMAIN);
+ tmy_create_entry("meminfo", 0400, tmy_dir,
+ TMY_INFO_MEMINFO);
+ tmy_create_entry("profile", 0600, tmy_dir,
+ TMY_PROFILE);
+ tmy_create_entry("manager", 0600, tmy_dir,
+ TMY_POLICY_MANAGER);
+ tmy_create_entry(".updates_counter", 0400, tmy_dir,
+ TMY_INFO_UPDATESCOUNTER);
+ tmy_create_entry("version", 0400, tmy_dir,
+ TMY_VERSION);
+ return 0;
+}
+
+postcore_initcall(tmy_interface_init);
Domain transition functions for TOMOYO Linux.
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->security.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/domain.c | 1256 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1256 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/domain.c 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,1256 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Domain transition functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+
+/************************* VARIABLES *************************/
+
+/* Lock for appending domain's ACL. */
+DECLARE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DECLARE_MUTEX(new_domain_assign_lock);
+
+/***** The structure for program files to force domain reconstruction. *****/
+
+struct domain_initializer_entry {
+ struct domain_initializer_entry *next;
+ const struct path_info *domainname; /* This may be NULL */
+ const struct path_info *program;
+ u8 is_deleted;
+ u8 is_not;
+ u8 is_last_name;
+};
+
+/***** The structure for domains to not to transit domains. *****/
+
+struct domain_keeper_entry {
+ struct domain_keeper_entry *next;
+ const struct path_info *domainname;
+ const struct path_info *program; /* This may be NULL */
+ u8 is_deleted;
+ u8 is_not;
+ u8 is_last_name;
+};
+
+/***** The structure for program files that should be aggregated. *****/
+
+struct aggregator_entry {
+ struct aggregator_entry *next;
+ const struct path_info *original_name;
+ const struct path_info *aggregated_name;
+ u8 is_deleted;
+};
+
+/***** The structure for program files that should be aliased. *****/
+
+struct alias_entry {
+ struct alias_entry *next;
+ const struct path_info *original_name;
+ const struct path_info *aliased_name;
+ u8 is_deleted;
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_is_domain_def - check if the line is likely a domain definition.
+ * @buffer: the line to check.
+ *
+ * Returns nonzero if @buffer is likely a domain definition.
+ * Returns zero otherwise.
+ *
+ * For complete validation check, use tmy_is_correct_domain().
+ */
+int tmy_is_domain_def(const unsigned char *buffer)
+{
+ return strncmp(buffer, TMY_ROOT_NAME, TMY_ROOT_NAME_LEN) == 0;
+}
+
+/**
+ * tmy_add_acl - add an entry to a domain.
+ * @ptr: pointer to the last "struct acl_info" of @domain. May be NULL.
+ * @domain: pointer to "struct domain_info".
+ * @new_ptr: pointer to "struct acl_info" to add.
+ *
+ * Returns zero.
+ *
+ * To be honest, I can calculate @ptr from @domain.
+ * But since the caller knows @ptr, I use it.
+ */
+int tmy_add_acl(struct acl_info *ptr,
+ struct domain_info *domain,
+ struct acl_info *new_ptr)
+{
+ mb(); /* Instead of using spinlock. */
+
+ if (!ptr)
+ domain->first_acl_ptr = (struct acl_info *) new_ptr;
+ else
+ ptr->next = (struct acl_info *) new_ptr;
+
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ return 0;
+}
+
+/**
+ * tmy_del_acl - remove an entry from a domain
+ * @ptr: pointer to "struct acl_info" to remove.
+ *
+ * Returns zero.
+ *
+ * TOMOYO Linux doesn't free memory used by policy because policies are not
+ * so frequently changed after entring into enforcing mode.
+ * This makes the code free of read-lock.
+ * The caller uses "down(&domain_acl_lock);" as write-lock.
+ */
+int tmy_del_acl(struct acl_info *ptr)
+{
+ ptr->is_deleted = 1;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ return 0;
+}
+
+/************************ DOMAIN INITIALIZER HANDLER ************************/
+
+static struct domain_initializer_entry *domain_initializer_list;
+
+/* Update domain initializer list. */
+static int tmy_add_domain_initializer_entry(const char *domainname,
+ const char *program,
+ const u8 is_not,
+ const u8 is_delete)
+{
+ struct domain_initializer_entry *new_entry;
+ struct domain_initializer_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_program;
+ const struct path_info *saved_domainname = NULL;
+ int error = -ENOMEM;
+ u8 is_last_name = 0;
+
+ if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+
+ if (domainname) {
+ if (!tmy_is_domain_def(domainname) &&
+ tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+ is_last_name = 1;
+
+ else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ return -EINVAL;
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ return -ENOMEM;
+ }
+
+ saved_program = tmy_save_name(program);
+ if (!saved_program)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+ if (ptr->is_not == is_not &&
+ ptr->domainname == saved_domainname &&
+ ptr->program == saved_program) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_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;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = domain_initializer_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ domain_initializer_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+ return error;
+}
+
+/**
+ * tmy_read_domain_initializer_policy - read domain initializer policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_initializer_policy(struct io_buffer *head)
+{
+ struct domain_initializer_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = domain_initializer_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted) {
+ const char *s1 = ptr->is_not ? "no_" : "";
+ const char *s2 = TMY_INITIALIZE_DOMAIN;
+ const char *s3 = ptr->program->name;
+
+ if (ptr->domainname) {
+ if (tmy_io_printf(head, "%s%s%s from %s\n",
+ s1, s2, s3,
+ ptr->domainname->name))
+ break;
+ } else {
+ if (tmy_io_printf(head, "%s%s%s\n",
+ s1, s2, s3))
+ break;
+ }
+ }
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_domain_initializer_policy - add domain initializer policy
+ * @data: a line to parse.
+ * @is_not: is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain initializer entry.
+ */
+int tmy_add_domain_initializer_policy(char *data,
+ const u8 is_not,
+ const u8 is_delete)
+{
+ char *cp = strstr(data, " from ");
+
+ if (cp) {
+ *cp = '\0';
+ return tmy_add_domain_initializer_entry(cp + 6, data, is_not,
+ is_delete);
+ }
+
+ return tmy_add_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/* Should I transit to a domain under "<kernel>" domain? */
+static int tmy_is_domain_initializer(const struct path_info *domainname,
+ const struct path_info *program,
+ const struct path_info *last_name)
+{
+ struct domain_initializer_entry *ptr;
+ int flag = 0;
+
+ for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+
+ if (ptr->domainname) {
+ if (!ptr->is_last_name) {
+ if (ptr->domainname != domainname)
+ continue;
+ } else {
+ if (tmy_pathcmp(ptr->domainname, last_name))
+ continue;
+ }
+ }
+
+ if (tmy_pathcmp(ptr->program, program))
+ continue;
+ if (ptr->is_not)
+ return 0;
+
+ flag = 1;
+ }
+
+ return flag;
+}
+
+/************************* DOMAIN KEEPER HANDLER *************************/
+
+static struct domain_keeper_entry *domain_keeper_list;
+
+/* Update domain keeper list. */
+static int tmy_add_domain_keeper_entry(const char *domainname,
+ const char *program,
+ const u8 is_not,
+ const u8 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 DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+ u8 is_last_name = 0;
+
+ if (!tmy_is_domain_def(domainname) &&
+ tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+ is_last_name = 1;
+
+ else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ return -EINVAL;
+
+ if (program) {
+ if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_program = tmy_save_name(program);
+ if (!saved_program)
+ return -ENOMEM;
+ }
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+ if (ptr->is_not == is_not &&
+ ptr->domainname == saved_domainname &&
+ ptr->program == saved_program) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_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;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = domain_keeper_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ domain_keeper_list = new_entry;
+ error = 0;
+
+out: ;
+
+ up(&lock);
+ return error;
+}
+
+/**
+ * tmy_add_domain_keeper_policy - add domain keeper policy.
+ * @data: a line to parse.
+ * @is_not: is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain keeper entry.
+ *
+ */
+int tmy_add_domain_keeper_policy(char *data,
+ const u8 is_not,
+ const u8 is_delete)
+{
+ char *cp = strstr(data, " from ");
+
+ if (cp) {
+ *cp = '\0';
+ return tmy_add_domain_keeper_entry(cp + 6, data,
+ is_not, is_delete);
+ }
+
+ return tmy_add_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tmy_read_domain_keeper_policy - read domain keeper policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_keeper_policy(struct io_buffer *head)
+{
+ struct domain_keeper_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = domain_keeper_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted)
+ continue;
+
+ if (ptr->program) {
+ if (tmy_io_printf(head,
+ "%s" TMY_KEEP_DOMAIN
+ "%s from %s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->program->name,
+ ptr->domainname->name))
+ break;
+
+ } else {
+ if (tmy_io_printf(head,
+ "%s" TMY_KEEP_DOMAIN
+ "%s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->domainname->name))
+ break;
+
+ }
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/* Should I remain in current domain? */
+static int tmy_is_domain_keeper(const struct path_info *domainname,
+ const struct path_info *program,
+ const struct path_info *last_name)
+{
+ struct domain_keeper_entry *ptr;
+ int flag = 0;
+
+ for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+
+ if (!ptr->is_last_name) {
+ if (ptr->domainname != domainname)
+ continue;
+ } else {
+ if (tmy_pathcmp(ptr->domainname, last_name))
+ continue;
+ }
+
+ if (ptr->program && tmy_pathcmp(ptr->program, program))
+ continue;
+ if (ptr->is_not)
+ return 0;
+ flag = 1;
+ }
+
+ return flag;
+}
+
+/********************* SYMBOLIC LINKED PROGRAM HANDLER *********************/
+
+static struct alias_entry *alias_list;
+
+/* Update alias list. */
+static int tmy_add_alias_entry(const char *original_name,
+ const char *aliased_name,
+ const u8 is_delete)
+{
+ struct alias_entry *new_entry;
+ struct alias_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_original_name;
+ const struct path_info *saved_aliased_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(original_name, 1, -1, -1, __FUNCTION__) ||
+ !tmy_correct_path(aliased_name, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+
+ saved_original_name = tmy_save_name(original_name);
+ saved_aliased_name = tmy_save_name(aliased_name);
+ if (!saved_original_name || !saved_aliased_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = alias_list; ptr; ptr = ptr->next) {
+ if (ptr->original_name == saved_original_name &&
+ ptr->aliased_name == saved_aliased_name) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->original_name = saved_original_name;
+ new_entry->aliased_name = saved_aliased_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = alias_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ alias_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_read_alias_policy - read alias policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_alias_policy(struct io_buffer *head)
+{
+ struct alias_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = alias_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head,
+ TMY_ALIAS "%s %s\n",
+ ptr->original_name->name,
+ ptr->aliased_name->name))
+ break;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_alias_policy - add alias policy.
+ * @data: a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an alias entry.
+ */
+int tmy_add_alias_policy(char *data, const u8 is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_alias_entry(data, cp, is_delete);
+}
+
+/************************ DOMAIN AGGREGATOR HANDLER ************************/
+
+static struct aggregator_entry *aggregator_list;
+
+/* Update aggregator list. */
+static int tmy_add_aggregator_entry(const char *original_name,
+ const char *aggregated_name,
+ const u8 is_delete)
+{
+ struct aggregator_entry *new_entry;
+ struct aggregator_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved_original_name;
+ const struct path_info *saved_aggregated_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(original_name, 1, 0, -1, __FUNCTION__) ||
+ !tmy_correct_path(aggregated_name, 1, -1, -1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_original_name = tmy_save_name(original_name);
+ saved_aggregated_name = tmy_save_name(aggregated_name);
+ if (!saved_original_name || saved_aggregated_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+ if (ptr->original_name == saved_original_name &&
+ ptr->aggregated_name == saved_aggregated_name) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->original_name = saved_original_name;
+ new_entry->aggregated_name = saved_aggregated_name;
+
+ mb(); /* Instead of using spinlock. */
+ ptr = aggregator_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ aggregator_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_read_aggregator_policy - read aggregator policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_aggregator_policy(struct io_buffer *head)
+{
+ struct aggregator_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = aggregator_list;
+
+ for (; ptr; ptr = ptr->next) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head,
+ TMY_AGGREGATOR "%s %s\n",
+ ptr->original_name->name,
+ ptr->aggregated_name->name))
+ break;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_aggregator_policy - add aggregator policy.
+ * @data: a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an aggregator entry.
+ */
+int tmy_add_aggregator_policy(char *data, const u8 is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_aggregator_entry(data, cp, is_delete);
+}
+
+/************************* DOMAIN DELETION HANDLER *************************/
+
+/**
+ * tmy_delete_domain - delete a domain.
+ * @domainname0: domainname to delete.
+ *
+ * Returns zero.
+ *
+ * This function deletes domains.
+ * The behavior of deleting domain is like deleting files on Linux's
+ * filesystem. A process transits to different domain upon do_execve(),
+ * and the process can refer the deleted domains after the domain is deleted,
+ * like a process opens a file and the process can read()/write() the deleted
+ * file after the file is deleted.
+ * This avoids processes from crashing due to referring non-existent domains.
+ * Administrator manually terminates processes thet are referring deleted
+ * domains after deleting domains.
+ * Also, undeleting domains is supported. See tmy_undelete_domain().
+ */
+int tmy_delete_domain(char *domainname0)
+{
+ struct domain_info *domain;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ down(&new_domain_assign_lock);
+
+ /* Is there an active domain? */ /* Never delete KERNEL_DOMAIN */
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ if (domain->is_deleted ||
+ tmy_pathcmp(domain->domainname, &domainname))
+ continue;
+ break;
+ }
+ if (domain) {
+ struct domain_info *domain2;
+
+ /* Mark already deleted domains as non undeletable. */
+ for (domain2 = KERNEL_DOMAIN.next;
+ domain2;
+ domain2 = domain2->next) {
+ if (!domain2->is_deleted ||
+ tmy_pathcmp(domain2->domainname, &domainname))
+ continue;
+ domain2->is_deleted = 255;
+ }
+
+ /* Delete and mark active domain as undeletable. */
+ domain->is_deleted = 1;
+ }
+
+ up(&new_domain_assign_lock);
+
+ return 0;
+}
+
+/**
+ * tmy_undelete_domain - undelete a domain.
+ * @domainname0: domainname to undelete.
+ *
+ * Returns pointer to undeleted "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function undeletes domains.
+ * Not only the domain previously deleted by tmy_delete_domain()
+ * but also all domains deleted by tmy_delete_domain() are undeletable.
+ * If there is no deleted domain named @domainname0 or
+ * a not-yet-deleted domain named @domainname0 exists, undelete fails.
+ * Otherwise, previously deleted domain named @domainname0 is undeleted.
+ */
+struct domain_info *tmy_undelete_domain(const char *domainname0)
+{
+ struct domain_info *domain;
+ struct domain_info *candidate_domain = NULL;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ down(&new_domain_assign_lock);
+
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ if (tmy_pathcmp(&domainname, domain->domainname))
+ continue;
+
+ if (!domain->is_deleted) {
+ /* This domain is active. I can't undelete. */
+ candidate_domain = NULL;
+ break;
+ }
+
+ /* Is this domain undeletable? */
+ if (domain->is_deleted == 1)
+ candidate_domain = domain;
+ }
+ if (candidate_domain)
+ candidate_domain->is_deleted = 0;
+
+ up(&new_domain_assign_lock);
+
+ return candidate_domain;
+}
+
+/************************ DOMAIN TRANSITION HANDLER ************************/
+
+/**
+ * tmy_find_domain - find a domain with given domainname.
+ * @domainname0: the domainname to find.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function does not create a new domain
+ * if a domain named @domainname0 does not exist.
+ */
+struct domain_info *tmy_find_domain(const char *domainname0)
+{
+ struct domain_info *domain;
+ static int first = 1;
+ struct path_info domainname;
+
+ domainname.name = domainname0;
+ tmy_fill_path_info(&domainname);
+
+ if (first) {
+ KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+ first = 0;
+ }
+
+ for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+ if (!domain->is_deleted &&
+ !tmy_pathcmp(&domainname, domain->domainname))
+ return domain;
+ }
+
+ return NULL;
+}
+
+/**
+ * tmy_new_domain - find or assign a domain with given domainname.
+ * @domainname: the domainname to find.
+ * @profile: profile number to assign if newly created.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function creates a new domain if a domain named @domainname0
+ * does not exist.
+ */
+struct domain_info *tmy_new_domain(const char *domainname,
+ const u8 profile)
+{
+ struct domain_info *domain = NULL;
+ const struct path_info *saved_domainname;
+
+ down(&new_domain_assign_lock);
+
+ domain = tmy_find_domain(domainname);
+ if (domain)
+ goto out;
+
+ if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+ goto out;
+
+ saved_domainname = tmy_save_name(domainname);
+ if (!saved_domainname)
+ goto out;
+
+ /* Can I reuse memory of deleted domain? */
+ for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+ struct task_struct *p;
+ struct acl_info *ptr;
+ int flag;
+
+ if (!domain->is_deleted ||
+ domain->domainname != saved_domainname)
+ continue;
+ flag = 0;
+
+ /***** CRITICAL SECTION START *****/
+ read_lock(&tasklist_lock);
+ for_each_process(p) {
+ /* "struct task_struct"->security is not NULL. */
+ if (((struct tmy_security *) p->security)->domain
+ == domain) {
+ flag = 1;
+ break;
+ }
+ }
+ read_unlock(&tasklist_lock);
+ /***** CRITICAL SECTION END *****/
+
+ /* Somebody is still referring this deleted domain. */
+ if (flag)
+ continue;
+
+ /* OK. Let's reuse memory for this deleted domain. */
+
+ /* Delete all entries in this deleted domain. */
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next)
+ ptr->is_deleted = 1;
+
+ domain->profile = profile;
+ domain->quota_warned = 0;
+
+ mb(); /* Instead of using spinlock. */
+ /* Undelete this deleted domain. */
+ domain->is_deleted = 0;
+ goto out;
+ }
+
+ /* No memory reusable. Create using new memory. */
+ domain = tmy_alloc_element(sizeof(*domain));
+ if (domain) {
+ struct domain_info *ptr = &KERNEL_DOMAIN;
+
+ domain->domainname = saved_domainname;
+ domain->profile = profile;
+
+ mb(); /* Instead of using spinlock. */
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = domain;
+ }
+out: ;
+ up(&new_domain_assign_lock);
+
+ return domain;
+}
+
+/* Convert non ASCII printable characters to ASCII printable characters. */
+static int tmy_escape(char *dest, const char *src, int dest_len)
+{
+ while (*src) {
+ const unsigned char c = *(const unsigned char *) src;
+
+ if (c == '\\') {
+ dest_len -= 2;
+ if (dest_len <= 0)
+ goto out;
+ *dest++ = '\\';
+ *dest++ = '\\';
+ } else if (c > ' ' && c < 127) {
+ if (--dest_len <= 0)
+ goto out;
+ *dest++ = c;
+ } else {
+ dest_len -= 4;
+ if (dest_len <= 0)
+ goto out;
+ *dest++ = '\\';
+ *dest++ = (c >> 6) + '0';
+ *dest++ = ((c >> 3) & 7) + '0';
+ *dest++ = (c & 7) + '0';
+ }
+ src++;
+ }
+
+ if (--dest_len <= 0)
+ goto out;
+ *dest = '\0';
+
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/* Get argv[0] of "struct linux_binprm". */
+static char *tmy_get_argv0(struct linux_binprm *bprm)
+{
+ char *arg_ptr;
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int i = pos / PAGE_SIZE;
+ int offset = pos % PAGE_SIZE;
+
+ if (bprm->argc <= 0)
+ return NULL;
+
+ arg_ptr = tmy_alloc(PAGE_SIZE);
+
+ if (!arg_ptr)
+ goto out;
+
+ while (1) {
+ struct page *page;
+ const char *kaddr;
+ char *tmp_arg;
+
+#ifdef CONFIG_MMU
+ if (get_user_pages(current, bprm->mm, pos,
+ 1, 0, 1, &page, NULL) <= 0)
+ goto out;
+#else
+ page = bprm->page[i];
+#endif
+ kaddr = kmap(page);
+ if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+ put_page(page);
+#endif
+ goto out;
+ }
+
+ memmove(arg_ptr + arg_len, kaddr + offset, PAGE_SIZE - offset);
+ kunmap(page);
+
+#ifdef CONFIG_MMU
+ put_page(page);
+ pos += PAGE_SIZE - offset;
+#endif
+
+ arg_len += PAGE_SIZE - offset;
+
+ if (memchr(arg_ptr, '\0', arg_len))
+ break;
+
+ tmp_arg = tmy_alloc(arg_len + PAGE_SIZE);
+ if (!tmp_arg)
+ goto out;
+
+ memmove(tmp_arg, arg_ptr, arg_len);
+ tmy_free(arg_ptr);
+ arg_ptr = tmp_arg;
+ i++;
+ offset = 0;
+ }
+ return arg_ptr;
+out: ;
+ tmy_free(arg_ptr);
+
+ return NULL;
+}
+
+/**
+ * tmy_find_next_domain - find a domain to transit to if do_execve() succeeds.
+ * @bprm: pointer to "struct linux_binprm".
+ * @next_domain: pointer to pointer to "struct domain_info".
+ *
+ * Returns zero if success. @next_domain receives new domain to transit to.
+ * Returns nonzero on failure.
+ *
+ * This function handles TOMOYO Linux's domain transition.
+ * New domains are automatically created unless the domain the caller process
+ * belongs to is assigned a profile for enforcing mode.
+ */
+int tmy_find_next_domain(struct linux_binprm *bprm,
+ struct domain_info **next_domain)
+{
+ /* This function assumes that the size of buffer returned */
+ /* by tmy_realpath() = TMY_MAX_PATHNAME_LEN. */
+ struct domain_info *old_domain = TMY_SECURITY->domain;
+ struct domain_info *domain = NULL;
+ const char *old_domain_name = old_domain->domainname->name;
+ const char *original_name = bprm->filename;
+ struct file *filp = bprm->file;
+ char *new_domain_name = NULL;
+ char *real_program_name = NULL;
+ char *symlink_program_name = NULL;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+ int retval;
+ struct path_info r;
+ struct path_info s;
+ struct path_info l;
+
+ /*
+ * Built-in initializers.
+ * This is needed because policies are not loaded
+ * until starting /sbin/init .
+ */
+ static int first = 1;
+ if (first) {
+ tmy_add_domain_initializer_entry(NULL, "/sbin/hotplug", 0, 0);
+ tmy_add_domain_initializer_entry(NULL, "/sbin/modprobe", 0, 0);
+ tmy_add_domain_initializer_entry(NULL, "/sbin/udevd", 0, 0);
+ first = 0;
+ }
+
+ /* Get realpath of program. */
+ /* I hope tmy_realpath() won't fail with -ENOMEM. */
+ retval = -ENOENT;
+ real_program_name = tmy_realpath(original_name);
+
+ if (!real_program_name)
+ goto out;
+
+ /* Get realpath of symbolic link. */
+ symlink_program_name = tmy_realpath_nofollow(original_name);
+ if (!symlink_program_name)
+ goto out;
+
+ r.name = real_program_name;
+ tmy_fill_path_info(&r);
+ s.name = symlink_program_name;
+ tmy_fill_path_info(&s);
+ l.name = strrchr(old_domain_name, ' ');
+
+ if (l.name)
+ l.name++;
+ else
+ l.name = old_domain_name;
+ tmy_fill_path_info(&l);
+
+ /* Check 'alias' directive. */
+ if (tmy_pathcmp(&r, &s)) {
+ struct alias_entry *ptr;
+
+ /* Is this program allowed to be called via symbolic links? */
+ for (ptr = alias_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted ||
+ tmy_pathcmp(&r, ptr->original_name) ||
+ tmy_pathcmp(&s, ptr->aliased_name))
+ continue;
+ memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+ strncpy(real_program_name,
+ ptr->aliased_name->name,
+ TMY_MAX_PATHNAME_LEN - 1);
+ tmy_fill_path_info(&r);
+ break;
+ }
+ }
+
+ /* Compare basename of real_program_name and argv[0] */
+ if (bprm->argc > 0 && tmy_flags(TMY_MAC_FOR_ARGV0)) {
+
+ char *org_argv0 = tmy_get_argv0(bprm);
+
+ retval = -ENOMEM;
+ if (org_argv0) {
+
+ const int len = strlen(org_argv0);
+ char *printable_argv0 = tmy_alloc(len * 4 + 8);
+
+ if (printable_argv0 &&
+ !tmy_escape(printable_argv0, org_argv0,
+ len * 4 + 8)) {
+ const char *base_argv0;
+ const char *base_filename;
+
+ base_argv0 = strrchr(printable_argv0, '/');
+ if (!base_argv0)
+ base_argv0 = printable_argv0;
+ else
+ base_argv0++;
+
+ base_filename = strrchr(real_program_name, '/');
+ if (!base_filename)
+ base_filename = real_program_name;
+ else
+ base_filename++;
+
+ if (strcmp(base_argv0, base_filename))
+ retval = tmy_argv0_perm(&r, base_argv0);
+ else
+ retval = 0;
+ }
+
+ tmy_free(printable_argv0);
+ tmy_free(org_argv0);
+ }
+
+ if (retval)
+ goto out;
+
+ }
+
+
+ /* Check 'aggregator' directive. */
+ {
+ struct aggregator_entry *ptr;
+
+ /* Is this program allowed to be aggregated? */
+ for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted ||
+ !tmy_path_match(&r, ptr->original_name))
+ continue;
+ memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+ strncpy(real_program_name,
+ ptr->aggregated_name->name,
+ TMY_MAX_PATHNAME_LEN - 1);
+ tmy_fill_path_info(&r);
+ break;
+ }
+ }
+
+ /* Check execute permission. */
+ retval = tmy_exec_perm(&r, filp);
+ if (retval < 0)
+ goto out;
+
+ /* Allocate memory for calcurating domain name. */
+ retval = -ENOMEM;
+ new_domain_name = tmy_alloc(TMY_MAX_PATHNAME_LEN + 16);
+ if (!new_domain_name)
+ goto out;
+
+ if (tmy_is_domain_initializer(old_domain->domainname, &r, &l))
+ /* Transit to the child of KERNEL_DOMAIN domain. */
+ snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+ TMY_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,
+ * for they might start before /sbin/init .
+ */
+ domain = old_domain;
+ else if (tmy_is_domain_keeper(old_domain->domainname, &r, &l))
+ /* Keep current domain. */
+ domain = old_domain;
+ else
+ /* Normal domain transition. */
+ snprintf(new_domain_name,
+ TMY_MAX_PATHNAME_LEN + 1,
+ "%s %s",
+ old_domain_name,
+ real_program_name);
+
+ if (domain || strlen(new_domain_name) >= TMY_MAX_PATHNAME_LEN)
+ goto ok;
+
+ if (is_enforce) {
+ domain = tmy_find_domain(new_domain_name);
+ if (!domain &&
+ tmy_supervisor("#Need to create domain\n%s\n",
+ new_domain_name) == 0) {
+ const u8 profile = TMY_SECURITY->domain->profile;
+ domain = tmy_new_domain(new_domain_name, profile);
+ }
+ } else {
+ const u8 profile = TMY_SECURITY->domain->profile;
+ domain = tmy_new_domain(new_domain_name, profile);
+ }
+
+ok: ;
+
+ if (!domain) {
+ printk(KERN_INFO "TOMOYO-ERROR: Domain '%s' not defined.\n",
+ new_domain_name);
+ if (is_enforce)
+ retval = -EPERM;
+ } else
+ retval = 0;
+out: ;
+
+ tmy_free(new_domain_name);
+ tmy_free(real_program_name);
+ tmy_free(symlink_program_name);
+ *next_domain = domain ? domain : old_domain;
+
+ return retval;
+}
This patch makes access logs sent to auditing subsystem.
TOMOYO Linux uses two channels for auditing.
One is 'AUDIT_TMY_GRANTED', used for auditing accesses which are
granted in the TOMOYO Linux policy.
The other is 'AUDIT_TMY_REJECTED', used for auditing accesses which
are not granted in the TOMOYO Linux policy.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/audit.h | 3 ++
security/tomoyo/audit.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/audit.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,68 @@
+/*
+ * security/tomoyo/audit.c
+ *
+ * Audit functions for TOMOYO Linux
+ */
+
+#include "tomoyo.h"
+#include <linux/audit.h>
+
+/**
+ * tmy_init_audit_log - allocate and initialize audit buffer.
+ * @len: pointer to length of requested size.
+ *
+ * Returns pointer to audit buffer on success. @len received allocated size.
+ * Returns NULL on failure.
+ *
+ * @len must not be a NULL.
+ */
+char *tmy_init_audit_log(int *len)
+{
+ char *buf;
+ struct task_struct *task = current;
+ const char *domainname = TMY_SECURITY->domain->domainname->name;
+
+ *len += strlen(domainname) + 256;
+ buf = tmy_alloc(*len);
+
+ if (!buf)
+ return NULL;
+
+ snprintf(buf, (*len) - 1,
+ "pid=%d uid=%d gid=%d euid=%d egid=%d "
+ "suid=%d sgid=%d fsuid=%d fsgid=%d : %s : ",
+ task->pid, task->uid, task->gid, task->euid, task->egid,
+ task->suid, task->sgid, task->fsuid, task->fsgid, domainname);
+
+ return buf;
+}
+
+/**
+ * tmy_write_audit_log - write audit log.
+ * @buf: pointer to access log contents.
+ * @is_granted: is the access request granted?
+ * @is_enforce: is the access requested in enforcing mode?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Write audit log.
+ * Caller must allocate @buf with tmy_init_audit_log().
+ */
+int tmy_write_audit_log(char *buf, const u8 is_granted, const u8 is_enforce)
+{
+ struct audit_buffer *ab;
+ int type = is_granted ? AUDIT_TMY_GRANTED : AUDIT_TMY_REJECTED;
+
+ ab = audit_log_start(current->audit_context, GFP_KERNEL, type);
+ if (ab) {
+ const char *msg
+ = is_granted ? "granted" : is_enforce ?
+ "error" : "warning";
+ audit_log_format(ab, "TOMOYO %s: %s", msg, buf);
+ audit_log_end(ab);
+ }
+
+ tmy_free(buf);
+ return ab ? 0 : -ENOMEM;
+}
--- linux-2.6.orig/include/linux/audit.h 2007-10-02 11:11:52.000000000 +0900
+++ linux-2.6/include/linux/audit.h 2007-10-02 11:26:22.000000000 +0900
@@ -120,6 +120,9 @@
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_TMY_GRANTED 2001 /* TOMOYO Linux audit granted */
+#define AUDIT_TMY_REJECTED 2002 /* TOMOYO Linux audit rejected */
+
/* Rule flags */
#define AUDIT_FILTER_USER 0x00 /* Apply rule to user-generated messages */
#define AUDIT_FILTER_TASK 0x01 /* Apply rule at task creation (not syscall) */
File access control functions for TOMOYO Linux.
TOMOYO Linux checks permission in
open/creat/unlink/truncate/ftruncate/mknod/mkdir/
rmdir/symlink/link/rename/uselib/sysctl .
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/file.c | 1544 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1544 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/file.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,1544 @@
+/*
+ * security/tomoyo/file.c
+ *
+ * File access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/************************* VARIABLES *************************/
+
+/***** The structure for globally readable files. *****/
+
+struct globally_readable_file_entry {
+ struct globally_readable_file_entry *next;
+ const struct path_info *filename;
+ u8 is_deleted;
+};
+
+/***** The structure for filename patterns. *****/
+
+struct pattern_entry {
+ struct pattern_entry *next;
+ const struct path_info *pattern;
+ u8 is_deleted;
+};
+
+/***** The structure for non-rewritable-by-default file patterns. *****/
+
+struct no_rewrite_entry {
+ struct no_rewrite_entry *next;
+ const struct path_info *pattern;
+ u8 is_deleted;
+};
+
+/***** The structure for detailed write operations. *****/
+
+static struct {
+ const char *keyword;
+ const int paths;
+} acl_type_array[] = {
+ { "create", 1 }, /* TMY_TYPE_CREATE_ACL */
+ { "unlink", 1 }, /* TMY_TYPE_UNLINK_ACL */
+ { "mkdir", 1 }, /* TMY_TYPE_MKDIR_ACL */
+ { "rmdir", 1 }, /* TMY_TYPE_RMDIR_ACL */
+ { "mkfifo", 1 }, /* TMY_TYPE_MKFIFO_ACL */
+ { "mksock", 1 }, /* TMY_TYPE_MKSOCK_ACL */
+ { "mkblock", 1 }, /* TMY_TYPE_MKBLOCK_ACL */
+ { "mkchar", 1 }, /* TMY_TYPE_MKCHAR_ACL */
+ { "truncate", 1 }, /* TMY_TYPE_TRUNCATE_ACL */
+ { "symlink", 1 }, /* TMY_TYPE_SYMLINK_ACL */
+ { "link", 2 }, /* TMY_TYPE_LINK_ACL */
+ { "rename", 2 }, /* TMY_TYPE_RENAME_ACL */
+ { "rewrite", 1 }, /* TMY_TYPE_REWRITE_ACL */
+ { NULL, 0 }
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_acltype2keyword - get keyword from access control index.
+ * @acl_type: index number.
+ *
+ * Returns keyword that corresponds with @acl_type .
+ */
+const char *tmy_acltype2keyword(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].keyword : NULL;
+}
+
+/**
+ * tmy_acltype2paths - get number of arguments from access control index.
+ * @acl_type: index number.
+ *
+ * Returns number of arguments that corresponds with @acl_type .
+ */
+int tmy_acltype2paths(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].paths : 0;
+}
+
+static int tmy_strendswith(const char *name, const char *tail)
+{
+ int len;
+
+ if (!name || !tail)
+ return 0;
+
+ len = strlen(name) - strlen(tail);
+ return len >= 0 && strcmp(name + len, tail) == 0;
+}
+
+static struct path_info *tmy_get_path(struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ /* sizeof(struct path_info_with_data) <= PAGE_SIZE */
+ struct path_info_with_data {
+ /* Keep this first, this pointer is passed to tmy_free(). */
+ struct path_info head;
+ char bariier1[16];
+ char body[TMY_MAX_PATHNAME_LEN];
+ char barrier2[16];
+ } *buf = tmy_alloc(sizeof(*buf));
+
+ if (buf) {
+ int error = tmy_realpath_dentry2(dentry,
+ mnt,
+ buf->body,
+ sizeof(buf->body) - 1);
+
+ if (error == 0) {
+ buf->head.name = buf->body;
+ tmy_fill_path_info(&buf->head);
+ return &buf->head;
+ }
+
+ tmy_free(buf);
+ buf = NULL;
+ printk(KERN_INFO "tmy_realpath_dentry = %d\n", error);
+ }
+
+ return NULL;
+}
+
+/************************* PROTOTYPES *************************/
+
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_file_log(const struct path_info *filename,
+ const u8 perm,
+ const u8 is_granted,
+ const u8 is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = filename->total_len + 8;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ "%d %s",
+ perm,
+ filename->name);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+static int tmy_audit_write_log(const char *operation,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ const u8 is_granted,
+ const u8 is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = strlen(operation) +
+ filename1->total_len +
+ (filename2 ? filename2->total_len : 0)
+ + 16;
+
+ buf = tmy_init_audit_log(&len);
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ "allow_%s %s %s",
+ operation, filename1->name, filename2 ? filename2->name : "");
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/********************** GLOBALLY READABLE FILE HANDLER **********************/
+
+static struct globally_readable_file_entry *globally_readable_list;
+
+static int tmy_add_globally_readable_entry(const char *filename,
+ const u8 is_delete)
+{
+ struct globally_readable_file_entry *new_entry;
+ struct globally_readable_file_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(filename, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = globally_readable_list; ptr; ptr = ptr->next) {
+ if (ptr->filename == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->filename = saved;
+ mb(); /* Instead of using spinlock. */
+ ptr = globally_readable_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ globally_readable_list = new_entry;
+
+ error = 0;
+
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+static int tmy_globally_readable(const struct path_info *filename)
+{
+ struct globally_readable_file_entry *ptr;
+
+ for (ptr = globally_readable_list; ptr; ptr = ptr->next) {
+ if (!ptr->is_deleted &&
+ !tmy_pathcmp(filename, ptr->filename))
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_globally_readable_policy - add or delete globally readable policy.
+ * @filename: pointer to filename to add ore remove.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_globally_readable_policy(char *filename, const u8 is_delete)
+{
+ return tmy_add_globally_readable_entry(filename, is_delete);
+}
+
+/**
+ * tmy_read_globally_readable_policy - read globally readable policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_globally_readable_policy(struct io_buffer *head)
+{
+ struct globally_readable_file_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = globally_readable_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_ALLOW_READ "%s\n",
+ ptr->filename->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/************************* FILE GROUP HANDLER *************************/
+
+static struct group_entry *group_list;
+
+static int tmy_add_group_entry(const char *group_name,
+ const char *member_name,
+ const u8 is_delete)
+{
+ static DECLARE_MUTEX(lock);
+ struct group_entry *new_group;
+ struct group_entry *group;
+ struct group_member *new_member;
+ struct group_member *member;
+ const struct path_info *saved_group;
+ const struct path_info *saved_member;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+ !group_name[0] ||
+ !tmy_correct_path(member_name, 0, 0, 0, __FUNCTION__) ||
+ !member_name[0])
+ return -EINVAL;
+
+ saved_group = tmy_save_name(group_name);
+ saved_member = tmy_save_name(member_name);
+
+ if (!saved_group || !saved_member)
+ return -ENOMEM;
+
+ down(&lock);
+ for (group = group_list; group; group = group->next) {
+ if (saved_group != group->group_name)
+ continue;
+
+ for (member = group->first_member;
+ member;
+ member = member->next) {
+ if (member->member_name == saved_member) {
+ member->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ break;
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ if (!group) {
+ new_group = tmy_alloc_element(sizeof(*new_group));
+ if (!new_group)
+ goto out;
+ new_group->group_name = saved_group;
+ mb(); /* Instead of using spinlock. */
+ group = group_list;
+
+ if (group) {
+ while (group->next)
+ group = group->next;
+ group->next = new_group;
+ } else
+ group_list = new_group;
+
+ group = new_group;
+ }
+
+ new_member = tmy_alloc_element(sizeof(*new_member));
+ if (!new_member)
+ goto out;
+
+ new_member->member_name = saved_member;
+ mb(); /* Instead of using spinlock. */
+ member = group->first_member;
+
+ if (member) {
+ while (member->next)
+ member = member->next;
+ member->next = new_member;
+ } else
+ group->first_member = new_member;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_add_group_policy - add or delete path group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_group_policy(char *data, const u8 is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+ return tmy_add_group_entry(data, cp, is_delete);
+}
+
+static struct group_entry *tmy_new_path_group(const char *group_name)
+{
+ int i;
+ struct group_entry *group;
+
+ for (i = 0; i <= 1; i++) {
+ for (group = group_list; group; group = group->next) {
+ if (strcmp(group_name, group->group_name->name) == 0)
+ return group;
+ }
+
+ if (i == 0) {
+ /*
+ * Add a dummy entry to create new path group
+ * and delete that entry.
+ */
+ tmy_add_group_entry(group_name, "/", 0);
+ tmy_add_group_entry(group_name, "/", 1);
+ }
+ }
+
+ return NULL;
+}
+
+static int tmy_path_match_group(const struct path_info *pathname,
+ const struct group_entry *group,
+ const int may_use_pattern)
+{
+ struct group_member *member;
+
+ for (member = group->first_member; member; member = member->next) {
+ if (member->is_deleted)
+ continue;
+
+ if (!member->member_name->is_patterned) {
+ if (!tmy_pathcmp(pathname, member->member_name))
+ return 1;
+ } else if (may_use_pattern) {
+ if (tmy_path_match(pathname, member->member_name))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_read_group_policy - read path group policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_group_policy(struct io_buffer *head)
+{
+ struct group_entry *group = head->read_var1;
+ struct group_member *member = head->read_var2;
+
+ if (!group)
+ group = group_list;
+
+ while (group) {
+ head->read_var1 = group;
+ if (!member)
+ member = group->first_member;
+ while (member) {
+ head->read_var2 = member;
+ if (!member->is_deleted &&
+ tmy_io_printf(head,
+ TMY_PATH_GROUP "%s %s\n",
+ group->group_name->name,
+ member->member_name->name))
+ break;
+ member = member->next;
+ }
+
+ if (member)
+ break;
+
+ head->read_var2 = NULL;
+ group = group->next;
+ }
+
+ return group ? -ENOMEM : 0;
+}
+
+/************************* FILE PATTERN HANDLER *************************/
+
+static struct pattern_entry *pattern_list;
+
+static int tmy_add_pattern_entry(const char *pattern, const u8 is_delete)
+{
+ struct pattern_entry *new_entry;
+ struct pattern_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 1, 0, __FUNCTION__))
+ return -EINVAL;
+
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = pattern_list; ptr; ptr = ptr->next) {
+ if (saved == ptr->pattern) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+
+ if (!new_entry)
+ goto out;
+ new_entry->pattern = saved;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = pattern_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ pattern_list = new_entry;
+
+ error = 0;
+out: ;
+ up(&lock);
+ return error;
+}
+
+static const struct path_info *tmy_get_pattern(const struct path_info *filename)
+{
+ struct pattern_entry *ptr;
+ const struct path_info *pattern = NULL;
+
+ for (ptr = pattern_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+
+ pattern = ptr->pattern;
+ if (!tmy_strendswith(pattern->name, "/\\*"))
+ break;
+ }
+
+ if (pattern)
+ filename = pattern;
+
+ return filename;
+}
+
+/**
+ * tmy_add_pattern_policy - add or delete file pattern policy.
+ * @pattern: pointer to file pattern entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pattern_policy(char *pattern, const u8 is_delete)
+{
+ return tmy_add_pattern_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_pattern_policy - read file pattern policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pattern_policy(struct io_buffer *head)
+{
+ struct pattern_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = pattern_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_FILE_PATTERN "%s\n",
+ ptr->pattern->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/*********************** NON REWRITABLE FILE HANDLER ***********************/
+
+static struct no_rewrite_entry *no_rewrite_list;
+
+static int tmy_add_no_rewrite_entry(const char *pattern, const u8 is_delete)
+{
+ struct no_rewrite_entry *new_entry;
+ struct no_rewrite_entry *ptr;
+ static DECLARE_MUTEX(lock);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ down(&lock);
+ for (ptr = no_rewrite_list; ptr; ptr = ptr->next) {
+ if (ptr->pattern == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->pattern = saved;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = no_rewrite_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ no_rewrite_list = new_entry;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+static int tmy_is_no_rewrite_file(const struct path_info *filename)
+{
+ struct no_rewrite_entry *ptr;
+
+ for (ptr = no_rewrite_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_no_rewrite_policy - add or delete no-rewrite policy.
+ * @pattern: pointer to no-rewrite entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_rewrite_policy(char *pattern, const u8 is_delete)
+{
+ return tmy_add_no_rewrite_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_no_rewrite_policy - read no-rewrite policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_rewrite_policy(struct io_buffer *head)
+{
+ struct no_rewrite_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = no_rewrite_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (!ptr->is_deleted &&
+ tmy_io_printf(head, TMY_DENY_REWRITE "%s\n",
+ ptr->pattern->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/************************* FILE ACL HANDLER *************************/
+
+static int tmy_add_file_acl(const char *filename,
+ u8 perm,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (perm > 7 || !perm) {
+ printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+ __FUNCTION__, perm, filename);
+ return -EINVAL;
+ }
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+
+ if (tmy_strendswith(filename, "/"))
+ /*
+ * Valid permissions for directory are
+ * only 'allow_mkdir' and 'allow_rmdir'.
+ */
+ return 0;
+
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ if (!is_delete && perm == 4 &&
+ tmy_globally_readable(saved))
+ return 0;
+
+ }
+
+ down(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if ((ptr->type == TMY_TYPE_FILE_ACL) &&
+ ptr->cond == cond &&
+ (acl->u.filename == saved)) {
+ if (ptr->is_deleted) {
+ acl->perm = 0;
+ mb(); /* Instead of using spinlock. */
+ ptr->is_deleted = 0;
+ }
+ /* Found. Just 'OR' the permission bits. */
+ acl->perm |= perm;
+ error = 0;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_FILE_ACL;
+ acl->head.cond = cond;
+ acl->perm = perm;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->cond != cond ||
+ ptr->is_deleted ||
+ acl->perm != perm ||
+ acl->u.filename != saved)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_file_acl(const struct path_info *filename, const u8 perm,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const int may_use_pat = ((perm & 1) == 0);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ if (!filename->is_dir) {
+ if (perm == 4 && tmy_globally_readable(filename))
+ return 0;
+ }
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct file_acl *acl = (struct file_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->is_deleted ||
+ (acl->perm & perm) != perm ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (tmy_path_match_group(filename,
+ acl->u.group,
+ may_use_pat))
+ return 0;
+ } else {
+ if ((may_use_pat || !acl->u.filename->is_patterned) &&
+ tmy_path_match(filename, acl->u.filename))
+ return 0;
+ }
+ }
+
+ return -EPERM;
+}
+
+static int tmy_file_perm2(const struct path_info *filename,
+ const u8 perm,
+ struct obj_info *obj,
+ const char *operation)
+{
+ int error = 0;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!filename)
+ return 0;
+
+ error = tmy_file_acl(filename, perm, obj);
+
+ tmy_audit_file_log(filename, perm, !error, is_enforce);
+
+ if (!error)
+ return error;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\n%d %s\n",
+ domain->domainname->name,
+ perm, filename->name);
+
+ else if (tmy_accept(TMY_MAC_FOR_FILE, domain)) {
+ /* Don't use patterns if execution bit is on. */
+ const struct path_info *patterned =
+ ((perm & 1) == 0) ?
+ tmy_get_pattern(filename) : filename;
+ tmy_add_file_acl(patterned->name, perm, domain, NULL, 0);
+ }
+
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_file_perm - check permission for sysctl(2) operation.
+ * @filename0: pointer to filename returned by sysctlpath_from_table().
+ * @perm: mode (read = 4, write = 2, read-write = 6).
+ * @operation: pointer to error message.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation)
+{
+ struct path_info filename;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ filename.name = filename0;
+ tmy_fill_path_info(&filename);
+
+ return tmy_file_perm2(&filename, perm, NULL, operation);
+}
+
+/**
+ * tmy_add_file_policy - add or delete file policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_file_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ char *filename = strchr(data, ' ');
+ unsigned int perm;
+ u8 type;
+
+ if (!filename)
+ return -EINVAL;
+ *filename++ = '\0';
+
+ if (sscanf(data, "%u", &perm) == 1)
+ return tmy_add_file_acl(filename, (u8) perm, domain, cond,
+ is_delete);
+
+ if (strncmp(data, "allow_", 6))
+ goto out;
+
+ data += 6;
+
+ for (type = 0; acl_type_array[type].keyword; type++) {
+ if (strcmp(data, acl_type_array[type].keyword))
+ continue;
+
+ if (acl_type_array[type].paths == 2) {
+ char *filename2 = strchr(filename, ' ');
+
+ if (!filename2)
+ break;
+ *filename2++ = '\0';
+ return tmy_add_double_write_acl(type, filename,
+ filename2,
+ domain, cond,
+ is_delete);
+ } else
+ return tmy_add_single_write_acl(type, filename,
+ domain, cond,
+ is_delete);
+
+ break;
+ }
+out: ;
+ return -EINVAL;
+}
+
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+ }
+
+ down(&domain_acl_lock);
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u.filename == saved) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+ }
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond || acl->u.filename != saved)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ const struct path_info *saved1;
+ const struct path_info *saved2;
+ struct acl_info *ptr;
+ int error = -ENOMEM;
+ u8 is_group1 = 0;
+ u8 is_group2 = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename1, 0, 0, 0, __FUNCTION__) ||
+ !tmy_correct_path(filename2, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename1[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved1 = (struct path_info *) tmy_new_path_group(filename1 + 1);
+ if (!saved1)
+ return -ENOMEM;
+ is_group1 = 1;
+ } else {
+ saved1 = tmy_save_name(filename1);
+ if (!saved1)
+ return -ENOMEM;
+ }
+
+ if (filename2[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved2 = (struct path_info *) tmy_new_path_group(filename2 + 1);
+ if (!saved2)
+ return -ENOMEM;
+ is_group2 = 1;
+ } else {
+ saved2 = tmy_save_name(filename2);
+ if (!saved2)
+ return -ENOMEM;
+ }
+
+ down(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u1.filename1 == saved1 &&
+ acl->u2.filename2 == saved2) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u1_is_group = is_group1;
+ acl->u2_is_group = is_group2;
+ acl->u1.filename1 = saved1;
+ acl->u2.filename2 = saved2;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond ||
+ acl->u1.filename1 != saved1 ||
+ acl->u2.filename2 != saved2)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_single_write_acl(const u8 type,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct single_acl *acl = (struct single_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (!tmy_path_match_group(filename, acl->u.group, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename, acl->u.filename))
+ continue;
+ }
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_double_write_acl(const u8 type,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct double_acl *acl = (struct double_acl *) ptr;
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u1_is_group) {
+ if (!tmy_path_match_group(filename1,
+ acl->u1.group1, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename1, acl->u1.filename1))
+ continue;
+ }
+
+ if (acl->u2_is_group) {
+ if (!tmy_path_match_group(filename2,
+ acl->u2.group2, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename2, acl->u2.filename2))
+ continue;
+ }
+
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_single_write_perm2(const unsigned int operation,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ int error;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ error = tmy_single_write_acl(operation, filename, obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ filename, NULL, !error, is_enforce);
+
+ if (!error)
+ goto ok;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ filename->name);
+
+ else if (tmy_accept(TMY_MAC_FOR_FILE, domain))
+ tmy_add_single_write_acl(operation,
+ tmy_get_pattern(filename)->name,
+ domain, NULL, 0);
+
+ if (!is_enforce)
+ error = 0;
+
+ok: ;
+ if (!error && operation == TMY_TYPE_TRUNCATE_ACL &&
+ tmy_is_no_rewrite_file(filename))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ filename, obj);
+
+ return error;
+}
+
+/**
+ * tmy_exec_perm - check permission for execve(2) operation.
+ * @filename: pointer to filename to execute.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_exec_perm(const struct path_info *filename, struct file *filp)
+{
+ struct obj_info obj;
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ return tmy_file_perm2(filename, 1, &obj, "do_execve");
+}
+
+/**
+ * tmy_open_perm - check permission for open(2) operation.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ * @flag: open flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_open_perm(struct dentry *dentry,
+ struct vfsmount *mnt,
+ const int flag)
+{
+ struct obj_info obj;
+ const int acc_mode = ACC_MODE(flag);
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ 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 = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ error = 0;
+ if ((acc_mode & MAY_WRITE) &&
+ ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
+ tmy_is_no_rewrite_file(buf))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+
+ if (error == 0)
+ error = tmy_file_perm2(buf, acc_mode, &obj, "open");
+
+ if (error == 0 && (flag & O_TRUNC))
+ error = tmy_single_write_perm2(TMY_TYPE_TRUNCATE_ACL,
+ buf, &obj);
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_single_write_perm - check permission for create(2) etc. operation.
+ * @operation: operation index number.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_single_write_perm(const unsigned int operation,
+ struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ buf = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ switch (operation) {
+ case TMY_TYPE_MKDIR_ACL:
+ case TMY_TYPE_RMDIR_ACL:
+ if (!buf->is_dir) {
+ strcat((char *) buf->name, "/");
+ tmy_fill_path_info(buf);
+ }
+ }
+ error = tmy_single_write_perm2(operation, buf, &obj);
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_rewrite_perm - check permission for truncate/overwrite operation.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_rewrite_perm(struct file *filp)
+{
+ int error = -ENOMEM;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+ struct path_info *buf = tmy_get_path(filp->f_dentry, filp->f_vfsmnt);
+
+ if (!buf)
+ goto out;
+
+ if (tmy_is_no_rewrite_file(buf)) {
+ struct obj_info obj;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+ } else
+ error = 0;
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_double_write_perm - check permission for link(2)/rename(2) operation.
+ * @operation: operation index number.
+ * @dentry1: pointer to "struct dentry".
+ * @mnt1: pointer to "struct vfsmount".
+ * @dentry2: pointer to "struct dentry".
+ * @mnt2: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_double_write_perm(const unsigned int operation,
+ struct dentry *dentry1,
+ struct vfsmount *mnt1,
+ struct dentry *dentry2,
+ struct vfsmount *mnt2)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf1;
+ struct path_info *buf2;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ buf1 = tmy_get_path(dentry1, mnt1);
+ buf2 = tmy_get_path(dentry2, mnt2);
+
+ if (!buf1 || !buf2)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry1;
+ obj.path1_vfsmnt = mnt1;
+ obj.path2_dentry = dentry2;
+ obj.path2_vfsmnt = mnt2;
+ if (operation == TMY_TYPE_RENAME_ACL) {
+ /* TMY_TYPE_LINK_ACL can't reach here for directory. */
+ if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
+ if (!buf1->is_dir) {
+ strcat((char *) buf1->name, "/");
+ tmy_fill_path_info(buf1);
+ }
+ if (!buf2->is_dir) {
+ strcat((char *) buf2->name, "/");
+ tmy_fill_path_info(buf2);
+ }
+ }
+ }
+ error = tmy_double_write_acl(operation, buf1, buf2, &obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ buf1, buf2, !error, is_enforce);
+
+ if (!error)
+ goto out;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ buf1->name, buf2->name);
+ else if (tmy_accept(TMY_MAC_FOR_FILE, domain))
+ tmy_add_double_write_acl(operation,
+ tmy_get_pattern(buf1)->name,
+ tmy_get_pattern(buf2)->name,
+ domain, NULL, 0);
+
+out: ;
+ tmy_free(buf1);
+ tmy_free(buf2);
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
argv[0] check functions for TOMOYO Linux.
If the executed program name and argv[0] is different,
TOMOYO Linux checks permission.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/exec.c | 222 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 222 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/exec.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,222 @@
+/*
+ * security/tomoyo/exec.c
+ *
+ * Argv0 access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_argv0_log(const struct path_info *filename,
+ const char *argv0,
+ const u8 is_granted,
+ const u8 is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = filename->total_len + strlen(argv0) + 8;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ TMY_ALLOW_ARGV0 "%s %s",
+ filename->name,
+ argv0);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* ARGV0 MISMATCH HANDLER *************************/
+
+static int tmy_add_argv0_entry(const char *filename,
+ const char *argv0,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ struct acl_info *ptr;
+ const struct path_info *saved_filename;
+ const struct path_info *saved_argv0;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(filename, 1, 0, -1, __FUNCTION__) ||
+ !tmy_correct_path(argv0, -1, 0, -1, __FUNCTION__) ||
+ strchr(argv0, '/'))
+ return -EINVAL;
+
+ saved_filename = tmy_save_name(filename);
+ saved_argv0 = tmy_save_name(argv0);
+ if (!saved_filename || !saved_argv0)
+ return -ENOMEM;
+
+ down(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+ while (1) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_ARGV0_ACL && ptr->cond == cond &&
+ acl->filename == saved_filename &&
+ acl->argv0 == saved_argv0) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_ARGV0_ACL;
+ acl->head.cond = cond;
+ acl->filename = saved_filename;
+ acl->argv0 = saved_argv0;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_ARGV0_ACL ||
+ ptr->cond != cond || ptr->is_deleted ||
+ acl->filename != saved_filename ||
+ acl->argv0 != saved_argv0)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+static int tmy_argv0_acl(const struct path_info *filename,
+ const char *argv0_)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ int error = -EPERM;
+ struct acl_info *ptr;
+ struct path_info argv0;
+
+ if (!tmy_flags(TMY_MAC_FOR_ARGV0))
+ return 0;
+
+ argv0.name = argv0_;
+ tmy_fill_path_info(&argv0);
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_ARGV0_ACL &&
+ ptr->is_deleted == 0 &&
+ tmy_check_condition(ptr->cond, NULL) == 0 &&
+ tmy_path_match(filename, acl->filename) &&
+ tmy_path_match(&argv0, acl->argv0)) {
+ error = 0;
+ break;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * tmy_argv0_perm - check for argv[0] permission.
+ * @filename: pointer to filename.
+ * @argv0: pointer to basename of argv[0].
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0)
+{
+ int error = 0;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_ARGV0);
+
+ if (!tmy_flags(TMY_MAC_FOR_ARGV0))
+ return 0;
+ if (!filename || !argv0 || !*argv0)
+ return 0;
+
+ error = tmy_argv0_acl(filename, argv0);
+
+ tmy_audit_argv0_log(filename, argv0, !error, is_enforce);
+
+ if (error) {
+ struct domain_info * const domain = TMY_SECURITY->domain;
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\n" TMY_ALLOW_ARGV0 "%s %s\n",
+ domain->domainname->name,
+ filename->name, argv0);
+
+ else if (tmy_accept(TMY_MAC_FOR_ARGV0, domain))
+ tmy_add_argv0_entry(filename->name, argv0, domain,
+ NULL, 0);
+
+ if (!is_enforce)
+ error = 0;
+ }
+
+ return error;
+}
+
+/**
+ * tmy_add_argv0_policy - add or delete argv[0] policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_argv0_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ char *argv0 = strchr(data, ' ');
+
+ if (!argv0)
+ return -EINVAL;
+
+ *argv0++ = '\0';
+
+ return tmy_add_argv0_entry(data, argv0, domain, cond,
+ is_delete);
+}
Network access control functions for TOMOYO Linux.
TOMOYO Linux checks permission by the following four parameters.
* protocol type (TCP, UDP, RAW)
* access type (bind, listen, connect, accept)
* IP address (Both IPv4 and IPv6 are available)
* port number
In order to check 'TCP accept' and 'UDP connect',
LSM expansion patch ([TOMOYO 14/15]) is needed.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/net.c | 975 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 975 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/net.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,975 @@
+/*
+ * security/tomoyo/net.c
+ *
+ * Network access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <net/ip.h>
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_network_log(const u8 is_ipv6,
+ const char *operation,
+ const u32 *address,
+ const u16 port,
+ const u8 is_granted,
+ const u8 is_enforce)
+{
+ char *buf;
+ int len = 256;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf = tmy_init_audit_log(&len);
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ TMY_ALLOW_NETWORK "%s ", operation);
+
+ if (is_ipv6)
+ tmy_print_ipv6(buf + strlen(buf), len - strlen(buf),
+ (const u16 *) address);
+ else {
+ u32 ip = *address;
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+ NIPQUAD_FMT, NIPQUAD(ip));
+ }
+
+ snprintf(buf + strlen(buf), len - strlen(buf) - 1, " %u", port);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* ADDRESS GROUP HANDLER *************************/
+
+/* List of address group. */
+static struct addr_group_entry *group_list;
+
+static int tmy_add_addr_group_entry(const char *group_name,
+ const u8 is_ipv6,
+ const u16 *min_address,
+ const u16 *max_address,
+ const u8 is_delete)
+{
+ static DECLARE_MUTEX(lock);
+ struct addr_group_entry *new_group;
+ struct addr_group_entry *group;
+ struct addr_group_member *new_member;
+ struct addr_group_member *member;
+ const struct path_info *saved_group_name;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+ !group_name[0])
+ return -EINVAL;
+
+ saved_group_name = tmy_save_name(group_name);
+ if (!saved_group_name)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (group = group_list; group; group = group->next) {
+
+ if (saved_group_name != group->group_name)
+ continue;
+
+ for (member = group->first_member;
+ member;
+ member = member->next) {
+
+ if (member->is_ipv6 != is_ipv6)
+ continue;
+
+ if (is_ipv6) {
+ if (memcmp(member->min.ipv6, min_address, 16) ||
+ memcmp(member->max.ipv6, max_address, 16))
+ 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;
+ }
+ break;
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ if (!group) {
+ new_group = tmy_alloc_element(sizeof(*new_group));
+ if (!new_group)
+ goto out;
+ new_group->group_name = saved_group_name;
+ mb(); /* Instead of using spinlock. */
+ group = group_list;
+ if (group) {
+ while (group->next)
+ group = group->next;
+ group->next = new_group;
+ } else
+ group_list = new_group;
+ group = new_group;
+ }
+
+ new_member = tmy_alloc_element(sizeof(*new_member));
+ if (!new_member)
+ goto out;
+
+ new_member->is_ipv6 = is_ipv6;
+
+ if (is_ipv6) {
+ memmove(new_member->min.ipv6, min_address, 16);
+ memmove(new_member->max.ipv6, max_address, 16);
+ } else {
+ new_member->min.ipv4 = *(u32 *) min_address;
+ new_member->max.ipv4 = *(u32 *) max_address;
+ }
+
+ mb(); /* Instead of using spinlock. */
+
+ member = group->first_member;
+ if (member) {
+ while (member->next)
+ member = member->next;
+ member->next = new_member;
+ } else
+ group->first_member = new_member;
+
+ error = 0;
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_add_addr_group_policy - add or delete address group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_addr_group_policy(char *data, const u8 is_delete)
+{
+ int count;
+ u8 is_ipv6;
+ u16 min_address[8];
+ u16 max_address[8];
+ unsigned int min[8];
+ unsigned int max[8];
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+
+ *cp++ = '\0';
+ count = sscanf(cp,
+ NIP6_FMT "-" NIP6_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &min[4], &min[5], &min[6], &min[7],
+ &max[0], &max[1], &max[2], &max[3],
+ &max[4], &max[5], &max[6], &max[7]);
+
+ if (count == 8 || count == 16) {
+
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ min_address[i] = htons((u16) min[i]);
+ max_address[i] = htons((u16) max[i]);
+ }
+ if (count == 8)
+ memmove(max_address, min_address, sizeof(min_address));
+ is_ipv6 = 1;
+
+ goto ok;
+
+ }
+
+ count = sscanf(cp,
+ NIPQUAD_FMT "-" NIPQUAD_FMT,
+ &min[0], &min[1],
+ &min[2], &min[3],
+ &max[0], &max[1],
+ &max[2], &max[3]);
+
+ if (count == 4 || count == 8) {
+ u32 ip = ((((u8) min[0]) << 24) +
+ (((u8) min[1]) << 16) +
+ (((u8) min[2]) << 8) +
+ (u8) min[3]);
+
+ *(u32 *) min_address = ip;
+
+ if (count == 8)
+ ip = ((((u8) max[0]) << 24) +
+ (((u8) max[1]) << 16) +
+ (((u8) max[2]) << 8) +
+ (u8) max[3]);
+
+ *(u32 *) max_address = ip;
+ is_ipv6 = 0;
+
+ goto ok;
+
+ }
+
+ return -EINVAL;
+
+ok: ;
+ return tmy_add_addr_group_entry(data, is_ipv6, min_address, max_address,
+ is_delete);
+}
+
+static struct addr_group_entry *tmy_new_addr_group(const char *name)
+{
+ int i;
+ struct addr_group_entry *group;
+
+ for (i = 0; i <= 1; i++) {
+ for (group = group_list; group; group = group->next) {
+
+ if (strcmp(name, group->group_name->name) == 0)
+ return group;
+
+ }
+
+ if (i == 0) {
+ /*
+ * Add a dummy entry to create new address group
+ * and delete that entry.
+ */
+ const u16 dum[2] = { 0, 0 };
+ tmy_add_addr_group_entry(name, 0, dum, dum, 0);
+ tmy_add_addr_group_entry(name, 0, dum, dum, 1);
+ }
+ }
+
+ return NULL;
+}
+
+static int tmy_address_match_group(const u8 is_ipv6,
+ const u32 *address,
+ const struct addr_group_entry *group)
+{
+ struct addr_group_member *member;
+ const u32 ip = ntohl(*address);
+
+ for (member = group->first_member; member; member = member->next) {
+
+ 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 1;
+
+ } else {
+
+ if (!is_ipv6 &&
+ member->min.ipv4 <= ip &&
+ ip <= member->max.ipv4)
+ return 1;
+
+ }
+ }
+
+ return 0;
+}
+
+static int tmy_read_addr_group(struct io_buffer *head,
+ struct addr_group_entry *group,
+ struct addr_group_member *member)
+{
+ char buf[128];
+ if (!member)
+ return 0;
+
+ if (member->is_ipv6) {
+
+ const u16 *min_addr = member->min.ipv6;
+ const u16 *max_addr = member->max.ipv6;
+
+ tmy_print_ipv6(buf, sizeof(buf), min_addr);
+
+ if (memcmp(min_addr, max_addr, 16)) {
+ char *cp = strchr(buf, '\0');
+ int len = sizeof(buf) - strlen(buf);
+
+ *cp++ = '-';
+ tmy_print_ipv6(cp, len, max_addr);
+ }
+
+ } else {
+
+ const u32 min_addr = member->min.ipv4;
+ const u32 max_addr = member->max.ipv4;
+
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf) - 1,
+ NIPQUAD_FMT, HIPQUAD(min_addr));
+
+ if (min_addr != max_addr) {
+ const int len = strlen(buf);
+
+ snprintf(buf + len, sizeof(buf) - 1 - len,
+ "-" NIPQUAD_FMT, HIPQUAD(max_addr));
+ }
+
+ }
+
+ return tmy_io_printf(head, TMY_ADDRESS_GROUP "%s %s\n",
+ group->group_name->name, buf);
+}
+
+/**
+ * tmy_read_addr_group_policy - read address group policy
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_addr_group_policy(struct io_buffer *head)
+{
+ struct addr_group_entry *group = head->read_var1;
+ struct addr_group_member *member = head->read_var2;
+
+ if (!group)
+ group = group_list;
+
+ while (group) {
+ head->read_var1 = group;
+ if (!member)
+ member = group->first_member;
+ while (member) {
+
+ head->read_var2 = member;
+ if (member->is_deleted) {
+ member = member->next;
+ continue;
+ }
+
+ if (tmy_read_addr_group(head, group, member))
+ break;
+
+ member = member->next;
+ }
+ if (member)
+ break;
+ head->read_var2 = NULL;
+ group = group->next;
+ }
+ return group ? -ENOMEM : 0;
+}
+
+/*********************** NETWORK NETWORK ACL HANDLER ***********************/
+
+/**
+ * tmy_print_ipv6 - print ipv6 address
+ * @buffer: pointer to buffer to save the result.
+ * @buffer_len: sizeof @buffer .
+ * @ip: pointer to an IPv6 address in network byte order.
+ *
+ * Returns @buffer .
+ */
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip)
+{
+ memset(buffer, 0, buffer_len);
+ snprintf(buffer, buffer_len - 1, NIP6_FMT,
+ ntohs(ip[0]), ntohs(ip[1]), ntohs(ip[2]), ntohs(ip[3]),
+ ntohs(ip[4]), ntohs(ip[5]), ntohs(ip[6]), ntohs(ip[7]));
+ return buffer;
+}
+
+/**
+ * tmy_network2keyword - get keyword from access control index.
+ * @operation: index number.
+ *
+ * Returns keyword that corresponds with @operation .
+ */
+const char *tmy_network2keyword(const unsigned int operation)
+{
+ const char *keyword = "unknown";
+ switch (operation) {
+ case TMY_NETWORK_ACL_UDP_BIND:
+ keyword = "UDP bind";
+ break;
+ case TMY_NETWORK_ACL_UDP_CONNECT:
+ keyword = "UDP connect";
+ break;
+ case TMY_NETWORK_ACL_TCP_BIND:
+ keyword = "TCP bind";
+ break;
+ case TMY_NETWORK_ACL_TCP_LISTEN:
+ keyword = "TCP listen";
+ break;
+ case TMY_NETWORK_ACL_TCP_CONNECT:
+ keyword = "TCP connect";
+ break;
+ case TMY_NETWORK_ACL_TCP_ACCEPT:
+ keyword = "TCP accept";
+ break;
+ case TMY_NETWORK_ACL_RAW_BIND:
+ keyword = "RAW bind";
+ break;
+ case TMY_NETWORK_ACL_RAW_CONNECT:
+ keyword = "RAW connect";
+ break;
+ }
+ return keyword;
+}
+
+/* Compare IPv4/IPv6 address. */
+static int tmy_cmp_network_entry(const u8 record_type,
+ struct net_acl *acl,
+ const struct addr_group_entry *group,
+ const u32 min_ip,
+ const u32 max_ip,
+ const u32 *min_address,
+ const u32 *max_address)
+{
+ int found = 0;
+
+ switch (record_type) {
+
+ case TMY_TYPE_ADDRESS_GROUP:
+ if (acl->u.group == group)
+ found = 1;
+ break;
+
+ case TMY_TYPE_IPv4:
+ if (acl->u.ipv4.min == min_ip &&
+ max_ip == acl->u.ipv4.max)
+ found = 1;
+ break;
+
+ case TMY_TYPE_IPv6:
+ if (memcmp(acl->u.ipv6.min, min_address, 16) == 0 &&
+ memcmp(max_address, acl->u.ipv6.max, 16) == 0)
+ found = 1;
+ break;
+
+ }
+
+ return found;
+}
+
+static int tmy_add_network_entry(const u8 operation,
+ const u8 record_type,
+ const struct addr_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 *cond,
+ const u8 is_delete)
+{
+ struct acl_info *ptr;
+ 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);
+
+ if (!domain)
+ return -EINVAL;
+
+ down(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_IP_NETWORK_ACL &&
+ ptr->cond == cond &&
+ acl->operation_type == operation &&
+ acl->record_type == record_type &&
+ acl->min_port == min_port &&
+ max_port == acl->max_port &&
+ tmy_cmp_network_entry(record_type, acl,
+ group, min_ip, max_ip,
+ min_address,
+ max_address)) {
+ ptr->is_deleted = 0;
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_IP_NETWORK_ACL;
+ acl->head.cond = cond;
+ acl->operation_type = operation;
+ acl->record_type = record_type;
+
+ if (record_type == TMY_TYPE_ADDRESS_GROUP)
+ acl->u.group = group;
+ else if (record_type == TMY_TYPE_IPv4) {
+ acl->u.ipv4.min = min_ip;
+ acl->u.ipv4.max = max_ip;
+ } else {
+ memmove(acl->u.ipv6.min, min_address, 16);
+ memmove(acl->u.ipv6.max, max_address, 16);
+ }
+
+ acl->min_port = min_port;
+ acl->max_port = max_port;
+ error = tmy_add_acl(ptr, domain,
+ (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+ if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+ ptr->cond != cond ||
+ ptr->is_deleted ||
+ acl->operation_type != operation ||
+ acl->record_type != record_type ||
+ acl->min_port != min_port ||
+ acl->max_port != max_port ||
+ !tmy_cmp_network_entry(record_type, acl,
+ group, min_ip, max_ip,
+ min_address,
+ max_address))
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+/* Check network permission. */
+static int tmy_network_entry(const u8 is_ipv6,
+ const int operation,
+ const u32 *address,
+ const u16 port)
+{
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const char *keyword = tmy_network2keyword(operation);
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_NETWORK);
+ /* using host byte order to allow u32 comparison than memcmp().*/
+ const u32 ip = ntohl(*address);
+
+ if (!tmy_flags(TMY_MAC_FOR_NETWORK))
+ return 0;
+
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct net_acl *acl = (struct net_acl *) ptr;
+
+ if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+ ptr->is_deleted ||
+ acl->operation_type != operation ||
+ port < acl->min_port ||
+ acl->max_port < port ||
+ tmy_check_condition(ptr->cond, NULL))
+ continue;
+
+ if (acl->record_type == TMY_TYPE_ADDRESS_GROUP &&
+ tmy_address_match_group(is_ipv6, address, acl->u.group))
+ break;
+
+ else if (acl->record_type == TMY_TYPE_IPv4 && !is_ipv6 &&
+ (acl->u.ipv4.min <= ip && ip <= acl->u.ipv4.max))
+ break;
+
+ else if (acl->record_type == TMY_TYPE_IPv6 && is_ipv6 &&
+ memcmp(acl->u.ipv6.min, address, 16) <= 0 &&
+ memcmp(address, acl->u.ipv6.max, 16) <= 0)
+ break;
+
+ }
+
+ tmy_audit_network_log(is_ipv6, keyword, address,
+ port, ptr != 0, is_enforce);
+
+ if (ptr)
+ return 0;
+
+ if (is_enforce) {
+
+ if (is_ipv6) {
+
+ char buf[64];
+
+ tmy_print_ipv6(buf, sizeof(buf), (const u16 *) address);
+ return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+ "%s %s %u\n",
+ domain->domainname->name, keyword,
+ buf, port);
+
+ }
+
+ return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+ "%s " NIPQUAD_FMT " %u\n",
+ domain->domainname->name, keyword,
+ HIPQUAD(ip), port);
+
+ }
+
+ if (tmy_accept(TMY_MAC_FOR_NETWORK, domain))
+ tmy_add_network_entry(operation,
+ is_ipv6 ? TMY_TYPE_IPv6 : TMY_TYPE_IPv4,
+ NULL, address, address,
+ port, port, domain, NULL, 0);
+
+ return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_network_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ u8 sock_type;
+ u8 operation;
+ u8 record_type;
+ u16 min_address[8];
+ u16 max_address[8];
+ unsigned int min[8];
+ unsigned int max[8];
+ struct addr_group_entry *group = NULL;
+ u16 min_port;
+ u16 max_port;
+ int count;
+ char *cp1 = NULL;
+ char *cp2 = NULL;
+
+ cp1 = strchr(data, ' ');
+ if (!cp1)
+ goto out;
+ cp1++;
+
+ if (strncmp(data, "TCP ", 4) == 0)
+ sock_type = SOCK_STREAM;
+ else if (strncmp(data, "UDP ", 4) == 0)
+ sock_type = SOCK_DGRAM;
+ else if (strncmp(data, "RAW ", 4) == 0)
+ sock_type = SOCK_RAW;
+ else
+ goto out;
+
+ cp2 = strchr(cp1, ' ');
+ if (!cp2)
+ goto out;
+ cp2++;
+
+ if (strncmp(cp1, "bind ", 5) == 0) {
+ switch (sock_type) {
+ case SOCK_STREAM:
+ operation = TMY_NETWORK_ACL_TCP_BIND;
+ break;
+ case SOCK_DGRAM:
+ operation = TMY_NETWORK_ACL_UDP_BIND;
+ break;
+ default:
+ operation = TMY_NETWORK_ACL_RAW_BIND;
+ break;
+ }
+ } else if (strncmp(cp1, "connect ", 8) == 0) {
+ switch (sock_type) {
+ case SOCK_STREAM:
+ operation = TMY_NETWORK_ACL_TCP_CONNECT;
+ break;
+ case SOCK_DGRAM:
+ operation = TMY_NETWORK_ACL_UDP_CONNECT;
+ break;
+ default:
+ operation = TMY_NETWORK_ACL_RAW_CONNECT;
+ break;
+ }
+ } else if (sock_type == SOCK_STREAM &&
+ strncmp(cp1, "listen ", 7) == 0)
+ operation = TMY_NETWORK_ACL_TCP_LISTEN;
+
+ else if (sock_type == SOCK_STREAM &&
+ strncmp(cp1, "accept ", 7) == 0)
+ operation = TMY_NETWORK_ACL_TCP_ACCEPT;
+
+ else
+ goto out;
+
+ cp1 = strchr(cp2, ' ');
+ if (!cp1)
+ goto out;
+ *cp1++ = '\0';
+
+ count = sscanf(cp2,
+ NIP6_FMT "-" NIP6_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &min[4], &min[5], &min[6], &min[7],
+ &max[0], &max[1], &max[2], &max[3],
+ &max[4], &max[5], &max[6], &max[7]);
+
+ if (count == 8 || count == 16) {
+
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ min_address[i] = htons((u16) min[i]);
+ max_address[i] = htons((u16) max[i]);
+ }
+
+ if (count == 8)
+ memmove(max_address, min_address, sizeof(min_address));
+ record_type = TMY_TYPE_IPv6;
+
+ goto ok;
+
+ }
+
+ count = sscanf(cp2,
+ NIPQUAD_FMT "-" NIPQUAD_FMT,
+ &min[0], &min[1], &min[2], &min[3],
+ &max[0], &max[1], &max[2], &max[3]);
+
+ if (count == 4 || count == 8) {
+
+ u32 ip = htonl((((u8) min[0]) << 24) +
+ (((u8) min[1]) << 16) +
+ (((u8) min[2]) << 8) +
+ (u8) min[3]);
+ *(u32 *) min_address = ip;
+
+ if (count == 8)
+ ip = htonl((((u8) max[0]) << 24) +
+ (((u8) max[1]) << 16) +
+ (((u8) max[2]) << 8) +
+ (u8) max[3]);
+ *(u32 *) max_address = ip;
+ record_type = TMY_TYPE_IPv4;
+
+ goto ok;
+
+ }
+
+ if (*cp2 == '@') {
+
+ group = tmy_new_addr_group(cp2 + 1);
+ if (!group)
+ return -ENOMEM;
+ record_type = TMY_TYPE_ADDRESS_GROUP;
+
+ goto ok;
+ }
+
+ goto out;
+
+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 tmy_add_network_entry(operation, record_type, group,
+ (u32 *) min_address,
+ (u32 *) max_address,
+ min_port, max_port, domain,
+ cond, is_delete);
+
+out: ;
+ return -EINVAL;
+}
+
+/**
+ * tmy_network_listen_acl - check permission for listen(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_listen_acl(const u8 is_ipv6,
+ const u8 *address,
+ const u16 port)
+{
+ return tmy_network_entry(is_ipv6,
+ TMY_NETWORK_ACL_TCP_LISTEN,
+ (const u32 *) address,
+ ntohs(port));
+}
+
+/**
+ * tmy_network_connect_acl - check permission for connect(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_connect_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ switch (sock_type) {
+ case SOCK_STREAM:
+ type = TMY_NETWORK_ACL_TCP_CONNECT;
+ break;
+ case SOCK_DGRAM:
+ type = TMY_NETWORK_ACL_UDP_CONNECT;
+ break;
+ default:
+ type = TMY_NETWORK_ACL_RAW_CONNECT;
+ break;
+ }
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_bind_acl - check permission for bind(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_bind_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ switch (sock_type) {
+ case SOCK_STREAM:
+ type = TMY_NETWORK_ACL_TCP_BIND;
+ break;
+ case SOCK_DGRAM:
+ type = TMY_NETWORK_ACL_UDP_BIND;
+ break;
+ default:
+ type = TMY_NETWORK_ACL_RAW_BIND;
+ break;
+ }
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_sendmsg_acl - check permission for sendmsg(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_sendmsg_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port)
+{
+ int type;
+
+ if (sock_type == SOCK_DGRAM)
+ type = SOCK_DGRAM;
+ else
+ type = TMY_NETWORK_ACL_RAW_CONNECT;
+
+ return tmy_network_entry(is_ipv6, type,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_accept_acl - check permission for accept(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: TCP client's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_accept_acl(const u8 is_ipv6, const u8 *address,
+ const u16 port)
+{
+ return tmy_network_entry(is_ipv6, TMY_NETWORK_ACL_TCP_ACCEPT,
+ (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_recvmsg_acl - check permission for recvmsg(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port: UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_recvmsg_acl(const u8 is_ipv6, const int sock_type,
+ const u8 *address, const u16 port)
+{
+ return tmy_network_entry(is_ipv6, sock_type == SOCK_DGRAM ?
+ TMY_NETWORK_ACL_UDP_CONNECT :
+ TMY_NETWORK_ACL_RAW_CONNECT,
+ (const u32 *) address, ntohs(port));
+}
Mount access control functions for TOMOYO Linux.
TOMOYO Linux checks permission according to
device name, mount point, filesystem type and optional flags.
TOMOYO Linux also checks permission in umount and pivot_root.
Each permission can be automatically accumulated into
the policy using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/mount.c | 1019 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1019 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/mount.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,1019 @@
+/*
+ * security/tomoyo/mount.c
+ *
+ * Mount access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/namei.h>
+#include <linux/mnt_namespace.h>
+
+/***** KEYWORDS for mount restrictions. *****/
+
+#define MOUNT_BIND_KEYWORD "--bind"
+#define MOUNT_MOVE_KEYWORD "--move"
+#define MOUNT_REMOUNT_KEYWORD "--remount"
+#define MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable"
+#define MOUNT_MAKE_PRIVATE_KEYWORD "--make-private"
+#define MOUNT_MAKE_SLAVE_KEYWORD "--make-slave"
+#define MOUNT_MAKE_SHARED_KEYWORD "--make-shared"
+
+/***** The structure for mount restrictions. *****/
+
+struct mount_entry {
+ struct mount_entry *next; /* NULL if none. */
+ const struct path_info *dev_name;
+ const struct path_info *dir_name;
+ const struct path_info *fs_type;
+ unsigned int flags; /* Mount flags. */
+ u8 is_deleted;
+};
+
+struct no_umount_entry {
+ struct no_umount_entry *next; /* NULL if none. */
+ const struct path_info *dir;
+ u8 is_deleted;
+};
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_mount_log(const u8 is_granted,
+ const u8 is_enforce,
+ const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+static int tmy_audit_mount_log(const u8 is_granted,
+ const u8 is_enforce,
+ const char *fmt, ...)
+{
+ char *buf1;
+ char *buf2;
+ unsigned int len;
+ va_list args;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf1 = tmy_alloc(PAGE_SIZE);
+
+ if (!buf1)
+ return -ENOMEM;
+
+ va_start(args, fmt);
+ len = vsnprintf(buf1, PAGE_SIZE, fmt, args);
+ va_end(args);
+
+ if (len >= PAGE_SIZE) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ buf2 = tmy_init_audit_log(&len);
+
+ if (!buf2) {
+ tmy_free(buf1);
+ return -ENOMEM;
+ }
+
+ snprintf(buf2 + strlen(buf2),
+ len - strlen(buf2),
+ "%s", buf1);
+
+ tmy_free(buf1);
+
+ return tmy_write_audit_log(buf2, is_granted, is_enforce);
+}
+
+/************************ MOUNT RESTRICTION HANDLER ************************/
+
+static void put_filesystem(struct file_system_type *fs)
+{
+ module_put(fs->owner);
+}
+
+static struct mount_entry *mount_list;
+
+/* Add or remove a mount entry. */
+static int tmy_add_mount_acl(const char *dev_name,
+ const char *dir_name,
+ const char *fs_type,
+ const unsigned int flags,
+ const u8 is_delete)
+{
+ 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 DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ fs = tmy_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) == 0)
+ /* Fix dev_name to "any" for remount permission. */
+ dev_name = "any";
+ if (strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+ strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+ dev_name = "any";
+
+ if (!tmy_correct_path(dev_name, 0, 0, 0, __FUNCTION__) ||
+ !tmy_correct_path(dir_name, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+
+ dev = tmy_save_name(dev_name);
+ if (!dev)
+ return -ENOMEM;
+ dir = tmy_save_name(dir_name);
+ if (!dir)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ if (ptr->flags != flags ||
+ tmy_pathcmp(ptr->dev_name, dev) ||
+ tmy_pathcmp(ptr->dir_name, dir) ||
+ tmy_pathcmp(ptr->fs_type, fs))
+ continue;
+
+ if (is_delete) {
+ ptr->is_deleted = 1;
+ error = 0;
+ goto out;
+ } else {
+ if (ptr->is_deleted) {
+ ptr->flags = flags;
+ ptr->is_deleted = 0;
+ } else {
+ if (ptr->flags == flags) {
+ error = 0;
+ goto out; /* No changes. */
+ }
+ ptr->flags = flags;
+ }
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_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;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = mount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ mount_list = new_entry;
+
+ error = 0;
+ ptr = new_entry;
+
+out: ;
+ up(&lock);
+
+ return error;
+}
+
+/* Print error message for mount request. */
+static inline int tmy_mount_perm_error(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags,
+ const u8 is_enforce)
+{
+ int error = -EPERM;
+ const char *realname1 = tmy_realpath(dev_name);
+ const char *realname2 = tmy_realpath(dir_name);
+ const char *exename = tmy_get_exe();
+
+ if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce,
+ "mount -o remount %s 0x%lX",
+ realname2 ? realname2 : dir_name,
+ flags);
+ if (is_enforce &&
+ !tmy_supervisor("# %s is requesting\nmount -o remount %s\n",
+ exename, realname2 ? realname2 : dir_name))
+ error = 0;
+
+ } else if (!strcmp(type, MOUNT_BIND_KEYWORD) ||
+ !strcmp(type, MOUNT_MOVE_KEYWORD)) {
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount %s %s %s 0x%lX\n",
+ exename, type,
+ realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ } 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)) {
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s %s 0x%lX", type,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount %s %s 0x%lX",
+ exename, type,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ } else {
+
+ tmy_audit_mount_log(0, is_enforce, "mount -t %s %s %s 0x%lX",
+ type, realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name, flags);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\n"
+ "mount -t %s %s %s 0x%lX\n",
+ exename, type,
+ realname1 ? realname1 : dev_name,
+ realname2 ? realname2 : dir_name,
+ flags) == 0)
+ error = 0;
+
+ }
+
+ tmy_free(exename);
+ tmy_free(realname2);
+ tmy_free(realname1);
+ return error;
+}
+
+/**
+ * tmy_mount_perm - check for mount permission.
+ * @dev_name: pointer to device name. May be NULL.
+ * @dir_name: pointer to mount point.
+ * @type: pointer to filesystem. May be NULL.
+ * @flags: mount flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_mount_perm(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags)
+{
+ const u8 is_enforce = tmy_enforce(TMY_RESTRICT_MOUNT);
+ int error = -EPERM;
+
+ if (!tmy_flags(TMY_RESTRICT_MOUNT))
+ 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:
+ tmy_audit_mount_log(0, 1, "%s%s%sare given "
+ "for single mount operation",
+ 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:
+ tmy_audit_mount_log(0, 1, "%s%s%s%sare given "
+ "for single mount operation",
+ flags & MS_UNBINDABLE ? "unbindable " : "",
+ flags & MS_PRIVATE ? "private " : "",
+ flags & MS_SLAVE ? "slave " : "",
+ flags & MS_SHARED ? "shared " : "");
+ return -EINVAL;
+ }
+
+ if (flags & MS_REMOUNT)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_REMOUNT_KEYWORD,
+ flags & ~MS_REMOUNT);
+ else if (flags & MS_MOVE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MOVE_KEYWORD,
+ flags & ~MS_MOVE);
+ else if (flags & MS_BIND)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_BIND_KEYWORD,
+ flags & ~MS_BIND);
+ else if (flags & MS_UNBINDABLE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_UNBINDABLE_KEYWORD,
+ flags & ~MS_UNBINDABLE);
+ else if (flags & MS_PRIVATE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_PRIVATE_KEYWORD,
+ flags & ~MS_PRIVATE);
+ else if (flags & MS_SLAVE)
+ error = tmy_mount_perm(dev_name, dir_name,
+ MOUNT_MAKE_SLAVE_KEYWORD,
+ flags & ~MS_SLAVE);
+ else if (flags & MS_SHARED)
+ error = tmy_mount_perm(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 = tmy_realpath(dir_name);
+ if (!requested_dir_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdir.name = requested_dir_name;
+ tmy_fill_path_info(&rdir);
+
+ /* Compare fs name. */
+ fstype = get_fs_type(type);
+ if (strcmp(type, MOUNT_REMOUNT_KEYWORD) == 0)
+ /* Needn't to resolve dev_name */;
+ else if (strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+ /* Needn't to resolve dev_name */;
+ else if (strcmp(type, MOUNT_BIND_KEYWORD) == 0 ||
+ strcmp(type, MOUNT_MOVE_KEYWORD) == 0) {
+ requested_dev_name = tmy_realpath(dev_name);
+ if (!requested_dev_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdev.name = requested_dev_name;
+ tmy_fill_path_info(&rdev);
+ need_dev = -1;
+ } else if (fstype) {
+ if (fstype->fs_flags & FS_REQUIRES_DEV) {
+ requested_dev_name = tmy_realpath(dev_name);
+ if (!requested_dev_name) {
+ error = -ENOENT;
+ goto cleanup;
+ }
+ rdev.name = requested_dev_name;
+ tmy_fill_path_info(&rdev);
+ need_dev = 1;
+ }
+ } else {
+ error = -ENODEV;
+ goto cleanup;
+ }
+
+ for (ptr = mount_list; ptr; ptr = ptr->next) {
+ 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 (tmy_path_match(&rdir, ptr->dir_name) == 0)
+ continue;
+
+ /* Compare device name. */
+ if (requested_dev_name &&
+ tmy_path_match(&rdev, ptr->dev_name) == 0)
+ continue;
+
+ /* OK. */
+ error = 0;
+
+ if (need_dev > 0)
+ tmy_audit_mount_log(1, is_enforce,
+ "mount -t %s %s %s 0x%lX",
+ type,
+ requested_dev_name,
+ requested_dir_name, flags);
+ else if (need_dev < 0)
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s %s %s 0x%lX",
+ type, requested_dev_name,
+ requested_dir_name, flags);
+ else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD))
+ tmy_audit_mount_log(1, is_enforce,
+ "mount -o remount %s 0x%lX",
+ requested_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))
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+ else
+ tmy_audit_mount_log(1, is_enforce,
+ "mount %s on %s 0x%lX",
+ type, requested_dir_name,
+ flags);
+
+ break;
+ }
+
+ if (error)
+ error = tmy_mount_perm_error(dev_name, dir_name,
+ type, flags, is_enforce);
+
+ if (error && tmy_accept(TMY_RESTRICT_MOUNT, NULL)) {
+ tmy_add_mount_acl(need_dev ?
+ requested_dev_name : dev_name,
+ requested_dir_name, type, flags, 0);
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+ }
+
+cleanup:
+ tmy_free(requested_dev_name);
+ tmy_free(requested_dir_name);
+ if (fstype)
+ put_filesystem(fstype);
+
+ }
+
+ if (!is_enforce)
+ error = 0;
+ return error;
+
+}
+
+/**
+ * tmy_add_mount_policy - add or delete mount policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_mount_policy(char *data, const u8 is_delete)
+{
+ char *cp;
+ char *cp2;
+ const char *fs;
+ const char *dev;
+ const char *dir;
+ unsigned int 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 tmy_add_mount_acl(dev, dir, fs, flags, is_delete);
+}
+
+/**
+ * tmy_read_mount_policy - read mount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_mount_policy(struct io_buffer *head)
+{
+ struct mount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = mount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_ALLOW_MOUNT "%s %s %s 0x%x\n",
+ ptr->dev_name->name, ptr->dir_name->name,
+ ptr->fs_type->name, ptr->flags))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+static int tmy_find_conceal(struct nameidata *nd,
+ struct vfsmount *vfsmnt,
+ struct dentry *dentry)
+{
+ int flag = 0;
+
+ if (IS_ROOT(dentry) || !d_unhashed(dentry)) {
+ while (1) {
+
+ if (nd->mnt->mnt_root == vfsmnt->mnt_root &&
+ nd->dentry == dentry) {
+ flag = 1;
+ break;
+ }
+
+ if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+
+ spin_lock(&vfsmount_lock);
+
+ if (vfsmnt->mnt_parent == vfsmnt) {
+ spin_unlock(&vfsmount_lock);
+ break;
+ }
+ dentry = vfsmnt->mnt_mountpoint;
+ vfsmnt = vfsmnt->mnt_parent;
+
+ spin_unlock(&vfsmount_lock);
+
+ continue;
+ }
+ dentry = dentry->d_parent;
+
+ }
+ }
+
+ return flag;
+}
+
+/**
+ * tmy_conceal_mount - check for conceal mount permission.
+ * @nd: pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ *
+ * People seldom mount on directries that have submounts.
+ * For example, you don't mount on /usr/ directory if /usr/local/ directory
+ * is already mounted, do you?
+ * This function forbids such mount requests.
+ */
+int tmy_conceal_mount(struct nameidata *nd)
+{
+ int flag = 0;
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ u8 is_enforce;
+ char *dir;
+
+ if (!tmy_flags(TMY_DENY_CONCEAL_MOUNT))
+ return 0;
+
+ if (namespace) {
+ struct list_head *p;
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *vfsmnt =
+ list_entry(p, struct vfsmount, mnt_list);
+ struct dentry *dentry = vfsmnt->mnt_root;
+
+ spin_lock(&dcache_lock);
+
+ flag = tmy_find_conceal(nd, vfsmnt, dentry);
+
+ spin_unlock(&dcache_lock);
+
+ if (flag)
+ break;
+ }
+ }
+
+ is_enforce = tmy_enforce(TMY_DENY_CONCEAL_MOUNT);
+ dir = tmy_realpath_dentry(nd->dentry, nd->mnt);
+
+ if (flag) {
+ int error = -EPERM;
+ if (dir) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "mount %s", dir);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nmount on %s\n",
+ exename, dir) == 0)
+ error = 0;
+
+ tmy_free(exename);
+ }
+ tmy_free(dir);
+
+ if (is_enforce)
+ return error;
+ } else {
+ if (dir)
+ tmy_audit_mount_log(1, is_enforce, "mount %s", dir);
+ tmy_free(dir);
+ }
+
+ return 0;
+}
+
+/************************ UMOUNT RESTRICTION HANDLER ************************/
+
+static struct no_umount_entry *no_umount_list;
+
+/* Add or remove a no-unmount entry. */
+static int tmy_add_no_umount_acl(const char *dir, const u8 is_delete)
+{
+ struct no_umount_entry *new_entry;
+ struct no_umount_entry *ptr;
+ const struct path_info *saved_dir;
+ static DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(dir, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+ saved_dir = tmy_save_name(dir);
+ if (!saved_dir)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ if (ptr->dir == saved_dir) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->dir = saved_dir;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = no_umount_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ no_umount_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_umount_perm - check for no-unmount permission.
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_umount_perm(struct vfsmount *mnt)
+{
+ int error = -EPERM;
+ const char *dir0;
+ const u8 is_enforce = tmy_enforce(TMY_RESTRICT_UMOUNT);
+
+ if (!tmy_flags(TMY_RESTRICT_UMOUNT))
+ return 0;
+
+ dir0 = tmy_realpath_dentry(mnt->mnt_root, mnt);
+
+ if (dir0) {
+ struct no_umount_entry *ptr;
+ struct path_info dir;
+
+ dir.name = dir0;
+ tmy_fill_path_info(&dir);
+
+ for (ptr = no_umount_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_path_match(&dir, ptr->dir))
+ break;
+ }
+
+ if (ptr) {
+ const char *exename = tmy_get_exe();
+
+ tmy_audit_mount_log(0, is_enforce, "umount %s", dir0);
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\nunmount %s\n",
+ exename, dir0) == 0)
+ error = 0;
+
+ tmy_free(exename);
+ } else {
+ tmy_audit_mount_log(1, is_enforce, "umount %s", dir0);
+ error = 0;
+ }
+
+ tmy_free(dir0);
+ }
+
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_add_no_umount_policy - add or delete no-unmount policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_umount_policy(char *data, const u8 is_delete)
+{
+ return tmy_add_no_umount_acl(data, is_delete);
+}
+
+/**
+ * tmy_read_no_umount_policy - read no-unmount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_umount_policy(struct io_buffer *head)
+{
+ struct no_umount_entry *ptr = head->read_var2;
+
+ if (!ptr)
+ ptr = no_umount_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_DENY_UNMOUNT "%s\n",
+ ptr->dir->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
+
+/***** The structure for pivot_root restrictions. *****/
+
+struct pivot_root_entry {
+ struct pivot_root_entry *next;
+ const struct path_info *old_root;
+ const struct path_info *new_root;
+ u8 is_deleted;
+};
+
+/********************** PIVOT_ROOT RESTRICTION HANDLER **********************/
+
+static struct pivot_root_entry *pivot_root_list;
+
+/* Add or remove a pivot_root entry. */
+static int tmy_add_pivot_root_acl(const char *old_root,
+ const char *new_root,
+ const u8 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 DECLARE_MUTEX(lock);
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(old_root, 1, 0, 1, __FUNCTION__) ||
+ !tmy_correct_path(new_root, 1, 0, 1, __FUNCTION__))
+ return -EINVAL;
+
+ saved_old_root = tmy_save_name(old_root);
+ if (!saved_old_root)
+ return -ENOMEM;
+ saved_new_root = tmy_save_name(new_root);
+ if (!saved_new_root)
+ return -ENOMEM;
+
+ down(&lock);
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ if (ptr->old_root == saved_old_root &&
+ ptr->new_root == saved_new_root) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->old_root = saved_old_root;
+ new_entry->new_root = saved_new_root;
+ mb(); /* Instead of using spinlock. */
+
+ ptr = pivot_root_list;
+ if (ptr) {
+ while (ptr->next)
+ ptr = ptr->next;
+ ptr->next = new_entry;
+ } else
+ pivot_root_list = new_entry;
+
+ error = 0;
+out: ;
+
+ up(&lock);
+
+ return error;
+}
+
+/**
+ * tmy_pivot_root_perm - check for pivot_root permission.
+ * @old_nd: pointer to "struct nameidata".
+ * @new_nd: pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_pivot_root_perm(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+ int error = -EPERM;
+ char *old_root;
+ char *new_root;
+ u8 is_enforce;
+ struct path_info old_root_dir;
+ struct path_info new_root_dir;
+
+ if (!tmy_flags(TMY_RESTRICT_PIVOT_ROOT))
+ return 0;
+
+ old_root = tmy_realpath_dentry(old_nd->dentry, old_nd->mnt);
+ new_root = tmy_realpath_dentry(new_nd->dentry, new_nd->mnt);
+
+ if (!old_root || !new_root)
+ goto out;
+
+ old_root_dir.name = old_root;
+ tmy_fill_path_info(&old_root_dir);
+ new_root_dir.name = new_root;
+ tmy_fill_path_info(&new_root_dir);
+
+ if (old_root_dir.is_dir && new_root_dir.is_dir) {
+ struct pivot_root_entry *ptr;
+
+ for (ptr = pivot_root_list; ptr; ptr = ptr->next) {
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_path_match(&old_root_dir, ptr->old_root) &&
+ tmy_path_match(&new_root_dir, ptr->new_root)) {
+ error = 0;
+ break;
+ }
+ }
+ }
+
+out: ;
+
+ is_enforce = tmy_enforce(TMY_RESTRICT_PIVOT_ROOT);
+ tmy_audit_mount_log(!error, is_enforce, "pivot_root %s %s",
+ new_root, old_root);
+
+ if (error) {
+ const char *exename = tmy_get_exe();
+
+ if (is_enforce &&
+ tmy_supervisor("# %s is requesting\npivot_root %s %s\n",
+ exename, new_root, old_root) == 0)
+ error = 0;
+
+ if (exename)
+ tmy_free(exename);
+
+ if (!is_enforce && tmy_accept(TMY_RESTRICT_PIVOT_ROOT, NULL)
+ && old_root && new_root) {
+ tmy_add_pivot_root_acl(old_root, new_root, 0);
+ tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+ }
+
+ if (!is_enforce)
+ error = 0;
+ }
+
+ tmy_free(old_root);
+ tmy_free(new_root);
+ return error;
+}
+
+/**
+ * tmy_add_pivot_root_policy - add or delete pivot_root policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pivot_root_policy(char *data, const u8 is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+
+ return tmy_add_pivot_root_acl(cp, data, is_delete);
+}
+
+/**
+ * tmy_read_pivot_root_policy - read pivot_root policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pivot_root_policy(struct io_buffer *head)
+{
+ struct pivot_root_entry *ptr = head->read_var2;
+ if (!ptr)
+ ptr = pivot_root_list;
+
+ while (ptr) {
+ head->read_var2 = ptr;
+ if (ptr->is_deleted == 0 &&
+ tmy_io_printf(head, TMY_ALLOW_PIVOT_ROOT "%s %s\n",
+ ptr->new_root->name, ptr->old_root->name))
+ break;
+ ptr = ptr->next;
+ }
+
+ return ptr ? -ENOMEM : 0;
+}
Signal control functions for TOMOYO Linux.
TOMOYO Linux checks sending signal by signal number and
the domain of target process. In order to check signal
permission, LSM expansion patch [TOMOYO 14/15] is needed.
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/signal.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 229 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/signal.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,229 @@
+/*
+ * security/tomoyo/signal.c
+ *
+ * Signal access contol functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_signal_log(const int signal,
+ const struct path_info *dest_domain,
+ const u8 is_granted,
+ const u8 is_enforce)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = dest_domain->total_len;
+ buf = tmy_init_audit_log(&len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ "%s%d %s",
+ TMY_ALLOW_SIGNAL, signal, dest_domain->name);
+
+ return tmy_write_audit_log(buf, is_granted, is_enforce);
+}
+
+/************************* SIGNAL ACL HANDLER *************************/
+
+static int tmy_add_signal_entry(const u16 sig, const char *dest_pattern,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ struct acl_info *ptr;
+ const struct path_info *saved_dest_pattern;
+ int error = -ENOMEM;
+
+ if (!domain)
+ return -EINVAL;
+ if (!dest_pattern ||
+ !tmy_is_correct_domain(dest_pattern, __FUNCTION__))
+ return -EINVAL;
+
+ saved_dest_pattern = tmy_save_name(dest_pattern);
+ if (!saved_dest_pattern)
+ return -ENOMEM;
+
+ down(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ ptr = domain->first_acl_ptr;
+ if (!ptr)
+ goto first_entry;
+
+ while (1) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_SIGNAL_ACL && acl->sig == sig
+ && ptr->cond == cond
+ && !tmy_pathcmp(acl->domainname, saved_dest_pattern)) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ break;
+ }
+
+ if (ptr->next) {
+ ptr = ptr->next;
+ continue;
+ }
+
+first_entry: ;
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ break;
+
+ acl->head.type = TMY_TYPE_SIGNAL_ACL;
+ acl->head.cond = cond;
+ acl->sig = sig;
+ acl->domainname = saved_dest_pattern;
+ error = tmy_add_acl(ptr, domain, (struct acl_info *) acl);
+ break;
+ }
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+ if (ptr->type != TMY_TYPE_SIGNAL_ACL || ptr->cond != cond ||
+ ptr->is_deleted || acl->sig != sig ||
+ tmy_pathcmp(acl->domainname, saved_dest_pattern))
+ continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+
+ok: ;
+ up(&domain_acl_lock);
+
+ return error;
+}
+
+/**
+ * tmy_signal_acl - check permission for kill(2)/tkill(2)/tgkill(2).
+ * @sig: signal number.
+ * @pid: pid of destination process.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_signal_acl(const int sig, const int pid)
+{
+ struct domain_info *domain = TMY_SECURITY->domain;
+ struct domain_info *dest = NULL;
+ const char *dest_pattern;
+ struct acl_info *ptr;
+ const u16 hash = sig;
+ const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_SIGNAL);
+
+ if (!tmy_flags(TMY_MAC_FOR_SIGNAL))
+ return 0;
+ if (!sig)
+ return 0; /* No check for NULL signal. */
+ if (current->pid == pid) {
+ tmy_audit_signal_log(sig, domain->domainname, 1, is_enforce);
+ return 0; /* No check for self. */
+ }
+
+ { /* Simplified checking. */
+ struct task_struct *p = NULL;
+ read_lock(&tasklist_lock);
+ if (pid > 0)
+ p = find_task_by_pid((pid_t) pid);
+ else if (pid == 0)
+ p = current;
+ else if (pid == -1)
+ dest = &KERNEL_DOMAIN;
+ else
+ p = find_task_by_pid((pid_t) -pid);
+ if (p)
+ /* "struct task_struct"->security is not NULL. */
+ dest = ((struct tmy_security *) p->security)->domain;
+ read_unlock(&tasklist_lock);
+ if (!dest)
+ return 0; /* I can't find destinatioin. */
+ }
+
+ if (domain == dest) {
+ tmy_audit_signal_log(sig, dest->domainname, 1, is_enforce);
+ return 0;
+ }
+
+ dest_pattern = dest->domainname->name;
+ for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) {
+ struct signal_acl *acl = (struct signal_acl *) ptr;
+
+ if (ptr->type == TMY_TYPE_SIGNAL_ACL && ptr->is_deleted == 0
+ && acl->sig == hash &&
+ tmy_check_condition(ptr->cond, NULL) == 0) {
+ const int len = acl->domainname->total_len;
+
+ if (strncmp(acl->domainname->name,
+ dest_pattern, len) == 0
+ && (dest_pattern[len] == ' ' ||
+ dest_pattern[len] == '\0'))
+ break;
+
+ }
+ }
+
+ if (ptr) {
+ tmy_audit_signal_log(sig, dest->domainname, 1, is_enforce);
+ return 0;
+ }
+
+ tmy_audit_signal_log(sig, dest->domainname, 0, is_enforce);
+ if (is_enforce)
+ return tmy_supervisor("%s\n" TMY_ALLOW_SIGNAL "%d %s\n",
+ domain->domainname->name,
+ sig, dest_pattern);
+ if (tmy_accept(TMY_MAC_FOR_SIGNAL, domain))
+ tmy_add_signal_entry(sig, dest_pattern, domain, NULL, 0);
+
+ return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_signal_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete)
+{
+ int sig;
+ char *domainname = strchr(data, ' ');
+
+ if (sscanf(data, "%d", &sig) == 1 && domainname &&
+ tmy_is_domain_def(domainname + 1))
+ return tmy_add_signal_entry(sig, domainname + 1, domain,
+ cond, is_delete);
+
+ return -EINVAL;
+}
LSM wrapper functions for TOMOYO Linux access control.
If bind mounts are used, TOMOYO requires all permissions for
all possible pathnames (whereas AppArmor requires one of possible pathnames).
If "struct vfsmount" is passed to LSM hooks as AppArmor proposes,
this file will become more simpler and "namespace_sem" can remain "static".
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/tomoyo.c | 748 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 748 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/tomoyo.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,748 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/security.h>
+#include <linux/highmem.h>
+#include <linux/namei.h>
+#include <linux/mnt_namespace.h>
+#include <linux/sysctl.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+
+#include "tomoyo.h"
+#include "realpath.h"
+#define MAX_SOCK_ADDR 128 /* net/socket.c */
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN = { NULL, NULL, NULL, 0, 0, 0 };
+
+static struct kmem_cache *tmy_cachep;
+
+static int tmy_task_alloc_security(struct task_struct *p)
+{
+ struct tmy_security *ptr = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+ if (!ptr)
+ return -ENOMEM;
+ memcpy(ptr, TMY_SECURITY, sizeof(*ptr));
+ p->security = ptr;
+ return 0;
+}
+
+static void tmy_task_free_security(struct task_struct *p)
+{
+ kmem_cache_free(tmy_cachep, p->security);
+}
+
+static int tmy_bprm_alloc_security(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+ return 0;
+}
+
+static int tmy_bprm_check_security(struct linux_binprm *bprm)
+{
+ struct domain_info *next_domain = NULL;
+ int retval = 0;
+
+ tmy_load_policy(bprm->filename);
+
+ /*
+ * TMY_CHECK_READ_FOR_OPEN_EXEC bit indicates whether this function is
+ * called by do_execve() or not.
+ * If called by do_execve(), I do domain transition.
+ */
+ if (!(TMY_SECURITY->flags
+ & TMY_CHECK_READ_FOR_OPEN_EXEC)) {
+ retval = tmy_find_next_domain(bprm, &next_domain);
+ if (retval == 0) {
+ TMY_SECURITY->domain = next_domain;
+ TMY_SECURITY->flags |=
+ TMY_CHECK_READ_FOR_OPEN_EXEC;
+ }
+ }
+
+ return retval;
+}
+
+static void tmy_bprm_post_apply_creds(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+}
+
+static void tmy_bprm_free_security(struct linux_binprm *bprm)
+{
+ TMY_SECURITY->domain = TMY_SECURITY->prev_domain;
+ TMY_SECURITY->flags &= ~TMY_CHECK_READ_FOR_OPEN_EXEC;
+}
+
+static int tmy_sysctl(struct ctl_table *table, int op)
+{
+ int error;
+ char *name;
+
+ if ((op & 6) == 0)
+ return 0;
+
+ name = sysctlpath_from_table(table);
+ if (!name)
+ return -ENOMEM;
+
+ error = tmy_file_perm(name, op & 6, "sysctl");
+ tmy_free(name);
+
+ return error;
+}
+
+static int tmy_inode_permission(struct inode *inode,
+ int mask,
+ struct nameidata *nd)
+{
+ int flag = 0;
+
+ if (S_ISDIR(inode->i_mode)) /* ignore because inode is directory */
+ return 0;
+ if (!nd || !nd->dentry || !nd->mnt)
+ return 0;
+ /*
+ * If called by other than do_execve(), I check for read permission of
+ * interpreter.
+ * Unlike DAC, I don't check for read permission of pathname passed to
+ * do_execve().
+ * TOMOYO Linux checks for program's execute permission and
+ * interpreter's read permission.
+ */
+ if ((mask == MAY_EXEC) &&
+ (TMY_SECURITY->flags & TMY_CHECK_READ_FOR_OPEN_EXEC))
+ mask = MAY_READ;
+ if ((mask == MAY_EXEC) || (mask == 0))
+ return 0;
+
+ if (mask == (MAY_READ | MAY_EXEC))
+ flag |= O_RDONLY + 1;
+ else {
+ if (mask & MAY_READ)
+ flag |= O_RDONLY + 1;
+ if (mask & MAY_WRITE)
+ flag |= O_WRONLY + 1;
+ if ((mask & MAY_APPEND))
+ flag |= O_APPEND;
+ }
+
+ return tmy_open_perm(nd->dentry, nd->mnt, flag);
+}
+
+static int tmy_do_single_write_perm(int operation, struct dentry *dentry)
+{
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ struct list_head *p;
+ int ret = 0;
+ int error = 0;
+ int index = 0;
+ int index2 = 0;
+
+ if (!dentry || !namespace)
+ return 0;
+
+start: ;
+ index2 = 0;
+
+ /* lock namespace */
+ down_read(&namespace_sem);
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *mnt = list_entry(p, struct vfsmount, mnt_list);
+
+ if (mnt->mnt_root->d_sb != dentry->d_sb)
+ continue;
+ if (index2++ < index)
+ continue;
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+ error = tmy_single_write_perm(operation, dentry, mnt);
+
+ if (error != 0)
+ ret = error;
+
+ index++;
+
+ goto start;
+ }
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+
+ return ret;
+}
+
+static int tmy_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+ if (iattr->ia_valid & ATTR_SIZE)
+ return tmy_do_single_write_perm(TMY_TYPE_TRUNCATE_ACL, dentry);
+ return 0;
+}
+
+static int tmy_inode_create(struct inode *dir, struct dentry *dentry, int mode)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_CREATE_ACL, dentry);
+}
+
+static int tmy_inode_unlink(struct inode *dir, struct dentry *dentry)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_UNLINK_ACL, dentry);
+}
+
+static int tmy_inode_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_MKDIR_ACL, dentry);
+}
+
+static int tmy_inode_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_RMDIR_ACL, dentry);
+}
+
+static int tmy_inode_symlink(struct inode *dir,
+ struct dentry *dentry,
+ const char *old_name)
+{
+ return tmy_do_single_write_perm(TMY_TYPE_SYMLINK_ACL, dentry);
+}
+
+static int tmy_inode_mknod(struct inode *inode,
+ struct dentry *dentry,
+ int mode,
+ dev_t dev)
+{
+ if (S_ISCHR(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKCHAR_ACL, dentry);
+ if (S_ISBLK(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKBLOCK_ACL, dentry);
+ if (S_ISFIFO(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKFIFO_ACL, dentry);
+ if (S_ISSOCK(mode))
+ return tmy_do_single_write_perm(TMY_TYPE_MKSOCK_ACL, dentry);
+
+ return 0;
+}
+
+static int tmy_do_double_write_perm(int operation,
+ struct dentry *old_dentry,
+ struct dentry *new_dentry)
+{
+ struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+ struct list_head *p;
+ int ret = 0;
+ int error = 0;
+ int index = 0;
+ int index2 = 0;
+
+ if (!old_dentry || !new_dentry)
+ return 0;
+start: ;
+ index2 = 0;
+
+ /* lock namespace */
+ down_read(&namespace_sem);
+
+ list_for_each(p, &namespace->list) {
+ struct vfsmount *mnt = list_entry(p, struct vfsmount, mnt_list);
+
+ if (mnt->mnt_root->d_sb != old_dentry->d_sb)
+ continue;
+ if (index2++ < index)
+ continue;
+ /* unlock namespace */
+ up_read(&namespace_sem);
+ error = tmy_double_write_perm(operation, old_dentry, mnt,
+ new_dentry, mnt);
+ if (error != 0)
+ ret = error;
+ index++;
+ goto start;
+ }
+
+ /* unlock namespace */
+ up_read(&namespace_sem);
+
+ return ret;
+}
+
+static int tmy_inode_link(struct dentry *old_dentry,
+ struct inode *inode,
+ struct dentry *new_dentry)
+{
+ return tmy_do_double_write_perm(TMY_TYPE_LINK_ACL,
+ old_dentry, new_dentry);
+}
+
+static int tmy_inode_rename(struct inode *old_inode,
+ struct dentry *old_dentry,
+ struct inode *new_inode,
+ struct dentry *new_dentry)
+{
+ return tmy_do_double_write_perm(TMY_TYPE_RENAME_ACL,
+ old_dentry,
+ new_dentry);
+}
+
+static int tmy_file_fcntl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ if (!(arg & O_APPEND))
+ return tmy_rewrite_perm(file);
+ return 0;
+}
+
+static int tmy_socket_listen(struct socket *sock, int backlog)
+{
+ char addr[MAX_SOCK_ADDR];
+ int addr_len;
+ int error = 0;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (sock->type != SOCK_STREAM)
+ return error;
+ if (sock->sk->sk_family != PF_INET && sock->sk->sk_family != PF_INET6)
+ return error;
+
+ if (sock->ops->getname(sock, (struct sockaddr *) addr, &addr_len, 0))
+ return -EPERM;
+
+ switch (((struct sockaddr *) addr)->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *) addr;
+ error = tmy_network_listen_acl(1, in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ break;
+ case AF_INET:
+ in = (struct sockaddr_in *) addr;
+ error = tmy_network_listen_acl(0, (u8 *) &in->sin_addr,
+ in->sin_port);
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_connect(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len0)
+{
+ unsigned int addr_len = (unsigned int) addr_len0;
+ int error = 0;
+ const unsigned int type = sock->type;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = (struct sockaddr_in6 *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_connect_acl(1, type,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_connect_acl(1, SOCK_RAW,
+ in6->sin6_addr.s6_addr,
+ port);
+ }
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_connect_acl(0, type,
+ (u8 *) &in->sin_addr,
+ in->sin_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_connect_acl(0, SOCK_RAW,
+ (u8 *) &in->sin_addr,
+ port);
+ }
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_bind(struct socket *sock,
+ struct sockaddr *addr,
+ int addr_len0)
+{
+ unsigned int addr_len = (unsigned int) addr_len0;
+ int error = 0;
+ const unsigned int type = sock->type;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = ((struct sockaddr_in6 *) addr);
+ if (type != SOCK_RAW)
+ error = tmy_network_bind_acl(1, type,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_bind_acl(1, SOCK_RAW,
+ in6->sin6_addr.s6_addr,
+ port);
+ }
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ if (type != SOCK_RAW)
+ error = tmy_network_bind_acl(0, type,
+ (u8 *) &in->sin_addr,
+ in->sin_port);
+ else {
+ const u16 port = htons(sock->sk->sk_protocol);
+
+ error = tmy_network_bind_acl(0, SOCK_RAW,
+ (u8 *) &in->sin_addr,
+ port);
+ }
+ break;
+ }
+
+ return error;
+}
+
+static int tmy_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
+{
+ int error = 0;
+ const int type = sock->type;
+ struct sockaddr *addr = (struct sockaddr *) msg->msg_name;
+ const unsigned int addr_len = msg->msg_namelen;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (!addr || (type != SOCK_DGRAM && type != SOCK_RAW))
+ return error;
+
+ switch (addr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+ u16 port;
+
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ break;
+
+ in6 = (struct sockaddr_in6 *) addr;
+ port = htons(sock->sk->sk_protocol);
+ error = tmy_network_sendmsg_acl(1, type, in6->sin6_addr.s6_addr,
+ type == SOCK_DGRAM ?
+ in6->sin6_port : port);
+ break;
+
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ break;
+
+ in = (struct sockaddr_in *) addr;
+ port = htons(sock->sk->sk_protocol);
+ error = tmy_network_sendmsg_acl(0, type, (u8 *) &in->sin_addr,
+ type == SOCK_DGRAM ?
+ in->sin_port : port);
+ break;
+ }
+
+ return error;
+}
+
+#ifdef TMY_LSM_EXPANSION
+static int tmy_socket_post_accept(struct socket *sock, struct socket *newsock)
+{
+ int error = 0;
+ int addr_len;
+ char addr[MAX_SOCK_ADDR];
+ struct sockaddr *sockaddr = (struct sockaddr *) addr;
+
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if ((newsock->sk->sk_family != PF_INET) &&
+ (newsock->sk->sk_family != PF_INET6))
+ return error;
+
+ if (newsock->ops->getname(newsock, sockaddr, &addr_len, 2) == 0) {
+ switch (sockaddr->sa_family) {
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *) addr;
+ error = tmy_network_accept_acl(1,
+ in6->sin6_addr.s6_addr,
+ in6->sin6_port);
+ break;
+
+ case AF_INET:
+ in = (struct sockaddr_in *) addr;
+ error = tmy_network_accept_acl(0, (u8 *) &in->sin_addr,
+ in->sin_port);
+ break;
+ }
+ } else
+ error = -EPERM;
+
+ if (error)
+ return -ECONNABORTED;
+ return error;
+}
+
+static int tmy_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ int error = 0;
+ const unsigned int type = sk->sk_type;
+
+ /* skb_recv_datagram() didn't dequeue. */
+ if (!skb)
+ return 0;
+
+ /* skb_recv_datagram() can be called from interrupt context. */
+ if (in_interrupt())
+ return 0;
+ /* I don't check if called by kernel process. */
+ if (segment_eq(get_fs(), KERNEL_DS))
+ return 0;
+
+ if (type != SOCK_DGRAM && type != SOCK_RAW)
+ return 0;
+
+ switch (sk->sk_family) {
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin;
+ u16 port;
+
+ case AF_INET6:
+
+ if (type == SOCK_DGRAM) {
+ /* UDP IPv6 */
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = udp_hdr(skb)->source;
+
+ if (skb->protocol == htons(ETH_P_IP))
+ ipv6_addr_set(&sin6.sin6_addr, 0, 0,
+ htonl(0xffff),
+ ip_hdr(skb)->saddr);
+ else
+ ipv6_addr_copy(&sin6.sin6_addr,
+ &ipv6_hdr(skb)->saddr);
+
+ port = sin6.sin6_port;
+ } else {
+ /* RAW IPv6 */
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = 0;
+ ipv6_addr_copy(&sin6.sin6_addr, &ipv6_hdr(skb)->saddr);
+
+ port = htons(sk->sk_protocol);
+ }
+
+ error = tmy_network_recvmsg_acl(1, type,
+ sin6.sin6_addr.s6_addr, port);
+
+ break;
+
+ case AF_INET:
+
+ if (type == SOCK_DGRAM) {
+ /* UDP IPv4 */
+ sin.sin_family = AF_INET;
+ sin.sin_port = udp_hdr(skb)->source;
+ sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+
+ port = sin.sin_port;
+ } else {
+ /* RAW IPv4 */
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+ sin.sin_port = 0;
+
+ port = htons(sk->sk_protocol);
+ }
+
+ error = tmy_network_recvmsg_acl(0, type,
+ (u8 *) &sin.sin_addr, port);
+
+ break;
+
+ }
+
+ if (error)
+ error = -EAGAIN;
+ return error;
+}
+#endif
+
+static int tmy_sb_mount(char *dev_name,
+ struct nameidata *nd,
+ char *type,
+ unsigned long flags,
+ void *data)
+{
+ char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ char *dir_name;
+ int error;
+
+ if (!buf)
+ return -ENOMEM;
+
+ dir_name = d_path(nd->dentry, nd->mnt, buf, PAGE_SIZE);
+
+ if (IS_ERR(dir_name))
+ error = PTR_ERR(dir_name);
+ else
+ error = tmy_mount_perm(dev_name, dir_name, type, flags);
+
+ if (!error && (flags & MS_REMOUNT) == 0)
+ error = tmy_conceal_mount(nd);
+
+ kfree(buf);
+ return error;
+}
+
+static int tmy_sb_umount(struct vfsmount *mnt, int flags)
+{
+ return tmy_umount_perm(mnt);
+}
+
+static int tmy_sb_pivotroot(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+ return tmy_pivot_root_perm(old_nd, new_nd);
+}
+
+#ifdef TMY_LSM_EXPANSION
+static int tmy_task_kill_unlocked(int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tkill_unlocked(int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return tmy_signal_acl(sig, pid);
+}
+#endif
+
+static struct security_operations tomoyo_security_ops = {
+ .task_alloc_security = tmy_task_alloc_security,
+ .task_free_security = tmy_task_free_security,
+ .bprm_alloc_security = tmy_bprm_alloc_security,
+ .bprm_check_security = tmy_bprm_check_security,
+ .bprm_post_apply_creds = tmy_bprm_post_apply_creds,
+ .bprm_free_security = tmy_bprm_free_security,
+ .sysctl = tmy_sysctl,
+ .inode_permission = tmy_inode_permission,
+ .inode_setattr = tmy_inode_setattr,
+ .inode_create = tmy_inode_create,
+ .inode_unlink = tmy_inode_unlink,
+ .inode_mkdir = tmy_inode_mkdir,
+ .inode_rmdir = tmy_inode_rmdir,
+ .inode_symlink = tmy_inode_symlink,
+ .inode_mknod = tmy_inode_mknod,
+ .inode_link = tmy_inode_link,
+ .inode_rename = tmy_inode_rename,
+ .file_fcntl = tmy_file_fcntl,
+ .socket_listen = tmy_socket_listen,
+ .socket_connect = tmy_socket_connect,
+ .socket_bind = tmy_socket_bind,
+ .socket_sendmsg = tmy_socket_sendmsg,
+ .sb_mount = tmy_sb_mount,
+ .sb_umount = tmy_sb_umount,
+ .sb_pivotroot = tmy_sb_pivotroot,
+#ifdef TMY_LSM_EXPANSION
+ .socket_post_accept = tmy_socket_post_accept,
+ .post_recv_datagram = tmy_post_recv_datagram,
+ .task_kill_unlocked = tmy_task_kill_unlocked,
+ .task_tkill_unlocked = tmy_task_tkill_unlocked,
+ .task_tgkill_unlocked = tmy_task_tgkill_unlocked,
+#endif
+};
+
+static int __init tmy_init(void)
+{
+
+ /* register ourselves with the security framework */
+ if (register_security(&tomoyo_security_ops))
+ panic("Failure registering TOMOYO Linux");
+
+ printk(KERN_INFO "TOMOYO Linux initialized\n");
+
+ /*tmy_proc_init();*/
+
+ tmy_cachep = kmem_cache_create("tomoyo_security",
+ sizeof(struct tmy_security),
+ 0, SLAB_PANIC, NULL);
+
+ init_task.security = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+ ((struct tmy_security *) init_task.security)->domain = &KERNEL_DOMAIN;
+ ((struct tmy_security *) init_task.security)->prev_domain = NULL;
+ ((struct tmy_security *) init_task.security)->flags = 0;
+
+ return 0;
+}
+
+security_initcall(tmy_init);
This patch allows administrators use conditional permission.
TOMOYO Linux supports conditional permission based on
process's UID,GID etc. and/or requested pathname's UID/GID.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/condition.c | 680 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 680 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/condition.c 2007-10-02 11:26:22.000000000 +0900
@@ -0,0 +1,680 @@
+/*
+ * security/tomoyo/condition.c
+ *
+ * Functions to support conditional access control for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/**
+ * tmy_find_condition_part - check whether a line contains condition part.
+ * @data: a line to check.
+ *
+ * Returns pointer to condition part if found.
+ * Returns NULL if not found.
+ *
+ * Since the trailing spaces are removed by tmy_normalize_line(),
+ * the last "\040if\040" sequence corresponds to condition part.
+ */
+char *tmy_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';
+ }
+ return cp;
+}
+
+#define VALUE_TYPE_DECIMAL 1 /* 01 */
+#define VALUE_TYPE_OCTAL 2 /* 10 */
+#define VALUE_TYPE_HEXADECIMAL 3 /* 11 */
+
+static int tmy_parse_ulong(unsigned long *result, const 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; /* 00 */
+ *str = ep;
+ return (base == 16 ? VALUE_TYPE_HEXADECIMAL :
+ (base == 8 ? VALUE_TYPE_OCTAL : VALUE_TYPE_DECIMAL));
+}
+
+static void tmy_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);
+}
+
+/* List of conditins. */
+static struct condition_list {
+ struct condition_list *next;
+ int length;
+ /* "unsigned long condition[length]" comes here.*/
+} head;
+
+#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 MAX_KEYWORD 19
+
+static struct {
+ const char *keyword;
+ const int keyword_len; /* strlen(keyword) */
+} cc_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 }
+};
+
+/**
+ * tmy_assign_condition - create condition part.
+ * @condition: pointer to condition part.
+ *
+ * Returns pointer to "struct condition_list" on success.
+ * Returns NULL on failure.
+ */
+const struct condition_list *tmy_assign_condition(const char *condition)
+{
+ const char *start;
+ struct condition_list *ptr;
+ struct condition_list *new_ptr;
+ unsigned long *ptr2;
+ int counter = 0;
+ int size;
+ int left;
+ int right;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ if (strncmp(condition, "if ", 3))
+ return NULL;
+ condition += 3;
+ start = condition;
+ while (*condition) {
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&left_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range1;
+ condition++;
+ if (!tmy_parse_ulong(&left_max, &condition)
+ || left_min > left_max)
+ goto out;
+ counter++; /* body */
+not_range1: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=')
+ condition++;
+ else
+ goto out;
+ counter++; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&right_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range2;
+ condition++;
+ if (!tmy_parse_ulong(&right_max, &condition)
+ || right_min > right_max)
+ goto out;
+ counter++; /* body */
+not_range2: ;
+ }
+ }
+ size = sizeof(*new_ptr) + counter * sizeof(unsigned long);
+ new_ptr = tmy_alloc(size);
+ if (!new_ptr)
+ return NULL;
+ new_ptr->length = counter;
+ ptr2 = (unsigned long *) (((u8 *) new_ptr) + sizeof(*new_ptr));
+ condition = start;
+ while (*condition) {
+ unsigned int match = 0;
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&left_min, &condition) << 2;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range3;
+ condition++;
+ match |= tmy_parse_ulong(&left_max, &condition) << 4;
+ counter--; /* body */
+ left++;
+not_range3: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=') {
+ match |= 1; condition++;
+ } else
+ goto out2;
+ counter--; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&right_min, &condition) << 6;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range4;
+ condition++;
+ match |= tmy_parse_ulong(&right_max, &condition) << 8;
+ counter--; /* body */
+ right++;
+not_range4: ;
+ }
+ if (counter < 0)
+ goto out2;
+ *ptr2++ = (match << 16) | (left << 8) | right;
+ if (left >= MAX_KEYWORD)
+ *ptr2++ = left_min;
+ if (left == MAX_KEYWORD + 1)
+ *ptr2++ = left_max;
+ if (right >= MAX_KEYWORD)
+ *ptr2++ = right_min;
+ if (right == MAX_KEYWORD + 1)
+ *ptr2++ = right_max;
+ }
+ {
+ static DECLARE_MUTEX(lock);
+ struct condition_list *prev = NULL;
+ down(&lock);
+ for (ptr = &head; ptr; prev = ptr, ptr = ptr->next) {
+ /* Don't compare if size differs. */
+ if (ptr->length != new_ptr->length)
+ continue;
+ /*
+ * Compare ptr and new_ptr
+ * except ptr->next and new_ptr->next .
+ */
+ if (memcmp(((u8 *) ptr) + sizeof(ptr->next),
+ ((u8 *) new_ptr) + sizeof(new_ptr->next),
+ size - sizeof(ptr->next)))
+ continue;
+ /* Same entry found. Share this entry. */
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ goto ok;
+ }
+ /* Same entry not found. Save this entry. */
+ ptr = tmy_alloc_element(size);
+ if (ptr)
+ memmove(ptr, new_ptr, size);
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ /* Append to chain. */
+ prev->next = new_ptr;
+ok: ;
+ up(&lock);
+ }
+ return new_ptr;
+out2: ;
+ tmy_free(new_ptr);
+out: ;
+ return NULL;
+}
+
+/* Get inode's attribute information. */
+static void tmy_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 = 1;
+ }
+ }
+
+ 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 = 1;
+ }
+ }
+ 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 = 1;
+ }
+ }
+ dput(dentry);
+ }
+}
+
+/**
+ * tmy_check_condition - check condition part.
+ * @ptr: pointer to "struct condition_list".
+ * @obj: pointer to "struct obj_info". May be NULL.
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_check_condition(const struct condition_list *ptr, struct obj_info *obj)
+{
+ struct task_struct *task = current;
+ int i;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ const unsigned long *ptr2;
+ if (!ptr)
+ return 0;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ for (i = 0; i < ptr->length; i++) {
+ const u8 match = ((*ptr2) >> 16) & 1;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if ((left >= PATH1_UID && left < MAX_KEYWORD)
+ || (right >= PATH1_UID && right < MAX_KEYWORD)) {
+ if (!obj)
+ goto out;
+ if (!obj->validate_done) {
+ tmy_get_attributes(obj);
+ obj->validate_done = 1;
+ }
+ }
+ switch (left) {
+ case TASK_UID:
+ left_min = task->uid;
+ left_max = left_min;
+ break;
+ case TASK_EUID:
+ left_min = task->euid;
+ left_max = left_min;
+ break;
+ case TASK_SUID:
+ left_min = task->suid;
+ left_max = left_min;
+ break;
+ case TASK_FSUID:
+ left_min = task->fsuid;
+ left_max = left_min;
+ break;
+ case TASK_GID:
+ left_min = task->gid;
+ left_max = left_min;
+ break;
+ case TASK_EGID:
+ left_min = task->egid;
+ left_max = left_min;
+ break;
+ case TASK_SGID:
+ left_min = task->sgid;
+ left_max = left_min;
+ break;
+ case TASK_FSGID:
+ left_min = task->fsgid;
+ left_max = left_min;
+ break;
+ case TASK_PID:
+ left_min = task->pid;
+ left_max = left_min;
+ break;
+ case TASK_PPID:
+ left_min = sys_getppid();
+ left_max = left_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case MAX_KEYWORD:
+ left_min = *ptr2++;
+ left_max = left_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ left_min = *ptr2++;
+ left_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ switch (right) {
+ case TASK_UID:
+ right_min = task->uid;
+ right_max = right_min;
+ break;
+ case TASK_EUID:
+ right_min = task->euid;
+ right_max = right_min;
+ break;
+ case TASK_SUID:
+ right_min = task->suid;
+ right_max = right_min;
+ break;
+ case TASK_FSUID:
+ right_min = task->fsuid;
+ right_max = right_min;
+ break;
+ case TASK_GID:
+ right_min = task->gid;
+ right_max = right_min;
+ break;
+ case TASK_EGID:
+ right_min = task->egid;
+ right_max = right_min;
+ break;
+ case TASK_SGID:
+ right_min = task->sgid;
+ right_max = right_min;
+ break;
+ case TASK_FSGID:
+ right_min = task->fsgid;
+ right_max = right_min;
+ break;
+ case TASK_PID:
+ right_min = task->pid;
+ right_max = right_min;
+ break;
+ case TASK_PPID:
+ right_min = sys_getppid();
+ right_max = right_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case MAX_KEYWORD:
+ right_min = *ptr2++;
+ right_max = right_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ right_min = *ptr2++;
+ right_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ 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 -EPERM;
+ }
+ return 0;
+}
+
+/**
+ * tmy_dump_condition - dump condition part.
+ * @head: pointer to "struct io_buffer".
+ * @ptr: pointer to "struct condition_list". May be NULL.
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_dump_condition(struct io_buffer *head, const struct condition_list *ptr)
+{
+ int i;
+ const unsigned long *ptr2;
+ char buffer[32];
+ if (!ptr)
+ goto last;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ memset(buffer, 0, sizeof(buffer));
+ for (i = 0; i < ptr->length; i++) {
+ const u16 match = (*ptr2) >> 16;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if (tmy_io_printf(head, "%s", i ? " " : " if "))
+ break;
+ if (left < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s", cc_keyword[left].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 2) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (left == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 4) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ if (tmy_io_printf(head, "%s", (match & 1) ? "=" : "!="))
+ break;
+ if (right < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s",
+ cc_keyword[right].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 6) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (right == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 8) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ }
+ if (i < ptr->length)
+ return -ENOMEM;
+last: ;
+ return tmy_io_printf(head, "\n") ? -ENOMEM : 0;
+}
LSM expansion for TOMOYO Linux.
LSM hooks for sending signal:
* task_kill_unlocked is added in sys_kill
* task_tkill_unlocked is added in sys_tkill
* task_tgkill_unlocked is added in sys_tgkill
LSM hooks for network accept and recv:
* socket_post_accept is modified to return int.
* post_recv_datagram is added in skb_recv_datagram.
You can try TOMOYO Linux without this patch, but in that case, you
can't use access control functionality for restricting signal
transmission and incoming network data.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/security.h | 91 +++++++++++++++++++++++++++++++++++++++++++----
kernel/signal.c | 17 ++++++++
net/core/datagram.c | 22 +++++++++++
net/socket.c | 7 ++-
security/dummy.c | 32 ++++++++++++++--
5 files changed, 157 insertions(+), 12 deletions(-)
--- linux-2.6.orig/include/linux/security.h 2007-10-02 11:11:51.000000000 +0900
+++ linux-2.6/include/linux/security.h 2007-10-02 11:26:23.000000000 +0900
@@ -628,6 +628,22 @@ struct request_sock;
* @sig contains the signal value.
* @secid contains the sid of the process where the signal originated
* Return 0 if permission is granted.
+ * @task_kill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_kill.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
+ * @task_tkill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_tkill.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
+ * @task_tgkill_unlocked:
+ * Check permission before sending signal @sig to the process of @pid
+ * with sys_tgkill.
+ * @tgid contains the thread group id.
+ * @pid contains the pid of target process.
+ * @sig contains the signal value.
* @task_wait:
* Check permission before allowing a process to reap a child process @p
* and collect its status information.
@@ -749,8 +765,12 @@ struct request_sock;
* @socket_post_accept:
* This hook allows a security module to copy security
* information into the newly created socket's inode.
+ * This hook also allows a security module to filter connections
+ * from unwanted peers.
+ * The connection will be aborted if this hook returns nonzero.
* @sock contains the listening socket structure.
* @newsock contains the newly created server socket for connection.
+ * Return 0 if permission is granted.
* @socket_sendmsg:
* Check permission before transmitting a message to another socket.
* @sock contains the socket structure.
@@ -764,6 +784,11 @@ struct request_sock;
* @size contains the size of message structure.
* @flags contains the operational flags.
* Return 0 if permission is granted.
+ * @post_recv_datagram:
+ * Check permission after receiving a datagram.
+ * @sk contains the socket.
+ * @skb contains the socket buffer (may be NULL).
+ * @flags contains the operational flags.
* @socket_getsockname:
* Check permission before the local address (name) of the socket object
* @sock is retrieved.
@@ -1279,6 +1304,9 @@ struct security_operations {
int (*task_movememory) (struct task_struct * p);
int (*task_kill) (struct task_struct * p,
struct siginfo * info, int sig, u32 secid);
+ int (*task_kill_unlocked) (int pid, int sig);
+ int (*task_tkill_unlocked) (int pid, int sig);
+ int (*task_tgkill_unlocked) (int tgid, int pid, int sig);
int (*task_wait) (struct task_struct * p);
int (*task_prctl) (int option, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
@@ -1346,12 +1374,16 @@ struct security_operations {
struct sockaddr * address, int addrlen);
int (*socket_listen) (struct socket * sock, int backlog);
int (*socket_accept) (struct socket * sock, struct socket * newsock);
- void (*socket_post_accept) (struct socket * sock,
- struct socket * newsock);
+#define TMY_LSM_EXPANSION
+ int (*socket_post_accept) (struct socket *sock,
+ struct socket *newsock);
int (*socket_sendmsg) (struct socket * sock,
struct msghdr * msg, int size);
int (*socket_recvmsg) (struct socket * sock,
struct msghdr * msg, int size, int flags);
+ int (*post_recv_datagram) (struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags);
int (*socket_getsockname) (struct socket * sock);
int (*socket_getpeername) (struct socket * sock);
int (*socket_getsockopt) (struct socket * sock, int level, int optname);
@@ -1967,6 +1999,21 @@ static inline int security_task_kill (st
return security_ops->task_kill (p, info, sig, secid);
}
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+ return security_ops->task_kill_unlocked(pid, sig);
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+ return security_ops->task_tkill_unlocked(pid, sig);
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return security_ops->task_tgkill_unlocked(tgid, pid, sig);
+}
+
static inline int security_task_wait (struct task_struct *p)
{
return security_ops->task_wait (p);
@@ -2645,6 +2692,21 @@ static inline int security_task_kill (st
return 0;
}
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return 0;
+}
+
static inline int security_task_wait (struct task_struct *p)
{
return 0;
@@ -2870,10 +2932,10 @@ static inline int security_socket_accept
return security_ops->socket_accept(sock, newsock);
}
-static inline void security_socket_post_accept(struct socket * sock,
- struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
- security_ops->socket_post_accept(sock, newsock);
+ return security_ops->socket_post_accept(sock, newsock);
}
static inline int security_socket_sendmsg(struct socket * sock,
@@ -2889,6 +2951,13 @@ static inline int security_socket_recvms
return security_ops->socket_recvmsg(sock, msg, size, flags);
}
+static inline int security_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ return security_ops->post_recv_datagram(sk, skb, flags);
+}
+
static inline int security_socket_getsockname(struct socket * sock)
{
return security_ops->socket_getsockname(sock);
@@ -3033,9 +3102,10 @@ static inline int security_socket_accept
return 0;
}
-static inline void security_socket_post_accept(struct socket * sock,
- struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
+ return 0;
}
static inline int security_socket_sendmsg(struct socket * sock,
@@ -3051,6 +3121,13 @@ static inline int security_socket_recvms
return 0;
}
+static inline int security_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags);
+{
+ return 0;
+}
+
static inline int security_socket_getsockname(struct socket * sock)
{
return 0;
--- linux-2.6.orig/kernel/signal.c 2007-10-02 11:11:51.000000000 +0900
+++ linux-2.6/kernel/signal.c 2007-10-02 11:26:23.000000000 +0900
@@ -2196,6 +2196,11 @@ asmlinkage long
sys_kill(int pid, int sig)
{
struct siginfo info;
+ int ret;
+
+ ret = security_task_kill_unlocked(pid, sig);
+ if (ret)
+ return ret;
info.si_signo = sig;
info.si_errno = 0;
@@ -2251,10 +2256,16 @@ static int do_tkill(int tgid, int pid, i
*/
asmlinkage long sys_tgkill(int tgid, int pid, int sig)
{
+ int ret;
+
/* This is only valid for single tasks */
if (pid <= 0 || tgid <= 0)
return -EINVAL;
+ ret = security_task_tgkill_unlocked(tgid, pid, sig);
+ if (ret)
+ return ret;
+
return do_tkill(tgid, pid, sig);
}
@@ -2264,10 +2275,16 @@ asmlinkage long sys_tgkill(int tgid, int
asmlinkage long
sys_tkill(int pid, int sig)
{
+ int ret;
+
/* This is only valid for single tasks */
if (pid <= 0)
return -EINVAL;
+ ret = security_task_tkill_unlocked(pid, sig);
+ if (ret)
+ return ret;
+
return do_tkill(0, pid, sig);
}
--- linux-2.6.orig/net/socket.c 2007-10-02 11:11:51.000000000 +0900
+++ linux-2.6/net/socket.c 2007-10-02 11:26:23.000000000 +0900
@@ -1434,13 +1434,16 @@ asmlinkage long sys_accept(int fd, struc
goto out_fd;
}
+ /* Filter connections from unwanted peers like TCP Wrapper. */
+ err = security_socket_post_accept(sock, newsock);
+ if (err)
+ goto out_fd;
+
/* File flags are not inherited via accept() unlike another OSes. */
fd_install(newfd, newfile);
err = newfd;
- security_socket_post_accept(sock, newsock);
-
out_put:
fput_light(sock->file, fput_needed);
out:
--- linux-2.6.orig/security/dummy.c 2007-10-02 11:11:51.000000000 +0900
+++ linux-2.6/security/dummy.c 2007-10-02 11:26:23.000000000 +0900
@@ -564,6 +564,21 @@ static int dummy_task_kill (struct task_
return 0;
}
+static int dummy_task_kill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static int dummy_task_tkill_unlocked(int pid, int sig)
+{
+ return 0;
+}
+
+static int dummy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+ return 0;
+}
+
static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
@@ -741,10 +756,10 @@ static int dummy_socket_accept (struct s
return 0;
}
-static void dummy_socket_post_accept (struct socket *sock,
- struct socket *newsock)
+static int dummy_socket_post_accept(struct socket *sock,
+ struct socket *newsock)
{
- return;
+ return 0;
}
static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg,
@@ -759,6 +774,13 @@ static int dummy_socket_recvmsg (struct
return 0;
}
+static int dummy_post_recv_datagram(struct sock *sk,
+ struct sk_buff *skb,
+ unsigned int flags)
+{
+ return 0;
+}
+
static int dummy_socket_getsockname (struct socket *sock)
{
return 0;
@@ -1053,6 +1075,9 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, task_movememory);
set_to_dummy_if_null(ops, task_wait);
set_to_dummy_if_null(ops, task_kill);
+ set_to_dummy_if_null(ops, task_kill_unlocked);
+ set_to_dummy_if_null(ops, task_tkill_unlocked);
+ set_to_dummy_if_null(ops, task_tgkill_unlocked);
set_to_dummy_if_null(ops, task_prctl);
set_to_dummy_if_null(ops, task_reparent_to_init);
set_to_dummy_if_null(ops, task_to_inode);
@@ -1096,6 +1121,7 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, socket_post_accept);
set_to_dummy_if_null(ops, socket_sendmsg);
set_to_dummy_if_null(ops, socket_recvmsg);
+ set_to_dummy_if_null(ops, post_recv_datagram);
set_to_dummy_if_null(ops, socket_getsockname);
set_to_dummy_if_null(ops, socket_getpeername);
set_to_dummy_if_null(ops, socket_setsockopt);
--- linux-2.6.orig/net/core/datagram.c 2007-10-02 11:11:51.000000000 +0900
+++ linux-2.6/net/core/datagram.c 2007-10-02 11:26:23.000000000 +0900
@@ -55,6 +55,7 @@
#include <net/checksum.h>
#include <net/sock.h>
#include <net/tcp_states.h>
+#include <linux/security.h>
/*
* Is a socket 'connection oriented' ?
@@ -178,6 +179,27 @@ struct sk_buff *skb_recv_datagram(struct
} else
skb = skb_dequeue(&sk->sk_receive_queue);
+ error = security_post_recv_datagram(sk, skb, flags);
+ if (error) {
+ unsigned long cpu_flags;
+
+ if (!(flags & MSG_PEEK))
+ goto no_peek;
+
+ 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);
+no_peek:
+ skb_free_datagram(sk, skb);
+ goto no_packet;
+ }
+
if (skb)
return skb;
Kconfig and Makefile for TOMOYO Linux.
TOMOYO Linux is placed in security/tomoyo .
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/Kconfig | 1 +
security/Makefile | 1 +
security/tomoyo/Kconfig | 18 ++++++++++++++++++
security/tomoyo/Makefile | 3 +++
4 files changed, 23 insertions(+)
--- linux-2.6.orig/security/Kconfig 2007-10-02 11:11:53.000000000 +0900
+++ linux-2.6/security/Kconfig 2007-10-02 11:26:21.000000000 +0900
@@ -94,6 +94,7 @@ config SECURITY_ROOTPLUG
If you are unsure how to answer this question, answer N.
source security/selinux/Kconfig
+source security/tomoyo/Kconfig
endmenu
--- linux-2.6.orig/security/Makefile 2007-10-02 11:11:53.000000000 +0900
+++ linux-2.6/security/Makefile 2007-10-02 11:26:21.000000000 +0900
@@ -16,3 +16,4 @@ obj-$(CONFIG_SECURITY) += security.o d
obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o
+obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
\ No newline at end of file
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/Kconfig 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,18 @@
+config SECURITY_TOMOYO
+ bool "TOMOYO Linux support"
+ depends on SECURITY
+ select SECURITY_NETWORK
+ select AUDIT
+ default n
+ help
+ This selects TOMOYO Linux.
+
+ TOMOYO Linux is a domain-based access control method using LSM.
+ If you answer Y, you will need a policy loader program
+ (/sbin/tomoyo-init) and some configuration files.
+ You can get them from
+ <http://tomoyo.sourceforge.jp/en/2.1.x/>
+
+ TOMOYO Linux is also applicable to figuring out the behavior
+ of your system, for TOMOYO uses the canonicalized absolute
+ pathnames and TreeView style domain transitions.
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/Makefile 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo.o tomoyo_func.o
+tomoyo_func-objs := domain.o common.o realpath.o audit.o file.o exec.o net.o mount.o signal.o condition.o
+EXTRA_CFLAGS += -Isecurity/tomoyo/include
On 2007/08/27 21:11, Kyle Moffett wrote:
>This is probably not acceptable; I doubt there's a chance in hell
>that TOMOYO will get merged as long as it has text-based-language
>parsing in the kernel. You also have $NEW_RANDOM_ABUSE_OF_PROCFS and
>$PATH_BASED_LSM_ISSUES. See the long flamewars on AppArmor for
>discussion on the latter.
About problems of pathname-based access control and countermeasures:
TOMOYO Linux has many countermeasures that prevents many of pathname-based access control's problems.
In short, in TOMOYO Linux, attackers can't create link freely, can't rename freely,
can't manipulate namespace freely.
Not all problems can be solved (some of causes are current LSM specification),
but is enough for SOHO (Small Office/Home Office)/personal systems.
Last discussion log is at http://lkml.org/lkml/2007/8/28/113 .
About policy file handling:
Common implementations treat policy file on the filesystem as the up-to-date data,
and the kernel keeps a copy of policy file in kernel's memory.
But TOMOYO's implementation is opposite.
TOMOYO Linux has "learning mode" feature that helps administrator develop ACL (access control list).
Since the "learning mode" automatically appends entries to in-memory datastructure,
TOMOYO Linux implements in-memory datastructure using a singly-linked list
using a kind of DBMS (DataBase Management System).
TOMOYO Linux regards the ACL in kernel's DBMS as the up-to-date data
and the ACL in the policy file as a backup.
TOMOYO Linux's policy file consists of instructions for reproducing a snapshot of
ACL entries in kernel's DBMS which was saved in the past.
This is the reason why TOMOYO Linux doesn't use binary (offset-from-start-of-policy) format
for policy file, and in-kernel policy parser exists.
Last discussion log is at http://marc.info/?l=linux-security-module&m=119039218805158&w=2 .
On 2007/08/27 23:49, Paul Moore wrote:
>This has been discussed several times on various lists and is not considered
>an acceptable solution to blocking incoming stream connection attempts.
>Please take a look at the existing LSM stream connection request hooks as
>well as how SELinux makes use of them.
(snip)
>Can you explain to me why this is not possible using the existing
>security_socket_sock_rcv_skb() LSM hook?
About network hook expansion:
TOMOYO Linux makes use of userspace intervention to allow/reject connections and/or packets
based on the application's domain.
Current network-related LSM hooks can't know the final recipient of connections and/or packets.
This is the reason why TOMOYO Linux wants to add post-accept() and post-recvmsg() hooks.
Last discussion log is at http://lkml.org/lkml/2007/9/5/98 .
On Tue, Oct 02, 2007 at 04:31:48PM +0900, Kentaro Takeda wrote:
> Common functions for TOMOYO Linux.
>
> TOMOYO Linux uses /sys/kernel/security/tomoyo interface for configuration.
>
This seems like a bit of an abuse of sysfs. Isn't this precisely what
configfs is for?
It seems that patches 1-3 are missing.
I'd also suggest making all of the patches a reply to the first email, so
they can be threaded.
- James
--
James Morris
<[email protected]>
> It seems that patches 1-3 are missing.
>
> I'd also suggest making all of the patches a reply to the first email, so
> they can be threaded.
Thanks a lot for your suggestions.
Actually we did just as you wrote, but somehow 0-3 parts are missing.
Unfortunately, the parent message [00/15] is in the three.
If the [00/15] will be delivered, everything goes just fine.
We are going to wait some more time and decide to repost them again.
Thanks again.
Kentaro Takeda
On Tue, 2 Oct 2007, Kentaro Takeda wrote:
> +
> + mb(); /* Instead of using spinlock. */
> + ptr = domain_initializer_list;
> + if (ptr) {
> + while (ptr->next)
> + ptr = ptr->next;
> + ptr->next = new_entry;
> + } else
> + domain_initializer_list = new_entry;
> +
Please use standard kernel list handling, per include/linux/list.h
Why do you need to avoid spinlocks for these manipulations?
- James
--
James Morris
<[email protected]>
Hello.
Thank you for your comment.
James Morris wrote:
> Why do you need to avoid spinlocks for these manipulations?
I don't need to use spinlocks here.
What I need to do here is avoid read/write reordering,
so mb() will be appropriate here.
struct something {
struct something *next;
void *data;
};
struct something *prev_ptr = ...;
struct something *new_ptr = kmalloc(sizeof(*new_ptr), GFP_KERNEL);
new_ptr->next = NULL;
new_ptr->data = some_value;
mb();
prev_ptr->next = new_ptr;
TOMOYO Linux doesn't use locks for reading singly-linked list.
Thus, new_ptr->data has to be made visible to other CPUs
before new_ptr is appended at the tail of singly-linked list.
Otherwise, other CPU may read undefined new_ptr->data values.
Performance is not critical because this mb() is called
only when appending new entry to the singly-linked list.
> Please use standard kernel list handling, per include/linux/list.h
Since new_ptr never be removed, new_ptr needn't to know
it's previous element's address, thus having only ->next pointer is enough.
new_ptr->next is assigned *only once*
(initialized with NULL, and assigned non-NULL later),
indicating "if ptr->next == NULL, ptr is the last element" and
"if ptr->next != NULL, ptr is not the last element".
If I use "struct hlist_node" which has two pointers,
it needs more memory than "struct something" which has one pointer.
It is possible to use API defined in include/linux/list.h ,
but to reduce memory usage, I dare not use these API.
Singly-linked list's rule in TOMOYO Linux is quite simple,
"ptr->next is NULL if the last element" and "non-NULL if not".
Regards.
On Tue, 2 Oct 2007, Kentaro Takeda wrote:
> LSM expansion for TOMOYO Linux.
>
> LSM hooks for sending signal:
> * task_kill_unlocked is added in sys_kill
> * task_tkill_unlocked is added in sys_tkill
> * task_tgkill_unlocked is added in sys_tgkill
Why do you need racy unlocked versions, in addition to the existing
security_task_kill() hook which is called safely via
check_kill_permission() ?
- James
--
James Morris
<[email protected]>
In article <[email protected]> (at Tue, 2 Oct 2007 21:44:57 +0900), Tetsuo Handa <[email protected]> says:
> If I use "struct hlist_node" which has two pointers,
> it needs more memory than "struct something" which has one pointer.
> It is possible to use API defined in include/linux/list.h ,
> but to reduce memory usage, I dare not use these API.
> Singly-linked list's rule in TOMOYO Linux is quite simple,
> "ptr->next is NULL if the last element" and "non-NULL if not".
Introducing your own list is not good.
Please use hlist or introduce new "slist".
--yoshfuji
On Tue, 2 Oct 2007, Tetsuo Handa wrote:
> Hello.
>
> Thank you for your comment.
>
> James Morris wrote:
> > Why do you need to avoid spinlocks for these manipulations?
>
> I don't need to use spinlocks here.
> What I need to do here is avoid read/write reordering,
> so mb() will be appropriate here.
>
> struct something {
> struct something *next;
> void *data;
> };
>
> struct something *prev_ptr = ...;
> struct something *new_ptr = kmalloc(sizeof(*new_ptr), GFP_KERNEL);
> new_ptr->next = NULL;
> new_ptr->data = some_value;
> mb();
> prev_ptr->next = new_ptr;
You're introducing a custom API, which is open-coded repeatedly throughout
your module.
All linked lists (at least, new ones) must use the standard kernel list
API.
> Performance is not critical because this mb() is called
> only when appending new entry to the singly-linked list.
Most of these uses appear to be slow path or nested inside other locks,
while overall, performance is likely to be dominated by your string
matching and permission checking. Use of mb(), which is typically
considered less understandable, in this case does not appear to be
justified vs. normal locking, and you also need to use the standard list
API. If performance really is an issue, then consider RCU.
- James
--
James Morris
<[email protected]>
Hello.
James Morris wrote:
> Why do you need racy unlocked versions, in addition to the existing
> security_task_kill() hook which is called safely via
> check_kill_permission() ?
TOMOYO Linux provides "delayed enforcing mode" which allows administrator
judge interactively for requests that violated policy.
Sometimes, especially after updating software packages, irregular behavior arise.
So, the administrator prepares for such irregular behavior
by invoking "ccs-queryd" userland program.
The "ccs-queryd" prints the contents of policy violation and
asks the administrator whether to grant the request that violated policy.
This can reduce the possibility of "restarting process failed due to permission denied".
Thus, security_task_kill() which is called with tasklist_lock held
is not what TOMOYO Linux wants.
I know this approach is racy, but TOMOYO Linux wants these unlocked versions
to avoid failure due to permission denial caused by MAC's policy.
Regards.
TOMOYO Linux uses pathnames for auditing and controlling file access.
Therefore, namespace_sem is needed.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
fs/namespace.c | 2 +-
1 files changed, 1 insertion(+), 1 deletion(-)
--- linux-2.6.orig/fs/namespace.c 2007-10-02 11:11:53.000000000 +0900
+++ linux-2.6/fs/namespace.c 2007-10-02 11:26:21.000000000 +0900
@@ -38,7 +38,7 @@ static int event;
static struct list_head *mount_hashtable __read_mostly;
static int hash_mask __read_mostly, hash_bits __read_mostly;
static struct kmem_cache *mnt_cache __read_mostly;
-static struct rw_semaphore namespace_sem;
+struct rw_semaphore namespace_sem;
/* /sys/fs */
decl_subsys(fs, NULL, NULL);
Data structures and prototype defitions for TOMOYO Linux.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/include/realpath.h | 44 +++
security/tomoyo/include/tomoyo.h | 517 +++++++++++++++++++++++++++++++++++++
2 files changed, 561 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/include/realpath.h 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,44 @@
+/*
+ * security/tomoyo/include/realpath.h
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO.
+ */
+
+#ifndef _TMY_REALPATH_H
+#define _TMY_REALPATH_H
+
+struct path_info;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tmy_realpath_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 tmy_alloc(), so caller must tmy_free() */
+/* if these functions didn't return NULL. */
+char *tmy_realpath(const char *pathname);
+char *tmy_realpath_nofollow(const char *pathname);
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt);
+
+/* Allocate memory for structures. */
+/* The RAM is chunked, so NEVER try to kfree() the returned pointer. */
+void *tmy_alloc_element(const unsigned int size);
+
+/* Get used RAM size for tmy_alloc_elements(). */
+unsigned int tmy_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 *tmy_save_name(const char *name);
+
+/* Get used RAM size for tmy_save_name(). */
+unsigned int tmy_get_memory_used_for_save_name(void);
+
+unsigned int tmy_get_memory_used_for_dynamic(void);
+char *sysctlpath_from_table(struct ctl_table *table);
+extern void tmy_realpath_init(void);
+
+#endif
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/include/tomoyo.h 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,517 @@
+/*
+ * security/tomoyo/include/tomoyo.h
+ *
+ * Header for TOMOYO Linux.
+ */
+
+#ifndef _TOMOYO_H
+#define _TOMOYO_H
+
+#define TOMOYO_VERSION_CODE "2.1.0-rc4"
+
+#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>
+
+extern struct rw_semaphore namespace_sem;
+extern struct semaphore domain_acl_lock;
+extern int sbin_init_started;
+
+struct tmy_security {
+ struct domain_info *domain;
+ struct domain_info *prev_domain;
+ u32 flags;
+};
+
+#define TMY_SECURITY ((struct tmy_security *) current->security)
+
+struct path_info {
+ const char *name;
+ u32 hash; /* = full_name_hash(name, strlen(name)) */
+ u16 total_len; /* = strlen(name) */
+ u16 const_len; /* = tmy_const_part_length(name) */
+ u8 is_dir; /* = tmy_strendswith(name, "/") */
+ u8 is_patterned; /* = PathContainsPattern(name) */
+ u16 depth; /* = tmy_path_depth(name) */
+};
+
+#define TMY_MAX_PATHNAME_LEN 4000
+
+struct group_member {
+ struct group_member *next;
+ const struct path_info *member_name;
+ u8 is_deleted;
+};
+
+struct group_entry {
+ struct group_entry *next;
+ const struct path_info *group_name;
+ struct group_member *first_member;
+};
+
+
+struct mini_stat {
+ uid_t uid;
+ gid_t gid;
+ ino_t ino;
+};
+struct dentry;
+struct vfsmount;
+struct obj_info {
+ u8 validate_done;
+ u8 path1_valid;
+ u8 path1_parent_valid;
+ u8 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 condition_list;
+
+struct linux_binprm;
+struct pt_regs;
+
+/*
+ * TOMOYO uses the following structures.
+ * Memory allocated for these structures are never kfree()ed.
+ * Since no locks are used for reading,
+ * assignment must be performed atomically.
+ */
+
+/************************ The structure for domains. ************************/
+
+struct acl_info {
+ struct acl_info *next;
+ const struct condition_list *cond;
+ u8 type;
+ u8 is_deleted;
+} __attribute__((__packed__));
+
+struct domain_info {
+ struct domain_info *next; /* NULL if none. */
+ struct acl_info *first_acl_ptr; /* NULL if none. */
+ const struct path_info *domainname; /* Never NULL. */
+ u8 profile;
+ u8 is_deleted;
+ u8 quota_warned;
+};
+
+#define TMY_MAX_PROFILES 256
+
+struct file_acl {
+ /* type = TMY_TYPE_FILE_ACL */
+ struct acl_info head;
+ u8 perm;
+ u8 u_is_group;
+ union {
+ const struct path_info *filename;
+ const struct group_entry *group;
+ } u;
+};
+
+struct argv0_acl {
+ /* type = TMY_TYPE_ARGV0_ACL */
+ struct acl_info head;
+ const struct path_info *filename; /* Pointer to single pathname. */
+ const struct path_info *argv0; /* strrchr(argv[0], '/') + 1 */
+};
+
+struct single_acl {
+ /* type = TMY_TYPE_* */
+ struct acl_info head;
+ u8 u_is_group;
+ union {
+ const struct path_info *filename;
+ const struct group_entry *group;
+ } u;
+};
+
+struct double_acl {
+ /* type = TMY_TYPE_RENAME_ACL or TMY_TYPE_LINK_ACL, */
+ struct acl_info head;
+ u8 u1_is_group;
+ u8 u2_is_group;
+ union {
+ const struct path_info *filename1;
+ const struct group_entry *group1;
+ } u1;
+ union {
+ const struct path_info *filename2;
+ const struct group_entry *group2;
+ } u2;
+};
+
+struct addr_group_member {
+ struct addr_group_member *next;
+ union {
+ u32 ipv4; /* Host byte order */
+ u16 ipv6[8]; /* Network byte order */
+ } min, max;
+ u8 is_deleted;
+ u8 is_ipv6;
+};
+
+struct addr_group_entry {
+ struct addr_group_entry *next;
+ const struct path_info *group_name;
+ struct addr_group_member *first_member;
+};
+
+#define TMY_TYPE_ADDRESS_GROUP 0
+#define TMY_TYPE_IPv4 1
+#define TMY_TYPE_IPv6 2
+
+struct net_acl {
+ /* type = TYPE_IP_NETWORK_ACL, */
+ struct acl_info head;
+ u8 operation_type;
+ u8 record_type;
+ union {
+ struct {
+ u32 min;
+ u32 max;
+ } ipv4;
+ struct {
+ u16 min[8];
+ u16 max[8];
+ } ipv6;
+ const struct addr_group_entry *group;
+ } u;
+ u16 min_port; /* Start of port number range. */
+ u16 max_port; /* End of port number range. */
+};
+
+struct signal_acl {
+ /* type = TYPE_SIGNAL_ACL */
+ struct acl_info head;
+ u16 sig;
+ /* Pointer to destination pattern. */
+ const struct path_info *domainname;
+};
+
+/************************* Keywords for ACLs. *************************/
+
+#define TMY_AGGREGATOR "aggregator "
+#define TMY_AGGREGATOR_LEN (sizeof(TMY_AGGREGATOR) - 1)
+#define TMY_ALIAS "alias "
+#define TMY_ALIAS_LEN (sizeof(TMY_ALIAS) - 1)
+#define TMY_ALLOW_READ "allow_read "
+#define TMY_ALLOW_READ_LEN (sizeof(TMY_ALLOW_READ) - 1)
+#define TMY_DELETE "delete "
+#define TMY_DELETE_LEN (sizeof(TMY_DELETE) - 1)
+#define TMY_DENY_REWRITE "deny_rewrite "
+#define TMY_DENY_REWRITE_LEN (sizeof(TMY_DENY_REWRITE) - 1)
+#define TMY_FILE_PATTERN "file_pattern "
+#define TMY_FILE_PATTERN_LEN (sizeof(TMY_FILE_PATTERN) - 1)
+#define TMY_INITIALIZE_DOMAIN "initialize_domain "
+#define TMY_INITIALIZE_DOMAIN_LEN (sizeof(TMY_INITIALIZE_DOMAIN) - 1)
+#define TMY_KEEP_DOMAIN "keep_domain "
+#define TMY_KEEP_DOMAIN_LEN (sizeof(TMY_KEEP_DOMAIN) - 1)
+#define TMY_NO_INITIALIZE_DOMAIN "no_initialize_domain "
+#define TMY_NO_INITIALIZE_DOMAIN_LEN (sizeof(TMY_NO_INITIALIZE_DOMAIN) - 1)
+#define TMY_NO_KEEP_DOMAIN "no_keep_domain "
+#define TMY_NO_KEEP_DOMAIN_LEN (sizeof(TMY_NO_KEEP_DOMAIN) - 1)
+#define TMY_PATH_GROUP "path_group "
+#define TMY_PATH_GROUP_LEN (sizeof(TMY_PATH_GROUP) - 1)
+#define TMY_SELECT "select "
+#define TMY_SELECT_LEN (sizeof(TMY_SELECT) - 1)
+#define TMY_UNDELETE "undelete "
+#define TMY_UNDELETE_LEN (sizeof(TMY_UNDELETE) - 1)
+
+#define TMY_ALLOW_MOUNT "allow_mount "
+#define TMY_ALLOW_MOUNT_LEN (sizeof(TMY_ALLOW_MOUNT) - 1)
+#define TMY_DENY_UNMOUNT "deny_unmount "
+#define TMY_DENY_UNMOUNT_LEN (sizeof(TMY_DENY_UNMOUNT) - 1)
+#define TMY_ALLOW_PIVOT_ROOT "allow_pivot_root "
+#define TMY_ALLOW_PIVOT_ROOT_LEN (sizeof(TMY_ALLOW_PIVOT_ROOT) - 1)
+
+#define TMY_USE_PROFILE "use_profile "
+
+#define TMY_ROOT_NAME "<kernel>"
+#define TMY_ROOT_NAME_LEN (sizeof(TMY_ROOT_NAME) - 1)
+
+#define TMY_ALLOW_ARGV0 "allow_argv0 "
+#define TMY_ALLOW_ARGV0_LEN (sizeof(TMY_ALLOW_ARGV0) - 1)
+
+#define TMY_ADDRESS_GROUP "address_group "
+#define TMY_ADDRESS_GROUP_LEN (sizeof(TMY_ADDRESS_GROUP) - 1)
+#define TMY_ALLOW_NETWORK "allow_network "
+#define TMY_ALLOW_NETWORK_LEN (sizeof(TMY_ALLOW_NETWORK) - 1)
+
+#define TMY_ALLOW_SIGNAL "allow_signal "
+#define TMY_ALLOW_SIGNAL_LEN (sizeof(TMY_ALLOW_SIGNAL) - 1)
+
+/******************** Index numbers for Access Controls. ********************/
+
+#define TMY_COMMENT 0
+#define TMY_MAC_FOR_FILE 1
+#define TMY_MAC_FOR_ARGV0 2
+#define TMY_MAC_FOR_NETWORK 3
+#define TMY_MAC_FOR_SIGNAL 4
+#define TMY_DENY_CONCEAL_MOUNT 5
+#define TMY_RESTRICT_MOUNT 6
+#define TMY_RESTRICT_UMOUNT 7
+#define TMY_RESTRICT_PIVOT_ROOT 8
+#define TMY_MAX_ACCEPT_ENTRY 9
+#define TMY_AUDIT_GRANT 10
+#define TMY_AUDIT_REJECT 11
+#define TMY_ALLOW_ENFORCE_GRACE 12
+#define TMY_MAX_CONTROL_INDEX 13
+
+#define TMY_NETWORK_ACL_UDP_BIND 0
+#define TMY_NETWORK_ACL_UDP_CONNECT 1
+#define TMY_NETWORK_ACL_TCP_BIND 2
+#define TMY_NETWORK_ACL_TCP_LISTEN 3
+#define TMY_NETWORK_ACL_TCP_CONNECT 4
+#define TMY_NETWORK_ACL_TCP_ACCEPT 5
+#define TMY_NETWORK_ACL_RAW_BIND 6
+#define TMY_NETWORK_ACL_RAW_CONNECT 7
+
+/******************** Index numbers for updates counter. ********************/
+
+#define TMY_UPDATE_DOMAINPOLICY 0
+#define TMY_UPDATE_SYSTEMPOLICY 1
+#define TMY_UPDATE_EXCEPTIONPOLICY 2
+#define TMY_UPDATE_PROFILE 3
+#define TMY_UPDATE_QUERY 4
+#define TMY_UPDATE_MANAGER 5
+#define TMY_MAX_UPDATES_COUNTER 6
+
+/*********************** Indexes for /proc interfaces. **********************/
+
+#define TMY_POLICY_DOMAINPOLICY 0
+#define TMY_POLICY_SYSTEMPOLICY 1
+#define TMY_POLICY_EXCEPTIONPOLICY 2
+#define TMY_POLICY_DOMAIN_STATUS 3
+#define TMY_INFO_PROCESS_STATUS 4
+#define TMY_INFO_MEMINFO 5
+#define TMY_INFO_SELFDOMAIN 6
+#define TMY_PROFILE 7
+#define TMY_POLICY_QUERY 8
+#define TMY_POLICY_MANAGER 9
+#define TMY_INFO_UPDATESCOUNTER 10
+#define TMY_VERSION 11
+
+/******************** The structure for /proc interfaces. *******************/
+
+struct io_buffer {
+ int (*read) (struct io_buffer *);
+ struct semaphore read_sem;
+ int (*write) (struct io_buffer *);
+ struct semaphore write_sem;
+ int (*poll) (struct file *file, poll_table *wait);
+ void *read_var1; /* The position currently reading from. */
+ void *read_var2; /* Extra variables for reading. */
+ struct domain_info *write_var1; /* The position currently writing to.*/
+ int read_step; /* The step for reading. */
+ char *read_buf; /* Buffer for reading. */
+ int read_eof; /* EOF flag for reading. */
+ int read_avail; /* Bytes available for reading. */
+ int readbuf_size; /* Size of read buffer. */
+ char *write_buf; /* Buffer for writing. */
+ int write_avail; /* Bytes available for writing. */
+ int writebuf_size; /* Size of write buffer. */
+};
+
+/************************* PROTOTYPES *************************/
+
+char *tmy_find_condition_part(char *data);
+const struct condition_list *tmy_assign_condition(const char *condition);
+int tmy_check_condition(const struct condition_list *ptr,
+ struct obj_info *obj);
+int tmy_dump_condition(struct io_buffer *head,
+ const struct condition_list *ptr);
+const char *tmy_get_exe(void);
+const char *tmy_acltype2keyword(const unsigned int acl_type);
+
+int tmy_mount_perm(char *dev_name,
+ char *dir_name,
+ char *type,
+ unsigned long flags);
+int tmy_conceal_mount(struct nameidata *nd);
+int tmy_umount_perm(struct vfsmount *mnt);
+int tmy_add_mount_policy(char *data, const u8 is_delete);
+int tmy_read_mount_policy(struct io_buffer *head);
+int tmy_add_no_umount_policy(char *data, const u8 is_delete);
+int tmy_read_no_umount_policy(struct io_buffer *head);
+int tmy_pivot_root_perm(struct nameidata *old_nd,
+ struct nameidata *new_nd);
+int tmy_add_pivot_root_policy(char *data, const u8 is_delete);
+int tmy_read_pivot_root_policy(struct io_buffer *head);
+
+int tmy_add_aggregator_policy(char *data, const u8 is_delete);
+int tmy_add_addr_group_policy(char *data, const u8 is_delete);
+int tmy_add_alias_policy(char *data, const u8 is_delete);
+int tmy_add_argv0_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+int tmy_add_acl(struct acl_info *ptr,
+ struct domain_info *domain,
+ struct acl_info *new_ptr);
+int tmy_add_domain_initializer_policy(char *data,
+ const u8 is_not,
+ const u8 is_delete);
+int tmy_add_domain_keeper_policy(char *data,
+ const u8 is_not,
+ const u8 is_delete);
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation);
+int tmy_add_file_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+int tmy_add_globally_readable_policy(char *data, const u8 is_delete);
+int tmy_add_group_policy(char *data, const u8 is_delete);
+int tmy_add_network_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+int tmy_add_no_rewrite_policy(char *pattern, const u8 is_delete);
+int tmy_add_pattern_policy(char *data, const u8 is_delete);
+int tmy_supervisor(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+
+int tmy_del_acl(struct acl_info *ptr);
+int tmy_delete_domain(char *data);
+int tmy_is_correct_domain(const unsigned char *domainname,
+ const char *function);
+int tmy_correct_path(const char *filename,
+ const int start_type,
+ const int pattern_type,
+ const int end_type,
+ const char *function);
+int tmy_is_domain_def(const unsigned char *buffer);
+int tmy_path_match(const struct path_info *pathname0,
+ const struct path_info *pattern0);
+int tmy_read_aggregator_policy(struct io_buffer *head);
+int tmy_read_alias_policy(struct io_buffer *head);
+int tmy_read_domain_initializer_policy(struct io_buffer *head);
+int tmy_read_domain_keeper_policy(struct io_buffer *head);
+int tmy_read_globally_readable_policy(struct io_buffer *head);
+int tmy_read_group_policy(struct io_buffer *head);
+int tmy_read_no_rewrite_policy(struct io_buffer *head);
+int tmy_read_pattern_policy(struct io_buffer *head);
+int tmy_read_addr_group_policy(struct io_buffer *head);
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0);
+int tmy_network_listen_acl(const u8 is_ipv6,
+ const u8 *address,
+ const u16 port);
+int tmy_network_connect_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_bind_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_sendmsg_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+int tmy_network_accept_acl(const u8 is_ipv6,
+ const u8 *address,
+ const u16 port);
+int tmy_network_recvmsg_acl(const u8 is_ipv6,
+ const int sock_type,
+ const u8 *address,
+ const u16 port);
+
+int tmy_signal_acl(int sig, int pid);
+int tmy_add_signal_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const u8 is_delete);
+
+char *tmy_init_audit_log(int *len);
+int tmy_write_audit_log(char *log, const u8 is_granted, const u8 is_enforce);
+int tmy_acltype2paths(const unsigned int acl_type);
+int tmy_io_printf(struct io_buffer *head, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+struct domain_info *tmy_find_domain(const char *domainname);
+struct domain_info *tmy_new_domain(const char *domainname, const u8 profile);
+struct domain_info *tmy_undelete_domain(const char *domainname0);
+u8 tmy_accept(const unsigned int index,
+ struct domain_info * const domain);
+u8 tmy_enforce(const unsigned int index);
+unsigned int tmy_flags(const unsigned int index);
+unsigned int tmy_audit_grant(void);
+unsigned int tmy_audit_reject(void);
+void tmy_update_counter(const unsigned char index);
+void *tmy_alloc(const size_t size);
+void tmy_free(const void *p);
+void tmy_fill_path_info(struct path_info *ptr);
+
+static inline int tmy_pathcmp(const struct path_info *a,
+ const struct path_info *b)
+{
+ return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+extern struct domain_info KERNEL_DOMAIN;
+void tmy_load_policy(const char *filename);
+int tmy_find_next_domain(struct linux_binprm *,
+ struct domain_info **);
+/*void tmy_proc_init(void);*/
+
+struct path_info;
+
+int tmy_exec_perm(const struct path_info *filename, struct file *filp);
+/* Check whether the given dentry is allowed to read/write/execute. */
+int tmy_open_perm(struct dentry *dentry, struct vfsmount *mnt, const int flag);
+/* Check whether the given dentry is allowed to write. */
+int tmy_single_write_perm(const unsigned int operation,
+ struct dentry *dentry,
+ struct vfsmount *mnt);
+int tmy_double_write_perm(const unsigned int operation,
+ struct dentry *dentry1,
+ struct vfsmount *mnt1,
+ struct dentry *dentry2,
+ struct vfsmount *mnt2);
+int tmy_rewrite_perm(struct file *filp);
+
+struct inode;
+
+/******************** Index numbers for Access Controls. ********************/
+
+#define TMY_TYPE_CREATE_ACL 0
+#define TMY_TYPE_UNLINK_ACL 1
+#define TMY_TYPE_MKDIR_ACL 2
+#define TMY_TYPE_RMDIR_ACL 3
+#define TMY_TYPE_MKFIFO_ACL 4
+#define TMY_TYPE_MKSOCK_ACL 5
+#define TMY_TYPE_MKBLOCK_ACL 6
+#define TMY_TYPE_MKCHAR_ACL 7
+#define TMY_TYPE_TRUNCATE_ACL 8
+#define TMY_TYPE_SYMLINK_ACL 9
+#define TMY_TYPE_LINK_ACL 10
+#define TMY_TYPE_RENAME_ACL 11
+#define TMY_TYPE_REWRITE_ACL 12
+
+#define TMY_TYPE_FILE_ACL 100
+#define TMY_TYPE_ARGV0_ACL 101
+#define TMY_TYPE_IP_NETWORK_ACL 103
+#define TMY_TYPE_SIGNAL_ACL 104
+
+#define TMY_CHECK_READ_FOR_OPEN_EXEC 1
+
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip);
+const char *tmy_network2keyword(const unsigned int operation);
+
+/* to check "if task.parent.pid" condition. */
+extern asmlinkage long sys_getppid(void);
+
+#endif
Basic functions to get canonicalized absolute pathnames
for TOMOYO Linux. Even the requested pathname is symlink()ed
or chroot()ed, TOMOYO Linux uses the original pathname.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/realpath.c | 697 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 697 insertions(+)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/realpath.c 2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,697 @@
+/*
+ * security/tomoyo/realpath.c
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO Linux.
+ */
+
+#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/proc_fs.h>
+#include <linux/sysctl.h>
+#include "realpath.h"
+#include "tomoyo.h"
+
+/***** realpath handler *****/
+
+static int tmy_print_ascii(const char *sp, const char *cp,
+ int *buflen0, char **end0)
+{
+ int buflen = *buflen0;
+ char *end = *end0;
+
+ while (sp <= cp) {
+ unsigned char c;
+
+ 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--;
+ }
+
+ *buflen0 = buflen;
+ *end0 = end;
+
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * tmy_get_absolute_path - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @vfsmnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ * @buffer: size of buffer to save the result.
+ * @buflen: size of @buffer .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Caller holds the dcache_lock.
+ * Based on __d_path() in fs/dcache.c
+ *
+ * Unlike d_path(), this function traverses upto the root directory of
+ * process's namespace.
+ *
+ * If @dentry is a directory, trailing '/' is appended.
+ * Characters other than ' ' < c < 127 are converted to \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+static int tmy_get_absolute_path(struct dentry *dentry,
+ struct vfsmount *vfsmnt,
+ char *buffer,
+ int buflen)
+{
+ char *start = buffer;
+ char *end = buffer + buflen;
+ u8 is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode));
+ const char *sp;
+ const char *cp;
+
+ if (buflen < 256)
+ goto out;
+
+ *--end = '\0';
+ buflen--;
+
+ while (1) {
+ struct dentry *parent;
+
+ if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+ /* Global root? */
+ spin_lock(&vfsmount_lock);
+ if (vfsmnt->mnt_parent == vfsmnt) {
+ spin_unlock(&vfsmount_lock);
+ break;
+ }
+ dentry = vfsmnt->mnt_mountpoint;
+ vfsmnt = vfsmnt->mnt_parent;
+ spin_unlock(&vfsmount_lock);
+ continue;
+ }
+
+ if (is_dir) {
+ is_dir = 0;
+ *--end = '/';
+ buflen--;
+ }
+
+ parent = dentry->d_parent;
+ sp = dentry->d_name.name;
+ cp = sp + dentry->d_name.len - 1;
+
+ /* 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;
+ }
+
+ }
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ if (--buflen < 0)
+ goto out;
+ *--end = '/';
+
+ dentry = parent;
+ }
+ if (*end == '/') {
+ buflen++;
+ end++;
+ }
+
+ sp = dentry->d_name.name;
+ cp = sp + dentry->d_name.len - 1;
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ /* Move the pathname to the top of the buffer. */
+ memmove(start, end, strlen(end) + 1);
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * tmy_realpath_dentry2 - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ * @newname: buffer to save the result.
+ * @newname_len: size of @newname .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_realpath_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);
+ error = tmy_get_absolute_path(d_dentry, d_mnt, newname, newname_len);
+ spin_unlock(&dcache_lock);
+ /***** CRITICAL SECTION END *****/
+
+ dput(d_dentry);
+ mntput(d_mnt);
+ return error;
+}
+
+/**
+ * tmy_realpath_dentry - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ *
+ * Returns realpath(3) of the @dentry on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt)
+{
+ char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+ if (buf &&
+ tmy_realpath_dentry2(dentry, mnt, buf,
+ TMY_MAX_PATHNAME_LEN - 1) == 0)
+ return buf;
+
+ tmy_free(buf);
+ return NULL;
+}
+
+/**
+ * tmy_realpath - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath(const char *pathname)
+{
+ struct nameidata nd;
+
+ if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+ char *buf = tmy_realpath_dentry(nd.dentry, nd.mnt);
+
+ path_release(&nd);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/**
+ * tmy_realpath_nofollow - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * Unlike tmy_realpath(), this function doesn't follow if @pathname is
+ * a symbolic link.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_nofollow(const char *pathname)
+{
+ struct nameidata nd;
+
+ if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+ char *buf = tmy_realpath_dentry(nd.dentry, nd.mnt);
+
+ path_release(&nd);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/* tmy_get_absolute_path() for "struct ctl_table". */
+static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
+{
+ char *end = buffer + buflen;
+
+ if (buflen < 256)
+ goto out;
+
+ *--end = '\0';
+ buflen--;
+
+ buflen -= 9; /* for "/proc/sys" prefix */
+
+ while (table) {
+ char buf[32];
+ const char *sp = table->procname;
+ const char *cp;
+
+ if (!sp) {
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
+ sp = buf;
+ }
+ cp = strchr(sp, '\0') - 1;
+
+ if (tmy_print_ascii(sp, cp, &buflen, &end))
+ goto out;
+
+ if (--buflen < 0)
+ goto out;
+
+ *--end = '/';
+ table = table->parent;
+ }
+
+ /* Move the pathname to the top of the buffer. */
+ memmove(buffer, "/proc/sys", 9);
+ memmove(buffer + 9, end, strlen(end) + 1);
+ return 0;
+out: ;
+ return -ENOMEM;
+}
+
+/**
+ * sysctlpath_from_table - return the realpath of a ctl_table.
+ * @table: pointer to "struct ctl_table".
+ *
+ * Returns realpath(3) of the @table on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *sysctlpath_from_table(struct ctl_table *table)
+{
+ char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+ if (buf && tmy_sysctl_path(table, buf, TMY_MAX_PATHNAME_LEN - 1) == 0)
+ return buf;
+
+ tmy_free(buf);
+ return NULL;
+}
+
+/***** Private memory allocator. *****/
+
+/*
+ * Round up an integer so that the returned pointers are appropriately aligned.
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int tmy_roundup(const unsigned int size)
+{
+ if (sizeof(void *) >= sizeof(long))
+ return ((size + sizeof(void *) - 1) / sizeof(void *))
+ * sizeof(void *);
+
+ return ((size + sizeof(long) - 1) / sizeof(long)) * sizeof(long);
+}
+
+static unsigned int allocated_memory_for_elements;
+
+/**
+ * tmy_get_memory_used_for_elements - get memory usage for private data.
+ *
+ * Returns size of memory allocated for keeping individual ACL elements.
+ */
+unsigned int tmy_get_memory_used_for_elements(void)
+{
+ return allocated_memory_for_elements;
+}
+
+/**
+ * tmy_alloc_element - allocate memory for structures.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size)
+{
+ static DECLARE_MUTEX(lock);
+ static char *buf;
+ static unsigned int buf_used_len = PAGE_SIZE;
+ char *ptr = NULL;
+ const unsigned int word_aligned_size = tmy_roundup(size);
+
+ if (word_aligned_size > PAGE_SIZE)
+ return NULL;
+
+ down(&lock);
+
+ if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+ ptr = kmalloc(PAGE_SIZE, GFP_KERNEL);
+
+ if (!ptr) {
+ printk(KERN_INFO "ERROR: "
+ "Out of memory for tmy_alloc_element().\n");
+ if (!sbin_init_started)
+ panic("MAC Initialization failed.\n");
+ } else {
+ memset(ptr, 0, PAGE_SIZE);
+ buf = ptr;
+ allocated_memory_for_elements += PAGE_SIZE;
+ buf_used_len = word_aligned_size;
+ ptr = buf;
+ }
+
+ } else if (word_aligned_size) {
+ unsigned int i;
+
+ ptr = buf + buf_used_len;
+ buf_used_len += word_aligned_size;
+
+ for (i = 0; i < word_aligned_size; i++) {
+ if (ptr[i]) { /* This must not happen! */
+ printk(KERN_ERR
+ "WARNING: Reserved memory was tainted! "
+ "The system might go wrong.\n");
+ ptr[i] = '\0';
+ }
+ }
+
+ }
+
+ up(&lock);
+ return ptr;
+}
+
+/***** Shared memory allocator. *****/
+
+static unsigned int allocated_memory_for_savename;
+
+/**
+ * tmy_get_memory_used_for_save_name - get memory usage for shared data.
+ *
+ * Returns size of memory allocated for keeping string tokens.
+ */
+unsigned int tmy_get_memory_used_for_save_name(void)
+{
+ return allocated_memory_for_savename;
+}
+
+#define MAX_HASH 256
+
+/* List of tokens. */
+struct name_entry {
+ struct name_entry *next; /* NULL if none. */
+ struct path_info entry;
+};
+
+/* List of free memory. */
+struct free_memory_block_list {
+ struct free_memory_block_list *next; /* NULL if none. */
+ char *ptr; /* Pointer to a free area. */
+ int len; /* Length of the area. */
+};
+
+/**
+ * tmy_save_name - keep the given token on the RAM.
+ * @name: the string token to remember.
+ *
+ * Returns pointer to memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping any string data.
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name)
+{
+ static struct free_memory_block_list fmb_list = { NULL, NULL, 0 };
+ static struct name_entry name_list[MAX_HASH]; /* The list of names. */
+ static DECLARE_MUTEX(lock);
+ struct name_entry *ptr;
+ struct name_entry *prev = NULL;
+ unsigned int hash;
+ struct free_memory_block_list *fmb = &fmb_list;
+ int len;
+ static int first_call = 1;
+
+ if (!name)
+ return NULL;
+
+ len = strlen(name) + 1;
+ if (len > TMY_MAX_PATHNAME_LEN) {
+ printk(KERN_INFO "ERROR: Name too long for tmy_save_name().\n");
+ return NULL;
+ }
+
+ hash = full_name_hash((const unsigned char *) name, len - 1);
+
+ down(&lock);
+
+ if (first_call) {
+ int i;
+
+ first_call = 0;
+ memset(&name_list, 0, sizeof(name_list));
+
+ for (i = 0; i < MAX_HASH; i++) {
+ name_list[i].entry.name = "/";
+ tmy_fill_path_info(&name_list[i].entry);
+ }
+
+ if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
+ panic("Bad size.");
+ }
+
+ ptr = &name_list[hash % MAX_HASH];
+ while (ptr) {
+ if (hash == ptr->entry.hash &&
+ strcmp(name, ptr->entry.name) == 0)
+ goto out;
+ prev = ptr;
+ ptr = ptr->next;
+ }
+
+ while (len > fmb->len) {
+ char *cp;
+
+ if (fmb->next) {
+ fmb = fmb->next;
+ continue;
+ }
+
+ cp = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!cp)
+ goto out2;
+
+ fmb->next = tmy_alloc_element(sizeof(*fmb));
+ if (!fmb->next) {
+ kfree(cp);
+out2: ;
+ printk(KERN_INFO
+ "ERROR: Out of memory for tmy_save_name().\n");
+ if (!sbin_init_started)
+ panic("MAC Initialization failed.\n");
+ goto out; /* ptr == NULL */
+ }
+
+ memset(cp, 0, PAGE_SIZE);
+ allocated_memory_for_savename += PAGE_SIZE;
+ fmb = fmb->next;
+ fmb->ptr = cp;
+ fmb->len = PAGE_SIZE;
+
+ }
+
+ ptr = tmy_alloc_element(sizeof(*ptr));
+ if (!ptr)
+ goto out;
+
+ ptr->entry.name = fmb->ptr;
+ memmove(fmb->ptr, name, len);
+ tmy_fill_path_info(&ptr->entry);
+ fmb->ptr += len;
+ fmb->len -= len;
+
+ prev->next = ptr; /* prev != NULL because name_list is not empty. */
+ if (fmb->len == 0) {
+ struct free_memory_block_list *ptr = &fmb_list;
+ while (ptr->next != fmb)
+ ptr = ptr->next;
+ ptr->next = fmb->next;
+ }
+out: ;
+
+ up(&lock);
+
+ return ptr ? &ptr->entry : NULL;
+}
+
+/***** Dynamic memory allocator. *****/
+
+/* List of temporary memory blocks. */
+struct cache_entry {
+ struct list_head list;
+ void *ptr;
+ int size;
+};
+
+static struct kmem_cache *tmy_cachep;
+
+/**
+ * tmy_realpath_init - initialize memory allocator.
+ */
+void tmy_realpath_init(void)
+{
+ tmy_cachep = kmem_cache_create("tomoyo_cache",
+ sizeof(struct cache_entry),
+ 0, SLAB_PANIC, NULL);
+}
+
+static LIST_HEAD(cache_list);
+static DEFINE_SPINLOCK(cache_list_lock);
+static unsigned int dynamic_memory_size;
+
+/**
+ * tmy_get_memory_used_for_dynamic - get memory usage for temporary purpose.
+ *
+ * Returns size of memory allocated for keeping temporary purpose.
+ */
+unsigned int tmy_get_memory_used_for_dynamic(void)
+{
+ return dynamic_memory_size;
+}
+
+/**
+ * tmy_alloc - allocate memory for temporary purpose.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The caller has to call tmy_free() the returned pointer
+ * when memory is no longer needed.
+ */
+void *tmy_alloc(const size_t size)
+{
+ void *ret = kmalloc(size, GFP_KERNEL);
+ struct cache_entry *new_entry;
+
+ if (!ret) {
+ printk(KERN_INFO "ERROR: "
+ "kmalloc(): Out of memory for tmy_alloc().\n");
+ return ret;
+ }
+
+ new_entry = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+ if (!new_entry) {
+ printk(KERN_INFO "ERROR: "
+ "kmem_cache_alloc(): Out of memory for tmy_alloc().\n");
+ kfree(ret);
+ ret = NULL;
+ } else {
+ INIT_LIST_HEAD(&new_entry->list);
+ new_entry->ptr = ret;
+ new_entry->size = size;
+
+ /***** CRITICAL SECTION START *****/
+ spin_lock(&cache_list_lock);
+ list_add_tail(&new_entry->list, &cache_list);
+ dynamic_memory_size += size;
+ spin_unlock(&cache_list_lock);
+ /***** CRITICAL SECTION END *****/
+
+ memset(ret, 0, size);
+ }
+
+ return ret;
+}
+
+/**
+ * tmy_free - free memory allocated by tmy_alloc().
+ * @p: pointer to memory block allocated by tmy_alloc().
+ *
+ * If caller calls this function for multiple times (i.e. double-free() bug) or
+ * calls this function with invalid pointer, warning message will appear.
+ * If caller forgets to call this function,
+ * tmy_get_memory_used_for_dynamic() will indicate memory leaks.
+ */
+void tmy_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_prev(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(tmy_cachep, entry);
+ } else
+ printk(KERN_INFO "BUG: tmy_free() with invalid pointer.\n");
+
+}
On Tue, Oct 02, 2007 at 05:05:44PM +0900, Paul Mundt wrote:
> On Tue, Oct 02, 2007 at 04:31:48PM +0900, Kentaro Takeda wrote:
> > Common functions for TOMOYO Linux.
> >
> > TOMOYO Linux uses /sys/kernel/security/tomoyo interface for configuration.
> >
> This seems like a bit of an abuse of sysfs.
This is not sysfs, it is securityfs, big difference.
> Isn't this precisely what configfs is for?
Possibly, but you can do the same thing in securityfs if needed.
thanks,
greg k-h
On Tue, 2 Oct 2007, Kentaro Takeda wrote:
> --- linux-2.6.orig/net/core/datagram.c 2007-10-02 11:11:51.000000000 +0900
> +++ linux-2.6/net/core/datagram.c 2007-10-02 11:26:23.000000000 +0900
> @@ -55,6 +55,7 @@
> #include <net/checksum.h>
> #include <net/sock.h>
> #include <net/tcp_states.h>
> +#include <linux/security.h>
>
> /*
> * Is a socket 'connection oriented' ?
> @@ -178,6 +179,27 @@ struct sk_buff *skb_recv_datagram(struct
> } else
> skb = skb_dequeue(&sk->sk_receive_queue);
>
> + error = security_post_recv_datagram(sk, skb, flags);
> + if (error) {
> + unsigned long cpu_flags;
> +
> + if (!(flags & MSG_PEEK))
> + goto no_peek;
> +
> + 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);
> +no_peek:
> + skb_free_datagram(sk, skb);
> + goto no_packet;
> + }
> +
> if (skb)
> return skb;
I'm guessing you need this to determine the receiving process, rather than
the socket (which is available via security_sock_rcv_skb()).
If so, is this to interactively determine from the user or admin whether
the packet should be accepted/denied for the receiving process?
- James
--
James Morris
<[email protected]>
James Morris <[email protected]> writes:
>
> All linked lists (at least, new ones) must use the standard kernel list
> API.
Really? Why? That rule is new to me.
Would also seem very limiting especially since the standard kernel lists
do not allow to express important patterns (like single linked lists)
I don't think that is a requirement.
-Andi
Hello.
Thank you for your comment.
James Morris wrote:
> I'm guessing you need this to determine the receiving process, rather than
> the socket (which is available via security_sock_rcv_skb()).
Use of security_sock_rcv_skb() was discussed at http://lkml.org/lkml/2007/8/28/74 ,
and answer is at http://lkml.org/lkml/2007/10/2/56 .
> If so, is this to interactively determine from the user or admin whether
> the packet should be accepted/denied for the receiving process?
Yes, it is to determine whether the packet should be accepted/denied
based on the receiving process (like what anti-virus software's firewall does).
Regards.
On Tue, 2 Oct 2007, Kentaro Takeda wrote:
> +/**
> + * tmy_alloc - allocate memory for temporary purpose.
> + * @size: requested size in bytes.
> + *
> + * Returns '\0'-initialized memory region on success.
> + * Returns NULL on failure.
> + *
> + * This function allocates memory for keeping ACL entries.
> + * The caller has to call tmy_free() the returned pointer
> + * when memory is no longer needed.
> + */
Would you please explain why you need another level of memory allocation?
What does it do apart from let you check for memory leaks?
- James
--
James Morris
<[email protected]>
Hello.
James Morris wrote:
> Would you please explain why you need another level of memory allocation?
>
> What does it do apart from let you check for memory leaks?
Difference between tmy_alloc() and kmalloc() are
tmy_alloc() allows administrator know "how much memory is used by TOMOYO Linux modules"
via /sys/kernel/security/tomoyo/meminfo interface.
This feature was requested by TOMOYO Linux users.
/proc/slabinfo can show how much memory is used by all modules,
but it cannot show how much memory is used by TOMOYO Linux modules.
tmy_alloc() can indicate memory-leaking bug and can avoid double-kfree() bug
by keeping the pointer returned by kmalloc() in a local "cache_list" list.
tmy_alloc() also keeps the size of memory allocated by kmalloc() in "cache_list" list
so that administrator can know "how much memory is used by TOMOYO Linux modules".
Calling ksize() after kmalloc() in tmy_alloc() and
calling ksize() before kfree() in tmy_free() might be better
if double-kfree-checks and memory-leaking-checks (i.e. "tmy_cachep") are unneeded.
Regards.
Hello.
YOSHIFUJI Hideaki wrote:
> Introducing your own list is not good.
> Please use hlist or introduce new "slist".
James Morris wrote:
> You're introducing a custom API, which is open-coded repeatedly throughout
> your module.
>
> All linked lists (at least, new ones) must use the standard kernel list
> API.
It seems that standard kernel list API does not have singly-linked list manipulation.
I'm considering the following list manipulation API.
--- Start of code ---
#include <stdio.h>
#include <stdlib.h>
static inline void prefetch(const void *x) {;}
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
/*
* nphlist -- hlist with no pointer to previous
*/
struct nphlist_node {
struct nphlist_node *next;
};
struct nphlist_head {
struct nphlist_node *first;
};
#define NPHLIST_HEAD_INIT { .first = NULL }
#define NPHLIST_HEAD(name) struct nphlist_head name = NPHLIST_HEAD_INIT
#define INIT_NPHLIST_HEAD(ptr) ((ptr)->first = NULL)
#define INIT_NPHLIST_NODE(ptr) ((ptr)->next = NULL)
static inline void nphlist_add(struct nphlist_node *entry, struct nphlist_head *list) {
struct nphlist_node *ptr = list->first;
if (ptr) {
while (ptr->next)
ptr = ptr->next;
ptr->next = entry;
} else {
list->first = entry;
}
}
#define nphlist_entry(ptr, type, member) container_of(ptr,type,member)
/**
* nphlist_for_each_entry - iterate over list of given type
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct nph_list to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the nphlist_node within the struct.
*/
#define nphlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head); \
pos && ({ prefetch(pos->next); 1;}) && \
({ tpos = nphlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
struct something {
struct nphlist_node next;
int v;
};
static NPHLIST_HEAD(data);
int main(int argc, char *argv[]) {
struct something *ptr;
struct nphlist_node *p;
int i;
for (i = 0; i < 10; i++) {
printf("add %d\n", i);
ptr = malloc(sizeof(*ptr));
INIT_NPHLIST_NODE(&(ptr->next));
ptr->v = 10 + i;
nphlist_add(&(ptr->next), &data);
}
printf("dump\n");
nphlist_for_each_entry(ptr, p, data.first, next) {
printf("%d\n", ptr->v);
}
return 0;
}
--- End of code ---
Where should I put this API, in include/linux/list.h or security/tomoyo/include/tomoyo.h ?
If I should put in include/linux/list.h , what name would be appropriate?
May I name it "slist" instead of "nphlist" ?
Regards.
In article <[email protected]> (at Wed, 3 Oct 2007 20:24:52 +0900), Tetsuo Handa <[email protected]> says:
> It seems that standard kernel list API does not have singly-linked list manipulation.
> I'm considering the following list manipulation API.
Tstsuo, please name it "slist", which is well-known.
--yoshfuji
On Wed, 3 Oct 2007, YOSHIFUJI Hideaki / ??ƣ???? wrote:
> In article <[email protected]> (at Wed, 3 Oct 2007 20:24:52 +0900), Tetsuo Handa <[email protected]> says:
>
> > It seems that standard kernel list API does not have singly-linked list manipulation.
> > I'm considering the following list manipulation API.
>
> Tstsuo, please name it "slist", which is well-known.
I'm pretty sure that the singly linked list idea has been rejected a few
times. Just use the existing API.
--
James Morris
<[email protected]>
James Morris wrote:
> I'm pretty sure that the singly linked list idea has been rejected a few
> times. Just use the existing API.
Too bad...
Well, is there a way to avoid read_lock when reading list?
Currently, TOMOYO Linux avoids read_lock, on the assumption that
(1) First, ptr->next is initialized with NULL.
(2) Later, ptr->next is assigned non-NULL address.
(3) Assigning to ptr->next is done atomically.
Regards.
In article <[email protected]> (at Wed, 3 Oct 2007 22:04:11 +0900), Tetsuo Handa <[email protected]> says:
> Well, is there a way to avoid read_lock when reading list?
>
> Currently, TOMOYO Linux avoids read_lock, on the assumption that
> (1) First, ptr->next is initialized with NULL.
> (2) Later, ptr->next is assigned non-NULL address.
> (3) Assigning to ptr->next is done atomically.
RCU?
--yoshfuji
On Wed, 2007-10-03 at 22:04 +0900, Tetsuo Handa wrote:
> James Morris wrote:
> > I'm pretty sure that the singly linked list idea has been rejected a few
> > times. Just use the existing API.
> Too bad...
>
> Well, is there a way to avoid read_lock when reading list?
>
> Currently, TOMOYO Linux avoids read_lock, on the assumption that
> (1) First, ptr->next is initialized with NULL.
> (2) Later, ptr->next is assigned non-NULL address.
> (3) Assigning to ptr->next is done atomically.
(4) wmb after asigning ptr->next
(5) rmb before reading ptr->next
But please do look at the various rcu list primitives. RCU allows one to
avoid non-exclusive locks in many cases.
non-exclusive locks are evil, they must all die...
Tetsuo Handa wrote:
> James Morris wrote:
>> I'm pretty sure that the singly linked list idea has been rejected a few
>> times. Just use the existing API.
> Too bad...
>
> Well, is there a way to avoid read_lock when reading list?
>
> Currently, TOMOYO Linux avoids read_lock, on the assumption that
> (1) First, ptr->next is initialized with NULL.
> (2) Later, ptr->next is assigned non-NULL address.
> (3) Assigning to ptr->next is done atomically.
>
> Regards.
Is it all of the purpose for the list structure?
If so, you can apply RCU instead to avoid read lock
when scanning the list, like:
rcu_read_lock();
list_for_each_entry(...) {
....
}
rcu_read_unlock();
--
KaiGai Kohei <[email protected]>
Hello.
KaiGai Kohei wrote:
> If so, you can apply RCU instead to avoid read lock
> when scanning the list, like:
>
> rcu_read_lock();
> list_for_each_entry(...) {
> ....
> }
> rcu_read_unlock();
Can I sleep between rcu_read_lock() and rcu_read_unlock() ?
As far as I saw, rcu_read_lock() makes in_atomic() true, so I think I can't sleep.
If I use RCU, I have to give up " [TOMOYO 13/15] Conditional permission support"
because tmy_check_condition() can sleep.
Regards.
On Wed, 2007-10-03 at 22:59 +0900, Tetsuo Handa wrote:
> Hello.
>
> KaiGai Kohei wrote:
> > If so, you can apply RCU instead to avoid read lock
> > when scanning the list, like:
> >
> > rcu_read_lock();
> > list_for_each_entry(...) {
> > ....
> > }
> > rcu_read_unlock();
>
> Can I sleep between rcu_read_lock() and rcu_read_unlock() ?
> As far as I saw, rcu_read_lock() makes in_atomic() true, so I think I can't sleep.
>
> If I use RCU, I have to give up " [TOMOYO 13/15] Conditional permission support"
> because tmy_check_condition() can sleep.
You can indeed not sleep in an rcu_read_lock() section. However, you
could grab a reference on an item, stop the iteration, drop
rcu_read_lock. Do you thing, re-acquire rcu_read_lock(), drop the ref,
and continue the iteration.
Also, how do you avoid referencing dead data with your sll? I haven't
actually looked at your patches, but the simple scheme you outlined
didn't handle the iteration + concurrent removal scenario:
thread 1 thread 2
foo = ptr->next
< schedule >
killme = ptr->next (same item)
ptr->next = ptr->next->next;
kfree(killme)
< schedule >
foo->bar <-- *BANG*
Hello.
Thank you for pointing out.
Peter Zijlstra wrote:
> > Currently, TOMOYO Linux avoids read_lock, on the assumption that
> > (1) First, ptr->next is initialized with NULL.
> > (2) Later, ptr->next is assigned non-NULL address.
> > (3) Assigning to ptr->next is done atomically.
> (4) wmb after asigning ptr->next
> (5) rmb before reading ptr->next
Excuse me, but I didn't understand why (4) and (5) are needed.
append_function() {
down(semaphore_for_write_protect);
...
ptr = head;
while (ptr->next) ptr = ptr->next;
ptr->next = new_entry;
...
up(semaphore_for_write_protect);
}
read_function() {
for (ptr = head; ptr; ptr = ptr->next) {
...
}
}
Are (4) and (5) needed even when (3) is exclusively protected by down() and up() ?
Regards.
Peter Zijlstra wrote:
> Also, how do you avoid referencing dead data with your sll? I haven't
> actually looked at your patches, but the simple scheme you outlined
> didn't handle the iteration + concurrent removal scenario:
Regarding my singly-linked list, no entries are removed from the list. It's append only (like CD-R media).
I set is_deleted flag of a entry instead of removing the entry from the list.
Regards.
In article <[email protected]> (at Wed, 3 Oct 2007 23:26:57 +0900), Tetsuo Handa <[email protected]> says:
> Peter Zijlstra wrote:
> > Also, how do you avoid referencing dead data with your sll? I haven't
> > actually looked at your patches, but the simple scheme you outlined
> > didn't handle the iteration + concurrent removal scenario:
> Regarding my singly-linked list, no entries are removed from the list. It's append only (like CD-R media).
> I set is_deleted flag of a entry instead of removing the entry from the list.
It is not a good practice. Please free such objects.
BTW, how many objects do you have in the list?
--yoshfuji
On Wed, 2007-10-03 at 23:26 +0900, Tetsuo Handa wrote:
> Peter Zijlstra wrote:
> > Also, how do you avoid referencing dead data with your sll? I haven't
> > actually looked at your patches, but the simple scheme you outlined
> > didn't handle the iteration + concurrent removal scenario:
> Regarding my singly-linked list, no entries are removed from the list. It's append only (like CD-R media).
> I set is_deleted flag of a entry instead of removing the entry from the list.
sounds like a might fine memory leak / dos attack.
On Wed, 2007-10-03 at 23:19 +0900, Tetsuo Handa wrote:
> Hello.
>
> Thank you for pointing out.
>
> Peter Zijlstra wrote:
> > > Currently, TOMOYO Linux avoids read_lock, on the assumption that
> > > (1) First, ptr->next is initialized with NULL.
> > > (2) Later, ptr->next is assigned non-NULL address.
> > > (3) Assigning to ptr->next is done atomically.
> > (4) wmb after asigning ptr->next
> > (5) rmb before reading ptr->next
> Excuse me, but I didn't understand why (4) and (5) are needed.
>
> append_function() {
>
> down(semaphore_for_write_protect);
> ...
> ptr = head;
> while (ptr->next) ptr = ptr->next;
> ptr->next = new_entry;
> ...
> up(semaphore_for_write_protect);
>
> }
If at all possible, use struct mutex.
> read_function() {
>
> for (ptr = head; ptr; ptr = ptr->next) {
> ...
> }
>
> }
>
> Are (4) and (5) needed even when (3) is exclusively protected by down() and up() ?
the up() would do 4. 5 ensures another cpu will actually see it. Althoug
in practise the various cache invalidations driven by the workload will
ensure it will become visible eventually anyway.
On Wed, 3 Oct 2007, Tetsuo Handa wrote:
> Regarding my singly-linked list, no entries are removed from the list.
> It's append only (like CD-R media). I set is_deleted flag of a entry
> instead of removing the entry from the list.
Why so?
This smells like a horrible leaking of memory. How fast can the list grow
during the lifetime of the system?
--
Jiri Kosina
On Wed, 3 Oct 2007, YOSHIFUJI Hideaki / ??ƣ???? wrote:
> In article <[email protected]> (at Wed, 3 Oct 2007 23:26:57 +0900), Tetsuo Handa <[email protected]> says:
>
> > Peter Zijlstra wrote:
> > > Also, how do you avoid referencing dead data with your sll? I haven't
> > > actually looked at your patches, but the simple scheme you outlined
> > > didn't handle the iteration + concurrent removal scenario:
> > Regarding my singly-linked list, no entries are removed from the list. It's append only (like CD-R media).
> > I set is_deleted flag of a entry instead of removing the entry from the list.
>
> It is not a good practice. Please free such objects.
> BTW, how many objects do you have in the list?
Doesn't matter. No list should be able to grow without bounds in the
kernel.
--
James Morris
<[email protected]>
On Wed, 2007-10-03 at 23:19 +0900, Tetsuo Handa wrote:
> Hello.
>
> Thank you for pointing out.
>
> Peter Zijlstra wrote:
> > > Currently, TOMOYO Linux avoids read_lock, on the assumption that
> > > (1) First, ptr->next is initialized with NULL.
> > > (2) Later, ptr->next is assigned non-NULL address.
> > > (3) Assigning to ptr->next is done atomically.
> > (4) wmb after asigning ptr->next
> > (5) rmb before reading ptr->next
> Excuse me, but I didn't understand why (4) and (5) are needed.
>
> append_function() {
>
> down(semaphore_for_write_protect);
> ...
> ptr = head;
> while (ptr->next) ptr = ptr->next;
> ptr->next = new_entry;
> ...
> up(semaphore_for_write_protect);
>
> }
It seems to me that this function alone is a reason to argue against
using a singly linked list. I know your patch doesn't actually contain
this as a function but it does use the same logic to append to your
lists. Does the overhead of the second pointer that the regular list
head uses outweigh the O(1) insertion and deletion it provides
(especially in your case)? I know domain transitions are rare but why
use something with O(N) insertion? This could be one reason why there
isn't an slist already in list.h
Dave Quigley
Hello.
YOSHIFUJI Hideaki wrote:
> It is not a good practice. Please free such objects.
> BTW, how many objects do you have in the list?
It varies from 0 to some thousands,
depending on the policy supplied by the administrator and/or the policy appended by "learning mode".
Peter Zijlstra wrote:
> sounds like a might fine memory leak / dos attack.
TOMOYO Linux keeps the policy in CD-R's manner.
Thus, once an entry is written, it's pointer is valid forever.
TOMOYO Linux's simplicity (singly-linked list with no read_lock) comes from
this "keep the policy in CD-R's manner".
Yes, it is a kind of memory leak, but is controllable.
The kernel no longer requires memory after entering into "enforcing mode".
So, attackers can't do DoS attack after entering into "enforcing mode".
Regards.
About use of singly-linked list:
What my SLL (singly-linked list) holds is bit different from other lists.
Almost all lists hold list of elements (e.g. buffer) that are used *temporarily*.
Thus, adding to the list and removing from the list are essential.
My SLL holds ACL (access control list) entries that are used *permanently*
(i.e. throughout the kernel's lifetime).
These ACL entries are policy used for MAC (mandatory access control).
You don't change MAC's policy without clear reason, do you?
Therefore, ACL entries of MAC's policy seldom need to be removed.
So I wonder
"Remodeling the mechanism of holding ACL entries to support removal of individual entry
worth the cost of reference-counter manipulation and the risk of dead-pointers?"
Your next question would be
"Why are you using SLL for holding elements that are used *permanently*?"
"Why not allocate a large memory block and hold all elements in that block?"
Yes, you are right. But I can't do so.
The reason is explained in "policy file handling" at http://lkml.org/lkml/2007/10/2/56 .
About use of list that can't remove elements:
I think that many of you are misunderstanding about
"When entries are automatically appended to a list".
If you run the system in "learning mode" *forever*,
it will consume all memory; so DoS attacks are possible.
But please be aware that entries are automatically appended
only while you are running the system in "learning mode".
Also, there is a safeguard mechanism that controls upper limit.
These lists consume less than some hundreds KB
for embedded systems and/or targeted protection of PC systems,
less than 1 MB for complete protection of PC systems.
You can see how much memory is used for holding ACL entries
via /sys/kernel/security/tomoyo/meminfo interface
and you will find that these lists won't consume all memory in your system.
When you are running the system in "enforcing mode",
no entries are appended automatically; so DoS attacks are impossible.
Hello.
Peter Zijlstra wrote:
> > Can I sleep between rcu_read_lock() and rcu_read_unlock() ?
> > As far as I saw, rcu_read_lock() makes in_atomic() true, so I think I can't sleep.
> You can indeed not sleep in an rcu_read_lock() section. However, you
> could grab a reference on an item, stop the iteration, drop
> rcu_read_lock. Do you thing, re-acquire rcu_read_lock(), drop the ref,
> and continue the iteration.
Something like this?
rcu_read_lock();
list_for_each_rcu(p, ...) {
ptr = list_entry(p, struct ..., list);
/* Grab a reference to "ptr". */
rcu_read_unlock();
my_task_that_may_sleep(ptr);
rcu_read_lock();
/* Drop a reference to "ptr". */
}
rcu_read_unlock();
Regarding my case, memory region pointed by "ptr" never be removed.
Do I need to grab a reference to "ptr" ?
Regards.
Hi
>Something like this?
>
>rcu_read_lock();
>list_for_each_rcu(p, ...) {
> ptr = list_entry(p, struct ..., list);
> /* Grab a reference to "ptr". */
> rcu_read_unlock();
> my_task_that_may_sleep(ptr);
> rcu_read_lock();
> /* Drop a reference to "ptr". */
>}
>rcu_read_unlock();
>Regarding my case, memory region pointed by "ptr" never be removed.
>Do I need to grab a reference to "ptr" ?
In Document/RCU/whatisRCU.txt
Note that the value returned by rcu_dereference() is valid
only within the enclosing RCU read-side critical section.
For example, the following is -not- legal:
rcu_read_lock();
p = rcu_dereference(head.next);
rcu_read_unlock();
x = p->address;
rcu_read_lock();
y = p->data;
rcu_read_unlock();
Holding a reference from one RCU read-side critical section
to another is just as illegal as holding a reference from
one lock-based critical section to another! Similarly,
using a reference outside of the critical section in which
it was acquired is just as illegal as doing so with normal
locking.
Jun Wang
Hello.
Thank you for pointing out.
Jun WANG wrote:
> >rcu_read_lock();
> >list_for_each_rcu(p, ...) {
> > ptr = list_entry(p, struct ..., list);
> > /* Grab a reference to "ptr". */
> > rcu_read_unlock();
> > my_task_that_may_sleep(ptr);
> > rcu_read_lock();
> > /* Drop a reference to "ptr". */
> > }
> > rcu_read_unlock();
>
> In Document/RCU/whatisRCU.txt
> Note that the value returned by rcu_dereference() is valid
> only within the enclosing RCU read-side critical section.
Excuse me, but I think "p" is used only between rcu_read_lock() and rcu_read_unlock().
Is it illegal to use "ptr" after rcu_read_unlock() if "ptr" is obtained before rcu_read_unlock() ?
Regards.
> Hello.
>
> Thank you for pointing out.
>
> Jun WANG wrote:
> > >rcu_read_lock();
> > >list_for_each_rcu(p, ...) {
> > > ptr = list_entry(p, struct ..., list);
> > > /* Grab a reference to "ptr". */
> > > rcu_read_unlock();
> > > my_task_that_may_sleep(ptr);
> > > rcu_read_lock();
> > > /* Drop a reference to "ptr". */
> > > }
> > > rcu_read_unlock();
>>>Regarding my case, memory region pointed by "ptr" never be removed.
>>>Do I need to grab a reference to "ptr" ?
> >
> > In Document/RCU/whatisRCU.txt
> > Note that the value returned by rcu_dereference() is valid
> > only within the enclosing RCU read-side critical section.
> Excuse me, but I think "p" is used only between rcu_read_lock() and rcu_read_unlock().
> Is it illegal to use "ptr" after rcu_read_unlock() if "ptr" is obtained before rcu_read_unlock() ?
>
> Regards.
>
I'm sorry,I think I got your idea, if you do not need ptr in
my_task_that_may_sleep(), why you need to grab a reference to "ptr". If
your my_task_that_may_sleep() needs ptr, and according to the
>"memory region pointed by "ptr" never be removed." you say,
it is ok to use "ptr" after rcu_read_ulock(). The basic idea behind RCU
is to split updates into "removal" and "reclamation" phases. If you
memory region pointed by "ptr" will not "reclamation" in sleep, it is ok
Regards.
Hello.
Jun WANG wrote:
> I'm sorry,I think I got your idea, if you do not need ptr in
> my_task_that_may_sleep(), why you need to grab a reference to "ptr". If
> your my_task_that_may_sleep() needs ptr, and according to the
> "memory region pointed by "ptr" never be removed." you say,
> it is ok to use "ptr" after rcu_read_ulock(). The basic idea behind RCU
> is to split updates into "removal" and "reclamation" phases. If you
> memory region pointed by "ptr" will not "reclamation" in sleep, it is ok
I need "ptr" in my_task_that_may_sleep(), but regarding my case,
memory region pointed by "ptr" will never be kfree()ed.
So, I don't need to grab a reference to "ptr"
because memory region pointed by "ptr" will never be kfree()ed.
And it is legal to use "ptr" after rcu_read_unlock()
because memory region pointed by "ptr" will never be kfree()ed.
Thank you.
On Mon, 2007-10-08 at 01:56 +0900, Tetsuo Handa wrote:
> So, I don't need to grab a reference to "ptr"
> because memory region pointed by "ptr" will never be kfree()ed.
> And it is legal to use "ptr" after rcu_read_unlock()
> because memory region pointed by "ptr" will never be kfree()ed.
Still think that is a very BAD (tm) idea. Esp since with the help of RCU
there is hardly any performance loss of not doing proper deletes.
Hello.
Peter Zijlstra wrote:
> > So, I don't need to grab a reference to "ptr"
> > because memory region pointed by "ptr" will never be kfree()ed.
> > And it is legal to use "ptr" after rcu_read_unlock()
> > because memory region pointed by "ptr" will never be kfree()ed.
>
> Still think that is a very BAD (tm) idea. Esp since with the help of RCU
> there is hardly any performance loss of not doing proper deletes.
>
Elements I keep in RCU list are policy table for access control
that are referred throughout the lifetime of the kernel.
So, there is hardly any needs to delete these elements.
Honestly speaking, I don't need to use RCU list at all.
Just using a singly linked list that doesn't support deletion is enough.
But James Morris advises me to use the existing API
( http://lkml.org/lkml/2007/10/2/156 http://lkml.org/lkml/2007/10/3/124 ).
Thank you.
Hello.
Peter Zijlstra wrote:
> > append_function() {
> >
> > down(semaphore_for_write_protect);
> > ...
> > ptr = head;
> > while (ptr->next) ptr = ptr->next;
> > ptr->next = new_entry;
> > ...
> > up(semaphore_for_write_protect);
> >
> > }
>
> If at all possible, use struct mutex.
Fixed in the fourth submission ( http://lkml.org/lkml/2007/10/11/140 ).
Thank you.
Hello.
James Morris wrote:
> > > It seems that standard kernel list API does not have singly-linked list manipulation.
> > > I'm considering the following list manipulation API.
>
> I'm pretty sure that the singly linked list idea has been rejected a few
> times. Just use the existing API.
>
OK. I posted new one that uses existing API at http://lkml.org/lkml/2007/10/11/140 .
By the way, what do you think of new primitives shown below?
I'd like to use list_for_each_cookie() and list_add_tail_mb() if acceptable.
----- Start of code -----
/********** Existing API **********/
#include <stdio.h>
#include <stdlib.h>
static inline void mb(void) {;}
static inline void prefetch(void *p) {;}
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
#define list_entry(ptr, type, member) container_of(ptr, type, member)
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &name, &name }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
/********** Proposed API **********/
/**
* 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 ((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 list_add_tail_mb(struct list_head *new,
struct list_head *head)
{
struct list_head *prev = head->prev;
struct list_head *next = head;
new->next = next;
new->prev = prev;
mb();
next->prev = new;
prev->next = new;
}
/********** Usage example **********/
struct something {
struct list_head list;
int v;
};
static LIST_HEAD(something_list);
int main(int argc, char *argv[]) {
struct something *ptr;
struct list_head *pos;
struct list_head *cookie = NULL;
int i;
for (i = 0; i < 10; i++) {
printf("add %d\n", i);
ptr = malloc(sizeof(*ptr));
ptr->v = 10 + i;
list_add_tail_mb(&(ptr->list), &something_list);
}
printf("dump\n");
i = 0;
do {
list_for_each_cookie(pos, cookie, &something_list) {
ptr = list_entry(pos, struct something, list);
if (++i % 4 == 0) { /* Interrupt such as buffer-full. */
printf("break\n");
break;
}
printf("%d\n", ptr->v);
}
} while (cookie);
return 0;
}
----- End of code -----
Regards.