[AUDIT] break large execve argument logging into smaller messages
authorEric Paris <eparis@redhat.com>
Mon, 7 Jan 2008 19:31:58 +0000 (14:31 -0500)
committerAl Viro <viro@zeniv.linux.org.uk>
Fri, 1 Feb 2008 19:23:55 +0000 (14:23 -0500)
execve arguments can be quite large.  There is no limit on the number of
arguments and a 4G limit on the size of an argument.

this patch prints those aruguments in bite sized pieces.  a userspace size
limitation of 8k was discovered so this keeps messages around 7.5k

single arguments larger than 7.5k in length are split into multiple records
and can be identified as aX[Y]=

Signed-off-by: Eric Paris <eparis@redhat.com>
Documentation/filesystems/proc.txt
include/linux/audit.h
kernel/audit.c
kernel/auditsc.c
kernel/sysctl.c

index 11fe51c036bf36a3fc243b6620fd098929966e9a..194c8f35132065496048f8ae0bd80b688240d96c 100644 (file)
@@ -1134,13 +1134,6 @@ check the amount of free space (value is in seconds). Default settings are: 4,
 resume it  if we have a value of 3 or more percent; consider information about
 the amount of free space valid for 30 seconds
 
-audit_argv_kb
--------------
-
-The file contains a single value denoting the limit on the argv array size
-for execve (in KiB). This limit is only applied when system call auditing for
-execve is enabled, otherwise the value is ignored.
-
 ctrl-alt-del
 ------------
 
index 4d1c2f2fcc1b335d982af175bd6abcc96775fc21..97153027207a27d3ded23f099c105321613c9372 100644 (file)
@@ -525,9 +525,11 @@ extern void                    audit_log_end(struct audit_buffer *ab);
 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,
+extern int                 audit_string_contains_control(const char *string,
+                                                         size_t len);
+extern void                audit_log_untrustedstring(struct audit_buffer *ab,
                                                      const char *string);
