lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Thu, 10 Jun 2010 21:09:38 +0900
From:	Tetsuo Handa <penguin-kernel@...ove.SAKURA.ne.jp>
To:	linux-security-module@...r.kernel.org
Cc:	linux-kernel@...r.kernel.org
Subject: [PATCH 2/4] TOMOYO: Add argv[]/envp[] condition support.

Command line parameters can change how the file is used. For example, executing
"/usr/sbin/sshd -o 'Banner /etc/shadow'" will make SSH server disclose the
content of /etc/shadow to unauthenticated SSH clients (because we have to allow
SSH server to read /etc/shadow in order to authenticate SSH clients).
Therefore, TOMOYO restricts argv[] upon execve() request in order to prevent
crackers from doing

  $ sudo chmod 644 /etc/shadow
  $ ssh-keygen -t rsa -f /tmp/key
  $ /usr/sbin/sshd -o 'Banner /etc/shadow' -o 'Port 8022' -o 'HostKey /tmp/key'
  $ ssh -p 8022 localhost

.

Environment variables can change how the system behaves. For example, executing
programs with LD_PRELOAD or LD_LIBRARY_PATH defined would alter the system's
behavior. Therefore, TOMOYO restricts envp[] upon execve() request.

This patch allows users to check argv[]/envp[] parameters passed to execve()
operation. For example,

  allow_execute /bin/true if exec.argc=1 exec.argv[1]="--help" exec.envp["HOME"]!=NULL

will allow execution of /bin/true only if the command line is
"/bin/true --help" and environment variable HOME is defined.

Signed-off-by: Tetsuo Handa <penguin-kernel@...ove.SAKURA.ne.jp>
---
 security/tomoyo/common.c    |  193 +++++++++++++++++
 security/tomoyo/common.h    |   50 ++++
 security/tomoyo/condition.c |  481 +++++++++++++++++++++++++++++++++++++++++++-
 security/tomoyo/domain.c    |  114 ++++++++--
 security/tomoyo/gc.c        |   17 +
 5 files changed, 819 insertions(+), 36 deletions(-)

--- security-testing-2.6.orig/security/tomoyo/common.c
+++ security-testing-2.6/security/tomoyo/common.c
@@ -18,6 +18,8 @@ static struct tomoyo_profile tomoyo_defa
 	.preference.enforcing_verbose = true,
 	.preference.learning_max_entry = 2048,
 	.preference.learning_verbose = false,
+	.preference.learning_exec_realpath = true,
+	.preference.learning_exec_argv0 = true,
 	.preference.permissive_verbose = true
 };
 
