Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 27922b4260f65d317aabda37e42bbbff > files > 200

kernel-2.6.18-238.el5.src.rpm

From: Eric Paris <eparis@redhat.com>
Date: Fri, 18 Jan 2008 10:58:47 -0500
Subject: [audit] break execve records into smaller parts
Message-id: 1200671927.21881.1.camel@localhost.localdomain
O-Subject: [RHEL5.2 PATCH] Audit: break execve records into smaller parts
Bugzilla: 429692

When auditing the arguments to execve the audit system was building a
single skb to hold the entire argument list.  With large execve argument
lists (ls * in a huge directory for example) we couldn't allocate enough
kernel memory to hold the skb and so the audit system failed its
purpose.

this patch breaks those argument lists (and individual arguments if they
are huge) into around 8k blobs.  8k was chosen because it was found that
audit userspace couldn't handle larger messages and also because an
order 2 memory allocation is pretty easy even under pressure.

I haven't sent this through brew, just built/tested by hand, so I don't
know if audit_log_untrustedstring() and audit_log_n_untrustedstring()
return changes break kABI.  If they do I don't have to change them, but
I don't remember accepting these onto the kABI list, so I assume I'm
safe and this way i'm following my upstream work.

It is headed upstream for 2.6.25
http://git.kernel.org/?p=linux/kernel/git/viro/audit-current.git;a=commitdiff;h=7e2f0942aaea7f1fdd658fea4dc86ed5ed19134f

-Eric

Acked-by: James Morris <jmorris@redhat.com>

diff --git a/include/linux/audit.h b/include/linux/audit.h
index dca9864..517b73d 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -502,14 +502,16 @@ extern void		    audit_log_format(struct audit_buffer *ab,
 					     const char *fmt, ...)
 			    __attribute__((format(printf,2,3)));
 extern void		    audit_log_end(struct audit_buffer *ab);
-extern void		    audit_log_hex(struct audit_buffer *ab,
-					  const unsigned char *buf,
+extern void		    audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
 					  size_t len);
-extern const char *	    audit_log_untrustedstring(struct audit_buffer *ab,
-						      const char *string);
-extern const char *	    audit_log_n_untrustedstring(struct audit_buffer *ab,
-							size_t n,
+extern void		    audit_log_n_string(struct audit_buffer *ab, size_t slen,
+						const char *string);
+extern int		    audit_string_contains_control(const char *string, size_t len);
+extern void		    audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
 							const char *string);
+
+extern void		    audit_log_untrustedstring(struct audit_buffer *ab,
+						      const char *string);
 extern void		    audit_log_d_path(struct audit_buffer *ab,
 					     const char *prefix,
 					     struct dentry *dentry,
diff --git a/kernel/audit.c b/kernel/audit.c
index ed9e35f..049eec1 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1256,8 +1256,8 @@ void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
  * Format a string of no more than slen characters into the audit buffer,
  * enclosed in quote marks.
  */
-static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
-			       const char *string)
+void audit_log_n_string(struct audit_buffer *ab, size_t slen,
+			const char *string)
 {
 	int avail, new_len;
 	unsigned char *ptr;
@@ -1285,7 +1285,22 @@ static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
 }
 
 /**
- * audit_log_n_unstrustedstring - log a string that may contain random characters
+ * audit_string_contains_control - does a string need to be logged in hex
+ * @string - string to be checked
+ * @len - max length of the string to check
+ */
+int audit_string_contains_control(const char *string, size_t len)
+{
+	const unsigned char *p;
+	for (p = string; p < (const unsigned char *)string + len && *p; p++) {
+		if (*p == '"' || *p < 0x21 || *p > 0x7f)
+			return 1;
+	}
+	return 0;
+}
+
+/**
+ * audit_log_n_untrustedstring - log a string that may contain random characters
  * @ab: audit_buffer
  * @len: lenth of string (not including trailing null)
  * @string: string to be logged
@@ -1298,33 +1313,26 @@ static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
  * The caller specifies the number of characters in the string to log, which may
  * or may not be the entire string.
  */
-const char *audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
-					const char *string)
+void audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
+				 const char *string)
 {
-	const unsigned char *p = string;
-
-	while (*p) {
-		if (*p == '"' || *p < 0x21 || *p > 0x7f) {
-			audit_log_hex(ab, string, len);
-			return string + len + 1;
-		}
-		p++;
-	}
-	audit_log_n_string(ab, len, string);
-	return p + 1;
+	if (audit_string_contains_control(string, len))
+		audit_log_hex(ab, string, len);
+	else
+		audit_log_n_string(ab, len, string);
 }
 
 /**
- * audit_log_unstrustedstring - log a string that may contain random characters
+ * audit_log_untrustedstring - log a string that may contain random characters
  * @ab: audit_buffer
  * @string: string to be logged
  *
- * Same as audit_log_n_unstrustedstring(), except that strlen is used to
+ * Same as audit_log_n_untrustedstring(), except that strlen is used to
  * determine string length.
  */