-extern const char *        audit_log_n_untrustedstring(struct audit_buffer *ab,
+extern void                audit_log_n_untrustedstring(struct audit_buffer *ab,
                                                        size_t n,
                                                        const char *string);
 extern void                audit_log_d_path(struct audit_buffer *ab,
index cf669828942657eeb18b8f45a8d5a455575fd0e5..26ff925e13f202e40abcba42cc34bca75d2ff24e 100644 (file)
@@ -1349,6 +1349,21 @@ static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
        skb_put(skb, slen + 2); /* don't include null terminator */
 }
 
+/**
+ * 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
@@ -1363,19 +1378,13 @@ 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;
-
-       for (p = string; p < (const unsigned char *)string + len && *p; p++) {
-               if (*p == '"' || *p < 0x21 || *p > 0x7f) {
-                       audit_log_hex(ab, string, len);
-                       return string + len + 1;
-               }
-       }
-       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);
 }
 
 /**
@@ -1386,9 +1395,9 @@ const char *audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
  * 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 */
index aaaca8a13bbe57cd17f084af9bc29a497fac1e62..6e03322e155b5ca2d72d45fe0b988b76f55b186a 100644 (file)
@@ -78,6 +78,9 @@ extern struct list_head audit_filter_list[];
 /* 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;
 
@@ -965,55 +968,187 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
        return rc;
 }
 
-static void audit_log_execve_info(struct audit_buffer *ab,
-               struct audit_aux_data_execve *axi)
+/*
+ * 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 __user *p,
+                                       char *buf)
 {
-       int i;
-       long len, ret;
-       const char __user *p;
-       char *buf;
+       char arg_num_len_buf[12];
+       const char __user *tmp_p = p;
+       /* 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;
+       int ret;
+
+       /* strnlen_user includes the null we don't want to send */
+       len_left = len = strnlen_user(p, MAX_ARG_STRLEN) - 1;
 
-       if (axi->mm != current->mm)
-               return; /* execve failed, no additional info */
-
-       p = (const char __user *)axi->mm->arg_start;
+       /*
+        * We just created this mm, if we can't find the strings
+        * we just copied into it something is _very_ wrong. Similar
+        * for strings that are too long, we should not have created
+        * any.
+        */
+       if (unlikely((len  = -1) || len > MAX_ARG_STRLEN - 1)) {
+               WARN_ON(1);
+               send_sig(SIGKILL, current, 0);
+       }
 
-       for (i = 0; i < axi->argc; i++, p += len) {
-               len = strnlen_user(p, MAX_ARG_STRLEN);
+       /* walk the whole argument looking for non-ascii chars */
+       do {
+               if (len_left > MAX_EXECVE_AUDIT_LEN)
+                       to_send = MAX_EXECVE_AUDIT_LEN;
+               else
+                       to_send = len_left;
+               ret = copy_from_user(buf, tmp_p, to_send);
                /*
-                * We just created this mm, if we can't find the strings
-                * we just copied into it something is _very_ wrong. Similar
-                * for strings that are too long, we should not have created
-                * any.
+                * There is no reason for this copy to be short. We just
+                * copied them here, and the mm hasn't been exposed to user-
+                * space yet.
                 */
-               if (!len || len > MAX_ARG_STRLEN) {
+               if (ret) {
                        WARN_ON(1);
                        send_sig(SIGKILL, current, 0);
                }
-
-               buf = kmalloc(len, GFP_KERNEL);
-               if (!buf) {
-                       audit_panic("out of memory for argv string\n");
+               buf[to_send] = '\0';
+               has_cntl = audit_string_contains_control(buf, to_send);
+               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;
                        break;
                }
+               len_left -= to_send;
+               tmp_p += to_send;
+       } while (len_left > 0);
+
+       len_left = len;
+
+       if (len > max_execve_audit_len)
+               too_long = 1;
+
+       /* rewalk 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;
+               }
 
-               ret = copy_from_user(buf, p, len);
                /*
-                * There is no reason for this copy to be short. We just
-                * copied them here, and the mm hasn't been exposed to user-
-                * space yet.
+                * 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);
+
+               /*
+                * normally arguments are small enough to fit and we already
+                * filled buf above when we checked for control characters
+                * so don't bother with another copy_from_user
                 */
+               if (len >= max_execve_audit_len)
+                       ret = copy_from_user(buf, p, to_send);
+               else
+                       ret = 0;
                if (ret) {
                        WARN_ON(1);
                        send_sig(SIGKILL, current, 0);
                }
+               buf[to_send] = '\0';
+
+               /* 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, buf, to_send);
+               else
+                       audit_log_format(*ab, "\"%s\"", buf);
+               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;
+       }
+       /* include the null we didn't log */
+       return len + 1;
+}
+
+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 __user *p;
+       char *buf;
 
-               audit_log_format(ab, "a%d=", i);
-               audit_log_untrustedstring(ab, buf);
-               audit_log_format(ab, "\n");
+       if (axi->mm != current->mm)
+               return; /* execve failed, no additional info */
+
+       p = (const char __user *)axi->mm->arg_start;
 
-               kfree(buf);
+       audit_log_format(*ab, "argc=%d ", axi->argc);
+
+       /*
+        * we need some kernel buffer to hold the userspace args.  Just
+        * allocate one big one rather than allocating one of the right size
+        * for every single argument inside audit_log_single_execve_arg()
+        * should be <8k allocation so should be pretty safe.
+        */
+       buf = kmalloc(MAX_EXECVE_AUDIT_LEN + 1, GFP_KERNEL);
+       if (!buf) {
+               audit_panic("out of memory for argv string\n");
+               return;
        }
+
+       for (i = 0; i < axi->argc; i++) {
+               len = audit_log_single_execve_arg(context, ab, i,
+                                                 &len_sent, p, buf);
+               if (len <= 0)
+                       break;
+               p += len;
+       }
+       kfree(buf);
 }
 
 static void audit_log_exit(struct audit_context *context, struct task_struct *tsk)
@@ -1157,7 +1292,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;
-                       audit_log_execve_info(ab, axi);
+                       audit_log_execve_info(context, &ab, axi);
                        break; }
 
                case AUDIT_SOCKETCALL: {
@@ -2094,8 +2229,6 @@ int __audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode
        return 0;
 }
 
-int audit_argv_kb = 32;
-
 int audit_bprm(struct linux_binprm *bprm)
 {
        struct audit_aux_data_execve *ax;
@@ -2104,14 +2237,6 @@ int audit_bprm(struct linux_binprm *bprm)
        if (likely(!audit_enabled || !context || context->dummy))
                return 0;
 
-       /*
-        * Even though the stack code doesn't limit the arg+env size any more,
-        * the audit code requires that _all_ arguments be logged in a single
-        * netlink skb. Hence cap it :-(
-        */
-       if (bprm->argv_len > (audit_argv_kb << 10))
-               return -E2BIG;
-
        ax = kmalloc(sizeof(*ax), GFP_KERNEL);
        if (!ax)
                return -ENOMEM;
index 357b68ba23ecd7975f93e4273787cf1041ca8df8..7cb1ac3e6fff9583c0d880788190d14da6e2ced1 100644 (file)
@@ -81,7 +81,6 @@ extern int percpu_pagelist_fraction;
 extern int compat_log;
 extern int maps_protect;
 extern int sysctl_stat_interval;
-extern int audit_argv_kb;
 extern int latencytop_enabled;
 
 /* Constants used for minimum and  maximum */
@@ -390,16 +389,6 @@ static struct ctl_table kern_table[] = {
                .mode           = 0644,
                .proc_handler   = &proc_dointvec,
        },
-#ifdef CONFIG_AUDITSYSCALL
-       {
-               .ctl_name       = CTL_UNNUMBERED,
-               .procname       = "audit_argv_kb",
-               .data           = &audit_argv_kb,
-               .maxlen         = sizeof(int),
-               .mode           = 0644,
-               .proc_handler   = &proc_dointvec,
-       },
-#endif
        {
                .ctl_name       = KERN_CORE_PATTERN,
                .procname       = "core_pattern",