@@ -85,7 +87,7 @@ static const char *tomoyo_yesno(const un
  * Returns true on success, false otherwise.
  */
 static bool tomoyo_print_name_union(struct tomoyo_io_buffer *head,
-				    const struct tomoyo_name_union *ptr)
+				 const struct tomoyo_name_union *ptr)
 {
 	int pos = head->read_avail;
 	if (pos && head->read_buf[pos - 1] == ' ')
@@ -97,6 +99,23 @@ static bool tomoyo_print_name_union(stru
 }
 
 /**
+ * tomoyo_print_name_union_quoted - Print a tomoyo_name_union with double quotes.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr:  Pointer to "struct tomoyo_name_union".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_print_name_union_quoted(struct tomoyo_io_buffer *head,
+					const struct tomoyo_name_union *ptr)
+{
+	if (ptr->is_group)
+		return tomoyo_io_printf(head, "@%s",
+				     ptr->group->group_name->name);
+	return tomoyo_io_printf(head, "\"%s\"", ptr->filename->name);
+}
+
+/**
  * tomoyo_print_number_union_common - Print a tomoyo_number_union.
  *
  * @head:       Pointer to "struct tomoyo_io_buffer".
@@ -335,6 +354,14 @@ static int tomoyo_write_profile(struct t
 		if (cp2)
 			sscanf(cp2 + 10, "%u",
 			       &profile->preference.learning_max_entry);
+		if (strstr(cp, "exec.realpath=yes"))
+			profile->preference.learning_exec_realpath = true;
+		else if (strstr(cp, "exec.realpath=no"))
+			profile->preference.learning_exec_realpath = false;
+		if (strstr(cp, "exec.argv0=yes"))
+			profile->preference.learning_exec_argv0 = true;
+		else if (strstr(cp, "exec.argv0=no"))
+			profile->preference.learning_exec_argv0 = false;
 		return 0;
 	}
 	if (profile == &tomoyo_default_profile)
@@ -395,10 +422,14 @@ static int tomoyo_read_profile(struct to
 		goto body;
 	tomoyo_io_printf(head, "PROFILE_VERSION=%s\n", "20090903");
 	tomoyo_io_printf(head, "PREFERENCE::learning={ verbose=%s "
-			 "max_entry=%u }\n",
+			 "max_entry=%u exec.realpath=%s exec.argv0=%s }\n",
 			 tomoyo_yesno(tomoyo_default_profile.preference.
 				      learning_verbose),
-			 tomoyo_default_profile.preference.learning_max_entry);
+			 tomoyo_default_profile.preference.learning_max_entry,
+			 tomoyo_yesno(tomoyo_default_profile.preference.
+				      learning_exec_realpath),
+			 tomoyo_yesno(tomoyo_default_profile.preference.
+				      learning_exec_argv0));
 	tomoyo_io_printf(head, "PREFERENCE::permissive={ verbose=%s }\n",
 			 tomoyo_yesno(tomoyo_default_profile.preference.
 				      permissive_verbose));
@@ -441,10 +472,16 @@ static int tomoyo_read_profile(struct to
 		}
 		if (profile->learning != &tomoyo_default_profile.preference &&
 		    !tomoyo_io_printf(head, "%u-PREFERENCE::learning={ "
-				      "verbose=%s max_entry=%u }\n", index,
+				      "verbose=%s max_entry=%u "
+				      "exec.realpath=%s exec.argv0=%s "
+				      "}\n", index,
 				      tomoyo_yesno(profile->preference.
 						   learning_verbose),
-				      profile->preference.learning_max_entry))
+				      profile->preference.learning_max_entry,
+				      tomoyo_yesno(profile->preference.
+						   learning_exec_realpath),
+				      tomoyo_yesno(profile->preference.
+						   learning_exec_argv0)))
 			goto out;
 		if (profile->permissive != &tomoyo_default_profile.preference
 		    && !tomoyo_io_printf(head, "%u-PREFERENCE::permissive={ "
@@ -881,6 +918,9 @@ static bool tomoyo_print_condition(struc
 {
 	const struct tomoyo_condition_element *condp;
 	const struct tomoyo_number_union *numbers_p;
+	const struct tomoyo_name_union *names_p;
+	const struct tomoyo_argv_entry *argv;
+	const struct tomoyo_envp_entry *envp;
 	u16 condc;
 	u16 i;
 	char buffer[32];
@@ -889,6 +929,10 @@ static bool tomoyo_print_condition(struc
 	condc = cond->condc;
 	condp = (const struct tomoyo_condition_element *) (cond + 1);
 	numbers_p = (const struct tomoyo_number_union *) (condp + condc);
+	names_p = (const struct tomoyo_name_union *)
+		(numbers_p + cond->numbers_count);
+	argv = (const struct tomoyo_argv_entry *) (names_p + cond->names_count);
+	envp = (const struct tomoyo_envp_entry *) (argv + cond->argc);
 	memset(buffer, 0, sizeof(buffer));
 	if (condc && !tomoyo_io_printf(head, "%s", " if"))
 		goto out;
@@ -898,6 +942,28 @@ static bool tomoyo_print_condition(struc
 		const u8 right = condp->right;
 		condp++;
 		switch (left) {
+		case TOMOYO_ARGV_ENTRY:
+			if (!tomoyo_io_printf(head, " exec.argv[%u]%s\"%s\"",
+					   argv->index, argv->is_not ?
+					   "!=" : "=", argv->value->name))
+				goto out;
+			argv++;
+			continue;
+		case TOMOYO_ENVP_ENTRY:
+			if (!tomoyo_io_printf(head, " exec.envp[\"%s\"]%s",
+					   envp->name->name, envp->is_not ?
+					   "!=" : "="))
+				goto out;
+			if (envp->value) {
+				if (!tomoyo_io_printf(head, "\"%s\"",
+						   envp->value->name))
+					goto out;
+			} else {
+				if (!tomoyo_io_printf(head, "NULL"))
+					goto out;
+			}
+			envp++;
+			continue;
 		case TOMOYO_NUMBER_UNION:
 			if (!tomoyo_print_number_union(head, numbers_p++))
 				goto out;
@@ -913,6 +979,10 @@ static bool tomoyo_print_condition(struc
 		if (!tomoyo_io_printf(head, "%s", match ? "=" : "!="))
 			goto out;
 		switch (right) {
+		case TOMOYO_NAME_UNION:
+			if (!tomoyo_print_name_union_quoted(head, names_p++))
+				goto out;
+			break;
 		case TOMOYO_NUMBER_UNION:
 			if (!tomoyo_print_number_union_nospace(head,
 							       numbers_p++))
@@ -1532,6 +1602,110 @@ static char *tomoyo_init_audit_log(int *
 	return buf;
 }
 
+/**
+ * tomoyo_get_argv0 - Get argv[0].
+ *
+ * @ee: Pointer to "struct tomoyo_execve_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_get_argv0(struct tomoyo_execve_entry *ee)
+{
+	struct linux_binprm *bprm = ee->bprm;
+	char *arg_ptr = ee->tmp;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int offset = pos % PAGE_SIZE;
+	bool done = false;
+	if (!bprm->argc)
+		goto out;
+	while (1) {
+		if (!tomoyo_dump_page(bprm, pos, &ee->dump))
+			goto out;
+		pos += PAGE_SIZE - offset;
+		/* Read. */
+		while (offset < PAGE_SIZE) {
+			const char *kaddr = ee->dump.data;
+			const unsigned char c = kaddr[offset++];
+			if (c && arg_len < TOMOYO_EXEC_TMPSIZE - 10) {
+				if (c == '\\') {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = '\\';
+				} else if (c > ' ' && c < 127) {
+					arg_ptr[arg_len++] = c;
+				} else {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = (c >> 6) + '0';
+					arg_ptr[arg_len++]
+						= ((c >> 3) & 7) + '0';
+					arg_ptr[arg_len++] = (c & 7) + '0';
+				}
+			} else {
+				arg_ptr[arg_len] = '\0';
+				done = true;
+				break;
+			}
+		}
+		offset = 0;
+		if (done)
+			break;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * tomoyo_get_execute_condition - Get condition part for execute requests.
+ *
+ * @ee: Pointer to "struct tomoyo_execve_entry".
+ *
+ * Returns pointer to "struct tomoyo_condition" on success, NULL otherwise.
+ */
+static struct tomoyo_condition *tomoyo_get_execute_condition
+(struct tomoyo_execve_entry *ee)
+{
+	struct tomoyo_condition *cond;
+	char *buf;
+	int len = 256;
+	char *realpath = NULL;
+	char *argv0 = NULL;
+	const struct tomoyo_preference *pref = tomoyo_profile(ee->r.profile)
+		->learning;
+	if (pref->learning_exec_realpath) {
+		struct file *file = ee->bprm->file;
+		realpath = tomoyo_realpath_from_path(&file->f_path);
+		if (realpath)
+			len += strlen(realpath) + 17;
+	}
+	if (pref->learning_exec_argv0) {
+		if (tomoyo_get_argv0(ee)) {
+			argv0 = ee->tmp;
+			len += strlen(argv0) + 16;
+		}
+	}
+	buf = kmalloc(len, GFP_NOFS);
+	if (!buf) {
+		kfree(realpath);
+		return NULL;
+	}
+	snprintf(buf, len - 1, "if");
+	if (realpath) {
+		const int pos = strlen(buf);
+		snprintf(buf + pos, len - pos - 1, " exec.realpath=\"%s\"",
+			 realpath);
+		kfree(realpath);
+	}
+	if (argv0) {
+		const int pos = strlen(buf);
+		snprintf(buf + pos, len - pos - 1, " exec.argv[0]=\"%s\"",
+			 argv0);
+	}
+	cond = tomoyo_get_condition(buf);
+	kfree(buf);
+	return cond;
+}
+
 /* Wait queue for tomoyo_query_list. */
 static DECLARE_WAIT_QUEUE_HEAD(tomoyo_query_wait);
 
@@ -1580,6 +1754,7 @@ int tomoyo_supervisor(struct tomoyo_requ
 	char *header;
 	switch (r->mode) {
 		char *buffer;
+		struct tomoyo_condition *cond;
 	case TOMOYO_CONFIG_LEARNING:
 		if (!tomoyo_domain_quota_is_ok(r))
 			return 0;
@@ -1593,7 +1768,13 @@ int tomoyo_supervisor(struct tomoyo_requ
 		vsnprintf(buffer, len - 1, fmt, args);
 		va_end(args);
 		tomoyo_normalize_line(buffer);
-		tomoyo_write_domain_policy2(buffer, r->domain, NULL, false);
+		if (r->ee)
+			cond = tomoyo_get_execute_condition(r->ee);
+		else
+			cond = NULL;
+		tomoyo_write_domain_policy2(buffer, r->domain, cond, false);
+		tomoyo_put_condition(cond);
+		kfree(buffer);
 		/* fall through */
 	case TOMOYO_CONFIG_PERMISSIVE:
 		return 0;
--- security-testing-2.6.orig/security/tomoyo/common.h
+++ security-testing-2.6/security/tomoyo/common.h
@@ -193,20 +193,37 @@ enum tomoyo_conditions_index {
 	TOMOYO_TASK_FSGID,           /* current_fsgid() */
 	TOMOYO_TASK_PID,             /* sys_getpid()   */
 	TOMOYO_TASK_PPID,            /* sys_getppid()  */
+	TOMOYO_EXEC_ARGC,            /* "struct linux_binprm *"->argc */
+	TOMOYO_EXEC_ENVC,            /* "struct linux_binprm *"->envc */
+	TOMOYO_EXEC_REALPATH,
 	TOMOYO_MAX_CONDITION_KEYWORD,
 	TOMOYO_NUMBER_UNION,
+	TOMOYO_NAME_UNION,
+	TOMOYO_ARGV_ENTRY,
+	TOMOYO_ENVP_ENTRY
 };
 
 #define TOMOYO_RETRY_REQUEST 1 /* Retry this request. */
 
 /********** Structure definitions. **********/
 
+/* Structure for dumping argv[] and envp[] of "struct linux_binprm". */
+struct tomoyo_page_dump {
+	struct page *page;    /* Previously dumped page. */
+	char *data;           /* Contents of "page". Size is PAGE_SIZE. */
+};
+
 struct tomoyo_condition_element {
-	/* Left hand operand. */
+	/*
+	 * Left hand operand. A "struct tomoyo_argv" for TOMOYO_ARGV_ENTRY, a
+	 * "struct tomoyo_envp" for TOMOYO_ENVP_ENTRY is attached to the tail
+	 * of the array of this struct.
+	 */
 	u8 left;
 	/*
 	 * Right hand operand. A "struct tomoyo_number_union" for
-	 * TOMOYO_NUMBER_UNION is attached to the tail of the array of this
+	 * TOMOYO_NUMBER_UNION, a "struct tomoyo_name_union" for
+	 * TOMOYO_NAME_UNION is attached to the tail of the array of this
 	 * struct.
 	 */
 	u8 right;
@@ -221,9 +238,15 @@ struct tomoyo_condition {
 	u32 size;
 	u16 condc;
 	u16 numbers_count;
+	u16 names_count;
+	u16 argc;
+	u16 envc;
 	/*
 	 * struct tomoyo_condition_element condition[condc];
 	 * struct tomoyo_number_union values[numbers_count];
+	 * struct tomoyo_name_union names[names_count];
+	 * struct tomoyo_argv argv[argc];
+	 * struct tomoyo_envp envp[envc];
 	 */
 };
 
@@ -332,7 +355,10 @@ struct tomoyo_number_group_member {
 /* Structure for execve() operation. */
 struct tomoyo_execve_entry {
 	struct tomoyo_request_info r;
+	struct linux_binprm *bprm;
 	int reader_idx;
+	/* For dumping argv[] and envp[]. */
+	struct tomoyo_page_dump dump;
 	/* For temporary use. */
 	char *tmp; /* Size is TOMOYO_EXEC_TMPSIZE bytes */
 };
@@ -402,6 +428,20 @@ struct tomoyo_domain_info {
 	atomic_t users; /* Number of referring credentials. */
 };
 
+/* Structure for argv[]. */
+struct tomoyo_argv_entry {
+	unsigned int index;
+	const struct tomoyo_path_info *value;
+	bool is_not;
+};
+
+/* Structure for envp[]. */
+struct tomoyo_envp_entry {
+	const struct tomoyo_path_info *name;
+	const struct tomoyo_path_info *value;
+	bool is_not;
+};
+
 /*
  * tomoyo_path_acl is a structure which is used for holding an
  * entry with one pathname operation (e.g. open(), mkdir()).
@@ -734,6 +774,8 @@ struct tomoyo_preference {
 	unsigned int learning_max_entry;
 	bool enforcing_verbose;
 	bool learning_verbose;
+	bool learning_exec_realpath;
+	bool learning_exec_argv0;
 	bool permissive_verbose;
 };
 
@@ -754,6 +796,8 @@ extern asmlinkage long sys_getppid(void)
 
 bool tomoyo_condition(struct tomoyo_request_info *r,
 		      const struct tomoyo_acl_info *acl);
+bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos,
+		      struct tomoyo_page_dump *dump);
 u8 tomoyo_parse_ulong(unsigned long *result, char **str);
 struct tomoyo_condition *tomoyo_get_condition(char * const condition);
 void tomoyo_del_condition(struct tomoyo_condition *cond);
@@ -1197,6 +1241,8 @@ static inline bool tomoyo_is_same_condit
 {
 	return p1->size == p2->size && p1->condc == p2->condc &&
 		p1->numbers_count == p2->numbers_count &&
+		p1->names_count == p2->names_count &&
+		p1->argc == p2->argc && p1->envc == p2->envc &&
 		!memcmp(p1 + 1, p2 + 1, p1->size - sizeof(*p1));
 }
 
--- security-testing-2.6.orig/security/tomoyo/condition.c
+++ security-testing-2.6/security/tomoyo/condition.c
@@ -7,6 +7,364 @@
 #include "common.h"
 #include <linux/slab.h>
 
+/**
+ * tomoyo_argv - Check argv[] in "struct linux_binbrm".
+ *
+ * @index:   Index number of @arg_ptr.
+ * @arg_ptr: Contents of argv[@index].
+ * @argc:    Length of @argv.
+ * @argv:    Pointer to "struct tomoyo_argv_entry".
+ * @checked: Set to true if @argv[@index] was found.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_argv(const unsigned int index, const char *arg_ptr,
+			const int argc, const struct tomoyo_argv_entry *argv,
+			u8 *checked)
+{
+	int i;
+	struct tomoyo_path_info arg;
+	arg.name = arg_ptr;
+	for (i = 0; i < argc; argv++, checked++, i++) {
+		bool result;
+		if (index != argv->index)
+			continue;
+		*checked = 1;
+		tomoyo_fill_path_info(&arg);
+		result = tomoyo_path_matches_pattern(&arg, argv->value);
+		if (argv->is_not)
+			result = !result;
+		if (!result)
+			return false;
+	}
+	return true;
+}
+
+/**
+ * tomoyo_envp - Check envp[] in "struct linux_binbrm".
+ *
+ * @env_name:  The name of environment variable.
+ * @env_value: The value of environment variable.
+ * @envc:      Length of @envp.
+ * @envp:      Pointer to "struct tomoyo_envp_entry".
+ * @checked:   Set to true if @envp[@env_name] was found.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_envp(const char *env_name, const char *env_value,
+			const int envc, const struct tomoyo_envp_entry *envp,
+			u8 *checked)
+{
+	int i;
+	struct tomoyo_path_info name;
+	struct tomoyo_path_info value;
+	name.name = env_name;
+	tomoyo_fill_path_info(&name);
+	value.name = env_value;
+	tomoyo_fill_path_info(&value);
+	for (i = 0; i < envc; envp++, checked++, i++) {
+		bool result;
+		if (!tomoyo_path_matches_pattern(&name, envp->name))
+			continue;
+		*checked = 1;
+		if (envp->value) {
+			result = tomoyo_path_matches_pattern(&value,
+							     envp->value);
+			if (envp->is_not)
+				result = !result;
+		} else {
+			result = true;
+			if (!envp->is_not)
+				result = !result;
+		}
+		if (!result)
+			return false;
+	}
+	return true;
+}
+
+/**
+ * tomoyo_scan_bprm - Scan "struct linux_binprm".
+ *
+ * @ee:   Pointer to "struct tomoyo_execve_entry".
+ * @argc: Length of @argc.
+ * @argv: Pointer to "struct tomoyo_argv_entry".
+ * @envc: Length of @envp.
+ * @envp: Poiner to "struct tomoyo_envp_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_scan_bprm(struct tomoyo_execve_entry *ee, const u16 argc,
+			     const struct tomoyo_argv_entry *argv,
+			     const u16 envc,
+			     const struct tomoyo_envp_entry *envp)
+{
+	struct linux_binprm *bprm = ee->bprm;
+	struct tomoyo_page_dump *dump = &ee->dump;
+	char *arg_ptr = ee->tmp;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int offset = pos % PAGE_SIZE;
+	int argv_count = bprm->argc;
+	int envp_count = bprm->envc;
+	bool result = true;
+	u8 local_checked[32];
+	u8 *checked;
+	if (argc + envc <= sizeof(local_checked)) {
+		checked = local_checked;
+		memset(local_checked, 0, sizeof(local_checked));
+	} else {
+		checked = kzalloc(argc + envc, GFP_NOFS);
+		if (!checked)
+			return false;
+	}
+	while (argv_count || envp_count) {
+		if (!tomoyo_dump_page(bprm, pos, dump)) {
+			result = false;
+			goto out;
+		}
+		pos += PAGE_SIZE - offset;
+		while (offset < PAGE_SIZE) {
+			/* Read. */
+			struct tomoyo_path_info arg;
+			const char *kaddr = dump->data;
+			const unsigned char c = kaddr[offset++];
+			arg.name = arg_ptr;
+			if (c && arg_len < TOMOYO_EXEC_TMPSIZE - 10) {
+				if (c == '\\') {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = '\\';
+				} else if (c > ' ' && c < 127) {
+					arg_ptr[arg_len++] = c;
+				} else {
+					arg_ptr[arg_len++] = '\\';
+					arg_ptr[arg_len++] = (c >> 6) + '0';
+					arg_ptr[arg_len++] =
+						((c >> 3) & 7) + '0';
+					arg_ptr[arg_len++] = (c & 7) + '0';
+				}
+			} else {
+				arg_ptr[arg_len] = '\0';
+			}
+			if (c)
+				continue;
+			/* Check. */
+			if (argv_count) {
+				if (!tomoyo_argv(bprm->argc - argv_count,
+					      arg_ptr, argc, argv,
+					      checked)) {
+					result = false;
+					break;
+				}
+				argv_count--;
+			} else if (envp_count) {
+				char *cp = strchr(arg_ptr, '=');
+				if (cp) {
+					*cp = '\0';
+					if (!tomoyo_envp(arg_ptr, cp + 1,
+						      envc, envp,
+						      checked + argc)) {
+						result = false;
+						break;
+					}
+				}
+				envp_count--;
+			} else {
+				break;
+			}
+			arg_len = 0;
+		}
+		offset = 0;
+		if (!result)
+			break;
+	}
+ out:
+	if (result) {
+		int i;
+		/* Check not-yet-checked entries. */
+		for (i = 0; i < argc; i++) {
+			if (checked[i])
+				continue;
+			/*
+			 * Return true only if all unchecked indexes in
+			 * bprm->argv[] are not matched.
+			 */
+			if (argv[i].is_not)
+				continue;
+			result = false;
+			break;
+		}
+		for (i = 0; i < envc; envp++, i++) {
+			if (checked[argc + i])
+				continue;
+			/*
+			 * Return true only if all unchecked environ variables
+			 * in bprm->envp[] are either undefined or not matched.
+			 */
+			if ((!envp->value && !envp->is_not) ||
+			    (envp->value && envp->is_not))
+				continue;
+			result = false;
+			break;
+		}
+	}
+	if (checked != local_checked)
+		kfree(checked);
+	return result;
+}
+
+static bool tomoyo_scan_exec_realpath(struct file *file,
+				      const struct tomoyo_name_union *ptr,
+				      const bool match)
+{
+	bool result;
+	struct tomoyo_path_info exe;
+	if (!file)
+		return false;
+	exe.name = tomoyo_realpath_from_path(&file->f_path);
+	if (!exe.name)
+		return false;
+	tomoyo_fill_path_info(&exe);
+	result = tomoyo_compare_name_union(&exe, ptr);
+	kfree(exe.name);
+	return result == match;
+}
+
+static bool tomoyo_parse_name_union_quoted(char *filename,
+					   struct tomoyo_name_union *ptr)
+{
+	bool result;
+	char *cp = NULL;
+	if (*filename == '"') {
+		cp = filename + strlen(filename) - 1;
+		if (*cp != '"')
+			return false;
+		*cp = '\0';
+		filename++;
+	}
+	result = tomoyo_parse_name_union(filename, ptr);
+	if (cp)
+		*cp = '"';
+	return result;
+}
+
+/**
+ * tomoyo_get_dqword - tomoyo_get_name() for a quoted string.
+ *
+ * @start: String to save.
+ *
+ * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
+ */
+static const struct tomoyo_path_info *tomoyo_get_dqword(char *start)
+{
+	char *cp;
+	if (*start++ != '"')
+		return NULL;
+	cp = start;
+	while (1) {
+		const char c = *cp++;
+		if (!c)
+			return NULL;
+		if (c != '"' || *cp)
+			continue;
+		*(cp - 1) = '\0';
+		break;
+	}
+	if (*start && !tomoyo_is_correct_word(start))
+		return NULL;
+	return tomoyo_get_name(start);
+}
+
+/**
+ * tomoyo_parse_argv - Parse an argv[] condition part.
+ *
+ * @start: String to parse.
+ * @argv:  Pointer to "struct tomoyo_argv_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_parse_argv(char *start, struct tomoyo_argv_entry *argv)
+{
+	unsigned long index;
+	const struct tomoyo_path_info *value;
+	bool is_not;
+	char c;
+	if (tomoyo_parse_ulong(&index, &start) != TOMOYO_VALUE_TYPE_DECIMAL)
+		goto out;
+	if (*start++ != ']')
+		goto out;
+	c = *start++;
+	if (c == '=')
+		is_not = false;
+	else if (c == '!' && *start++ == '=')
+		is_not = true;
+	else
+		goto out;
+	value = tomoyo_get_dqword(start);
+	if (!value)
+		goto out;
+	argv->index = index;
+	argv->is_not = is_not;
+	argv->value = value;
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * tomoyo_parse_envp - Parse an envp[] condition part.
+ *
+ * @start: String to parse.
+ * @envp:  Pointer to "struct tomoyo_envp_entry".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_parse_envp(char *start, struct tomoyo_envp_entry *envp)
+{
+	const struct tomoyo_path_info *name;
+	const struct tomoyo_path_info *value;
+	bool is_not;
+	char *cp = start;
+	/*
+	 * Since environment variable names don't
+	 * contain '=', I can treat '"]=' and '"]!='
+	 * sequences as delimiters.
+	 */
+	while (1) {
+		if (!strncmp(start, "\"]=", 3)) {
+			is_not = false;
+			*start = '\0';
+			start += 3;
+			break;
+		} else if (!strncmp(start, "\"]!=", 4)) {
+			is_not = true;
+			*start = '\0';
+			start += 4;
+			break;
+		} else if (!*start++) {
+			goto out;
+		}
+	}
+	if (!tomoyo_is_correct_word(cp))
+		goto out;
+	name = tomoyo_get_name(cp);
+	if (!name)
+		goto out;
+	if (!strcmp(start, "NULL")) {
+		value = NULL;
+	} else {
+		value = tomoyo_get_dqword(start);
+		if (!value)
+			goto out;
+	}
+	envp->name = name;
+	envp->is_not = is_not;
+	envp->value = value;
+	return true;
+ out:
+	return false;
+}
+
 /* The list for "struct tomoyo_condition". */
 LIST_HEAD(tomoyo_condition_list);
 
@@ -21,6 +379,9 @@ const char *tomoyo_condition_keyword[TOM
 	[TOMOYO_TASK_FSGID]           = "task.fsgid",
 	[TOMOYO_TASK_PID]             = "task.pid",
 	[TOMOYO_TASK_PPID]            = "task.ppid",
+	[TOMOYO_EXEC_ARGC]            = "exec.argc",
+	[TOMOYO_EXEC_ENVC]            = "exec.envc",
+	[TOMOYO_EXEC_REALPATH]        = "exec.realpath",
 };
 
 /* #define DEBUG_CONDITION */
@@ -45,10 +406,16 @@ struct tomoyo_condition *tomoyo_get_cond
 	struct tomoyo_condition *ptr;
 	struct tomoyo_condition_element *condp;
 	struct tomoyo_number_union *numbers_p;
+	struct tomoyo_name_union *names_p;
+	struct tomoyo_argv_entry *argv;
+	struct tomoyo_envp_entry *envp;
 	u32 size;
 	bool found = false;
 	u16 condc = 0;
 	u16 numbers_count = 0;
+	u16 names_count = 0;
+	u16 argc = 0;
+	u16 envc = 0;
 	char *end_of_string;
 	start = condition;
 	if (!strncmp(start, "if ", 3))
@@ -73,6 +440,15 @@ struct tomoyo_condition *tomoyo_get_cond
 			start = "";
 		}
 		dprintk(KERN_WARNING "%u: <%s>\n", __LINE__, word);
+		if (!strncmp(word, "exec.argv[", 10)) {
+			argc++;
+			condc++;
+			continue;
+		} else if (!strncmp(word, "exec.envp[\"", 11)) {
+			envc++;
+			condc++;
+			continue;
+		}
 		eq = strchr(word, '=');
 		if (!eq)
 			goto out;
@@ -97,6 +473,10 @@ struct tomoyo_condition *tomoyo_get_cond
 		condc++;
 		dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__,
 			word, left);