-const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
+void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
 {
-	return audit_log_n_untrustedstring(ab, strlen(string), string);
+	audit_log_n_untrustedstring(ab, strlen(string), string);
 }
 
 /* This is a helper-function to print the escaped d_path */
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index e0fdfbf..0db0afe 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -82,6 +82,9 @@ extern int audit_enabled;
 /* Indicates that audit should log the full pathname. */
 #define AUDIT_NAME_FULL -1
 
+/* no execve audit message should be longer than this (userspace limits) */
+#define MAX_EXECVE_AUDIT_LEN 7500
+
 /* number of audit rules */
 int audit_n_rules;
 
@@ -969,6 +972,117 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
 	return rc;
 }
 
+/*
+ * to_send and len_sent accounting are very loose estimates.  We aren't
+ * really worried about a hard cap to MAX_EXECVE_AUDIT_LEN so much as being
+ * within about 500 bytes (next page boundry)
+ *
+ * why snprintf?  an int is up to 12 digits long.  if we just assumed when
+ * logging that a[%d]= was going to be 16 characters long we would be wasting
+ * space in every audit message.  In one 7500 byte message we can log up to
+ * about 1000 min size arguments.  That comes down to about 50% waste of space
+ * if we didn't do the snprintf to find out how long arg_num_len was.
+ */
+static int audit_log_single_execve_arg(struct audit_context *context,
+					struct audit_buffer **ab,
+					int arg_num,
+					size_t *len_sent,
+					const char *p)
+{
+	char arg_num_len_buf[12];
+	/* how many digits are in arg_num? 3 is the length of a=\n */
+	size_t arg_num_len = snprintf(arg_num_len_buf, 12, "%d", arg_num) + 3;
+	size_t len, len_left, to_send;
+	size_t max_execve_audit_len = MAX_EXECVE_AUDIT_LEN;
+	unsigned int i, has_cntl = 0, too_long = 0;
+
+	/* strnlen_user includes the null we don't want to send */
+	len_left = len = strlen(p);
+
+	has_cntl = audit_string_contains_control(p, len);
+	if (has_cntl)
+		/*
+		 * hex messages get logged as 2 bytes, so we can only
+		 * send half as much in each message
+		 */
+		max_execve_audit_len = MAX_EXECVE_AUDIT_LEN / 2;
+
+	if (len > max_execve_audit_len)
+		too_long = 1;
+
+	/* walk the argument actually logging the message */
+	for (i = 0; len_left > 0; i++) {
+		int room_left;
+
+		if (len_left > max_execve_audit_len)
+			to_send = max_execve_audit_len;
+		else
+			to_send = len_left;
+
+		/* do we have space left to send this argument in this ab? */
+		room_left = MAX_EXECVE_AUDIT_LEN - arg_num_len - *len_sent;
+		if (has_cntl)
+			room_left -= (to_send * 2);
+		else
+			room_left -= to_send;
+		if (room_left < 0) {
+			*len_sent = 0;
+			audit_log_end(*ab);
+			*ab = audit_log_start(context, GFP_KERNEL, AUDIT_EXECVE);
+			if (!*ab)
+				return 0;
+		}
+
+		/*
+		 * first record needs to say how long the original string was
+		 * so we can be sure nothing was lost.
+		 */
+		if ((i == 0) && (too_long))
+			audit_log_format(*ab, "a%d_len=%ld ", arg_num,
+					 has_cntl ? 2*len : len);
+
+		/* actually log it */
+		audit_log_format(*ab, "a%d", arg_num);
+		if (too_long)
+			audit_log_format(*ab, "[%d]", i);
+		audit_log_format(*ab, "=");
+		if (has_cntl)
+			audit_log_hex(*ab, p, to_send);
+		else
+			audit_log_n_string(*ab, to_send, p);
+		audit_log_format(*ab, "\n");
+
+		p += to_send;
+		len_left -= to_send;
+		*len_sent += arg_num_len;
+		if (has_cntl)
+			*len_sent += to_send * 2;
+		else
+			*len_sent += to_send;
+	}
+	return len;
+}
+
+static void audit_log_execve_info(struct audit_context *context,
+				  struct audit_buffer **ab,
+				  struct audit_aux_data_execve *axi)
+{
+	int i;
+	size_t len, len_sent = 0;
+	const char *p;
+
+	p = axi->mem;
+
+	audit_log_format(*ab, "argc=%d ", axi->argc);
+
+	for (i = 0; i < axi->argc; i++) {
+		len = audit_log_single_execve_arg(context, ab, i, &len_sent, p);
+		if (len <= 0)
+			break;
+		/* skip the null */
+		p += len + 1;
+	}
+}
 static void audit_log_exit(struct audit_context *context, struct task_struct *tsk)
 {
 	int i, call_panic = 0;
@@ -1108,13 +1222,7 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
 
 		case AUDIT_EXECVE: {
 			struct audit_aux_data_execve *axi = (void *)aux;
-			int i;
-			const char *p;
-			for (i = 0, p = axi->mem; i < axi->argc; i++) {
-				audit_log_format(ab, "a%d=", i);
-				p = audit_log_untrustedstring(ab, p);
-				audit_log_format(ab, "\n");
-			}
+			audit_log_execve_info(context, &ab, axi);
 			break; }
 
 		case AUDIT_SOCKETCALL: {