+		if (left == TOMOYO_EXEC_REALPATH) {
+			names_count++;
+			continue;
+		}
 		for (right = 0; right < TOMOYO_MAX_CONDITION_KEYWORD; right++) {
 			if (strcmp(word, tomoyo_condition_keyword[right]))
 				continue;
@@ -107,19 +487,29 @@ struct tomoyo_condition *tomoyo_get_cond
 		if (right == TOMOYO_MAX_CONDITION_KEYWORD)
 			numbers_count++;
 	}
-	dprintk(KERN_DEBUG "%u: cond=%u numbers=%u\n", __LINE__,
-		condc, numbers_count);
+	dprintk(KERN_DEBUG "%u: cond=%u numbers=%u names=%u ac=%u "
+		"ec=%u\n", __LINE__, condc, numbers_count, names_count,
+		argc, envc);
 	size = sizeof(*entry)
 		+ condc * sizeof(struct tomoyo_condition_element)
-		+ numbers_count * sizeof(struct tomoyo_number_union);
+		+ numbers_count * sizeof(struct tomoyo_number_union)
+		+ names_count * sizeof(struct tomoyo_name_union)
+		+ argc * sizeof(struct tomoyo_argv_entry)
+		+ envc * sizeof(struct tomoyo_envp_entry);
 	entry = kzalloc(size, GFP_NOFS);
 	if (!entry)
 		return NULL;
 	INIT_LIST_HEAD(&entry->list);
 	entry->condc = condc;
 	entry->numbers_count = numbers_count;
+	entry->names_count = names_count;
+	entry->argc = argc;
+	entry->envc = envc;
 	condp = (struct tomoyo_condition_element *) (entry + 1);
 	numbers_p = (struct tomoyo_number_union *) (condp + condc);
+	names_p = (struct tomoyo_name_union *) (numbers_p + numbers_count);
+	argv = (struct tomoyo_argv_entry *) (names_p + names_count);
+	envp = (struct tomoyo_envp_entry *) (argv + argc);
 	for (start = condition; start < end_of_string; start++)
 		if (!*start)
 			*start = ' ';
@@ -145,6 +535,25 @@ struct tomoyo_condition *tomoyo_get_cond
 			start = "";
 		}
 		dprintk(KERN_WARNING "%u: <%s>\n", __LINE__, word);
+		if (!strncmp(word, "exec.argv[", 10)) {
+			if (!tomoyo_parse_argv(word + 10, argv))
+				goto out;
+			argv++;
+			argc--;
+			condc--;
+			left = TOMOYO_ARGV_ENTRY;
+			right = -1;
+			goto store_value;
+		} else if (!strncmp(word, "exec.envp[\"", 11)) {
+			if (!tomoyo_parse_envp(word + 11, envp))
+				goto out;
+			envp++;
+			envc--;
+			condc--;
+			left = TOMOYO_ENVP_ENTRY;
+			right = -1;
+			goto store_value;
+		}
 		eq = strchr(word, '=');
 		if (!eq) {
 			dprintk(KERN_WARNING "%u: No operator.\n",
@@ -179,6 +588,13 @@ struct tomoyo_condition *tomoyo_get_cond
 		condc--;
 		dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__,
 			word, left);
+		if (left == TOMOYO_EXEC_REALPATH) {
+			right = TOMOYO_NAME_UNION;
+			if (!tomoyo_parse_name_union_quoted(word, names_p++))
+				goto out;
+			names_count--;
+			goto store_value;
+		}
 		for (right = 0; right < TOMOYO_MAX_CONDITION_KEYWORD; right++) {
 			if (strcmp(word, tomoyo_condition_keyword[right]))
 				continue;
@@ -190,6 +606,7 @@ struct tomoyo_condition *tomoyo_get_cond
 				goto out;
 			numbers_count--;
 		}
+ store_value:
 		condp->left = left;
 		condp->right = right;
 		condp->equals = !is_not;
@@ -202,12 +619,16 @@ struct tomoyo_condition *tomoyo_get_cond
 	for (start = condition; start < end_of_string; start++)
 		if (!*start)
 			*start = ' ';
-	dprintk(KERN_DEBUG "%u: <%s> cond=%u numbers=%u\n",
-		__LINE__, condition, condc, numbers_count);
+	dprintk(KERN_DEBUG "%u: <%s> cond=%u numbers=%u names=%u ac=%u "
+		"ec=%u\n", __LINE__, condition, condc, numbers_count,
+		names_count, argc, envc);
+	BUG_ON(names_count);
 	BUG_ON(numbers_count);
+	BUG_ON(argc);
+	BUG_ON(envc);
 	BUG_ON(condc);
 #else
-	BUG_ON(numbers_count | condc);
+	BUG_ON(names_count | numbers_count | argc | envc | condc);
 #endif
 	entry->size = size;
 	if (mutex_lock_interruptible(&tomoyo_policy_lock))
@@ -265,19 +686,54 @@ bool tomoyo_condition(struct tomoyo_requ
 	unsigned long right_max = 0;
 	const struct tomoyo_condition_element *condp;
 	const struct tomoyo_number_union *numbers_p;
+	const struct tomoyo_name_union *names_p;
+	const struct tomoyo_argv_entry *argv;
+	const struct tomoyo_envp_entry *envp;
 	u16 condc;
+	u16 argc;
+	u16 envc;
+	struct linux_binprm *bprm = NULL;
 	const struct tomoyo_condition *cond = acl->cond;
 	if (!cond)
 		return true;
 	condc = cond->condc;
+	argc = cond->argc;
+	envc = cond->envc;
+	if (r->ee)
+		bprm = r->ee->bprm;
+	if (!bprm && (argc || envc))
+		return false;
 	condp = (struct tomoyo_condition_element *) (cond + 1);
 	numbers_p = (const struct tomoyo_number_union *) (condp + condc);
+	names_p = (const struct tomoyo_name_union *)
+		(numbers_p + cond->numbers_count);
+	argv = (const struct tomoyo_argv_entry *) (names_p + cond->names_count);
+	envp = (const struct tomoyo_envp_entry *) (argv + argc);
 	for (i = 0; i < condc; i++) {
 		const bool match = condp->equals;
 		const u8 left = condp->left;
 		const u8 right = condp->right;
 		u8 j;
 		condp++;
+		/* Check argv[] and envp[] later. */
+		if (left == TOMOYO_ARGV_ENTRY || left == TOMOYO_ENVP_ENTRY)
+			continue;
+		/* Check string expressions. */
+		if (right == TOMOYO_NAME_UNION) {
+			const struct tomoyo_name_union *ptr = names_p++;
+			switch (left) {
+				struct tomoyo_execve_entry *ee;
+				struct file *file;
+			case TOMOYO_EXEC_REALPATH:
+				ee = r->ee;
+				file = ee ? ee->bprm->file : NULL;
+				if (!tomoyo_scan_exec_realpath(file, ptr,
+							       match))
+					goto out;
+				break;
+			}
+			continue;
+		}
 		/* Check numeric or bit-op expressions. */
 		for (j = 0; j < 2; j++) {
 			const u8 index = j ? right : left;
@@ -313,6 +769,16 @@ bool tomoyo_condition(struct tomoyo_requ
 			case TOMOYO_TASK_PPID:
 				value = sys_getppid();
 				break;
+			case TOMOYO_EXEC_ARGC:
+				if (!bprm)
+					goto out;
+				value = bprm->argc;
+				break;
+			case TOMOYO_EXEC_ENVC:
+				if (!bprm)
+					goto out;
+				value = bprm->envc;
+				break;
 			case TOMOYO_NUMBER_UNION:
 				/* Fetch values later. */
 				break;
@@ -360,5 +826,8 @@ bool tomoyo_condition(struct tomoyo_requ
  out:
 		return false;
 	}
+	/* Check argv[] and envp[] now. */
+	if (r->ee && (argc || envc))
+		return tomoyo_scan_bprm(r->ee, argc, argv, envc, envp);
 	return true;
 }
--- security-testing-2.6.orig/security/tomoyo/domain.c
+++ security-testing-2.6/security/tomoyo/domain.c
@@ -797,34 +797,74 @@ struct tomoyo_domain_info *tomoyo_find_o
 }
 
 /**
- * tomoyo_find_next_domain - Find a domain.
+ * tomoyo_dump_page - Dump a page to buffer.
  *
  * @bprm: Pointer to "struct linux_binprm".
+ * @pos:  Location to dump.
+ * @dump: Poiner to "struct tomoyo_page_dump".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos,
+		      struct tomoyo_page_dump *dump)
+{
+	struct page *page;
+	/* dump->data is released by tomoyo_find_next_domain(). */
+	if (!dump->data) {
+		dump->data = kzalloc(PAGE_SIZE, GFP_NOFS);
+		if (!dump->data)
+			return false;
+	}
+	/* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */
+#ifdef CONFIG_MMU
+	if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page, NULL) <= 0)
+		return false;
+#else
+	page = bprm->page[pos / PAGE_SIZE];
+#endif
+	if (page != dump->page) {
+		const unsigned int offset = pos % PAGE_SIZE;
+		/*
+		 * Maybe kmap()/kunmap() should be used here.
+		 * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic().
+		 * So do I.
+		 */
+		char *kaddr = kmap_atomic(page, KM_USER0);
+		dump->page = page;
+		memcpy(dump->data + offset, kaddr + offset,
+		       PAGE_SIZE - offset);
+		kunmap_atomic(kaddr, KM_USER0);
+	}
+	/* Same with put_arg_page(page) in fs/exec.c */
+#ifdef CONFIG_MMU
+	put_page(page);
+#endif
+	return true;
+}
+
+/**
+ * tomoyo_find_next_domain - Find a domain.
+ *
+ * @ee: Pointer to "struct tomoyo_execve_entry".
  *
  * Returns 0 on success, negative value otherwise.
  *
  * Caller holds tomoyo_read_lock().
  */
-int tomoyo_find_next_domain(struct linux_binprm *bprm)
+static int tomoyo_find_next_domain2(struct tomoyo_execve_entry *ee)
 {
-	char *tmp = kmalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS);
-	struct tomoyo_request_info r;
+	struct tomoyo_request_info *r = &ee->r;
 	struct tomoyo_domain_info *old_domain = tomoyo_domain();
 	struct tomoyo_domain_info *domain = NULL;
 	const char *old_domain_name = old_domain->domainname->name;
-	const char *original_name = bprm->filename;
-	bool is_enforce;
+	const char *original_name = ee->bprm->filename;
+	const bool is_enforce = (r->mode == TOMOYO_CONFIG_ENFORCING);
 	int retval = -ENOMEM;
 	bool need_kfree = false;
 	struct tomoyo_path_info rn = { }; /* real name */
 	struct tomoyo_path_info sn = { }; /* symlink name */
 	struct tomoyo_path_info ln; /* last name */
 
-	if (!tmp)
-		return -ENOMEM;
-	tomoyo_init_request_info(&r, NULL, TOMOYO_TYPE_EXECUTE);
-	is_enforce = (r.mode == TOMOYO_CONFIG_ENFORCING);
-
 	ln.name = tomoyo_get_last_name(old_domain);
 	tomoyo_fill_path_info(&ln);
  retry:
@@ -881,7 +921,7 @@ int tomoyo_find_next_domain(struct linux
 	}
 
 	/* Check execute permission. */
-	retval = tomoyo_check_exec_perm(&r, &rn);
+	retval = tomoyo_check_exec_perm(r, &rn);
 	if (retval == TOMOYO_RETRY_REQUEST)
 		goto retry;
 	if (retval < 0)
@@ -889,7 +929,7 @@ int tomoyo_find_next_domain(struct linux
 
 	if (tomoyo_is_domain_initializer(old_domain->domainname, &rn, &ln)) {
 		/* Transit to the child of tomoyo_kernel_domain domain. */
-		snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1,
+		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1,
 			 TOMOYO_ROOT_NAME " " "%s", rn.name);
 	} else if (old_domain == &tomoyo_kernel_domain &&
 		   !tomoyo_policy_loaded) {
@@ -904,28 +944,28 @@ int tomoyo_find_next_domain(struct linux
 		domain = old_domain;
 	} else {
 		/* Normal domain transition. */
-		snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1,
+		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1,
 			 "%s %s", old_domain_name, rn.name);
 	}
-	if (domain || strlen(tmp) >= TOMOYO_EXEC_TMPSIZE - 10)
+	if (domain || strlen(ee->tmp) >= TOMOYO_EXEC_TMPSIZE - 10)
 		goto done;
-	domain = tomoyo_find_domain(tmp);
+	domain = tomoyo_find_domain(ee->tmp);
 	if (domain)
 		goto done;
 	if (is_enforce) {
-		int error = tomoyo_supervisor(&r, "# wants to create domain\n"
-					      "%s\n", tmp);
+		int error = tomoyo_supervisor(r, "# wants to create domain\n"
+					      "%s\n", ee->tmp);
 		if (error == TOMOYO_RETRY_REQUEST)
 			goto retry;
 		if (error < 0)
 			goto done;
 	}
-	domain = tomoyo_find_or_assign_new_domain(tmp, old_domain->profile);
+	domain = tomoyo_find_or_assign_new_domain(ee->tmp, old_domain->profile);
  done:
 	if (domain)
 		goto out;
 	printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n",
-	       tmp);
+	       ee->tmp);
 	if (is_enforce)
 		retval = -EPERM;
 	else
@@ -935,10 +975,40 @@ int tomoyo_find_next_domain(struct linux
 		domain = old_domain;
 	/* Update reference count on "struct tomoyo_domain_info". */
 	atomic_inc(&domain->users);
-	bprm->cred->security = domain;
+	ee->bprm->cred->security = domain;
 	if (need_kfree)
 		kfree(rn.name);
 	kfree(sn.name);
-	kfree(tmp);
+	return retval;
+}
+
+/**
+ * tomoyo_find_next_domain - Find a domain.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+int tomoyo_find_next_domain(struct linux_binprm *bprm)
+{
+	int retval = -ENOMEM;
+	struct tomoyo_execve_entry *ee = kzalloc(sizeof(*ee), GFP_NOFS);
+
+	if (!ee)
+		return -ENOMEM;
+	ee->tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS);
+	if (!ee->tmp)
+		goto out;
+	/* ee->dump.data is allocated by tomoyo_dump_page(). */
+	tomoyo_init_request_info(&ee->r, NULL, TOMOYO_MAC_FILE_EXECUTE);
+	ee->r.ee = ee;
+	ee->bprm = bprm;
+	retval = tomoyo_find_next_domain2(ee);
+	kfree(ee->tmp);
+	kfree(ee->dump.data);
+ out:
+	kfree(ee);
 	return retval;
 }
--- security-testing-2.6.orig/security/tomoyo/gc.c
+++ security-testing-2.6/security/tomoyo/gc.c
@@ -194,13 +194,30 @@ void tomoyo_del_condition(struct tomoyo_
 {
 	const u16 condc = cond->condc;
 	const u16 numbers_count = cond->numbers_count;
+	const u16 names_count = cond->names_count;
+	const u16 argc = cond->argc;
+	const u16 envc = cond->envc;
 	unsigned int i;
 	const struct tomoyo_condition_element *condp
 		= (const struct tomoyo_condition_element *) (cond + 1);
 	struct tomoyo_number_union *numbers_p
 		= (struct tomoyo_number_union *) (condp + condc);
+	struct tomoyo_name_union *names_p
+		= (struct tomoyo_name_union *) (numbers_p + numbers_count);
+	const struct tomoyo_argv_entry *argv
+		= (const struct tomoyo_argv_entry *) (names_p + names_count);
+	const struct tomoyo_envp_entry *envp
+		= (const struct tomoyo_envp_entry *) (argv + argc);
 	for (i = 0; i < numbers_count; i++)
 		tomoyo_put_number_union(numbers_p++);
+	for (i = 0; i < names_count; i++)
+		tomoyo_put_name_union(names_p++);
+	for (i = 0; i < argc; argv++, i++)
+		tomoyo_put_name(argv->value);
+	for (i = 0; i < envc; envp++, i++) {
+		tomoyo_put_name(envp->name);
+		tomoyo_put_name(envp->value);
+	}
 }
 
 static void tomoyo_del_name(const struct tomoyo_name_entry *ptr)
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