Commit c8a3667e authored by Paul Moore's avatar Paul Moore Committed by Ben Hutchings

audit: fix a double fetch in audit_log_single_execve_arg()

commit 43761473 upstream.

There is a double fetch problem in audit_log_single_execve_arg()
where we first check the execve(2) argumnets for any "bad" characters
which would require hex encoding and then re-fetch the arguments for
logging in the audit record[1].  Of course this leaves a window of
opportunity for an unsavory application to munge with the data.

This patch reworks things by only fetching the argument data once[2]
into a buffer where it is scanned and logged into the audit
records(s).  In addition to fixing the double fetch, this patch
improves on the original code in a few other ways: better handling
of large arguments which require encoding, stricter record length
checking, and some performance improvements (completely unverified,
but we got rid of some strlen() calls, that's got to be a good
thing).

As part of the development of this patch, I've also created a basic
regression test for the audit-testsuite, the test can be tracked on
GitHub at the following link:

 * https://github.com/linux-audit/audit-testsuite/issues/25

[1] If you pay careful attention, there is actually a triple fetch
problem due to a strnlen_user() call at the top of the function.

[2] This is a tiny white lie, we do make a call to strnlen_user()
prior to fetching the argument data.  I don't like it, but due to the
way the audit record is structured we really have no choice unless we
copy the entire argument at once (which would require a rather
wasteful allocation).  The good news is that with this patch the
kernel no longer relies on this strnlen_user() value for anything
beyond recording it in the log, we also update it with a trustworthy
value whenever possible.
Reported-by: default avatarPengfei Wang <wpengfeinudt@gmail.com>
Signed-off-by: default avatarPaul Moore <paul@paul-moore.com>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 8229d94a
...@@ -71,6 +71,7 @@ ...@@ -71,6 +71,7 @@
#include <linux/fs_struct.h> #include <linux/fs_struct.h>
#include <linux/compat.h> #include <linux/compat.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/uaccess.h>
#include "audit.h" #include "audit.h"
...@@ -79,7 +80,8 @@ ...@@ -79,7 +80,8 @@
#define AUDITSC_SUCCESS 1 #define AUDITSC_SUCCESS 1
#define AUDITSC_FAILURE 2 #define AUDITSC_FAILURE 2
/* no execve audit message should be longer than this (userspace limits) */ /* no execve audit message should be longer than this (userspace limits),
* see the note near the top of audit_log_execve_info() about this value */
#define MAX_EXECVE_AUDIT_LEN 7500 #define MAX_EXECVE_AUDIT_LEN 7500
/* max length to print of cmdline/proctitle value during audit */ /* max length to print of cmdline/proctitle value during audit */
...@@ -1015,185 +1017,178 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid, ...@@ -1015,185 +1017,178 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
return rc; return rc;
} }
/* static void audit_log_execve_info(struct audit_context *context,
* to_send and len_sent accounting are very loose estimates. We aren't struct audit_buffer **ab)
* really worried about a hard cap to MAX_EXECVE_AUDIT_LEN so much as being {
* within about 500 bytes (next page boundary) long len_max;
* long len_rem;
* why snprintf? an int is up to 12 digits long. if we just assumed when long len_full;
* logging that a[%d]= was going to be 16 characters long we would be wasting long len_buf;
* space in every audit message. In one 7500 byte message we can log up to long len_abuf;
* about 1000 min size arguments. That comes down to about 50% waste of space long len_tmp;
* if we didn't do the snprintf to find out how long arg_num_len was. bool require_data;
*/ bool encode;
static int audit_log_single_execve_arg(struct audit_context *context, unsigned int iter;
struct audit_buffer **ab, unsigned int arg;
int arg_num, char *buf_head;
size_t *len_sent, char *buf;
const char __user *p, const char __user *p = (const char __user *)current->mm->arg_start;
char *buf)
{ /* NOTE: this buffer needs to be large enough to hold all the non-arg
char arg_num_len_buf[12]; * data we put in the audit record for this argument (see the
const char __user *tmp_p = p; * code below) ... at this point in time 96 is plenty */
/* how many digits are in arg_num? 5 is the length of ' a=""' */ char abuf[96];
size_t arg_num_len = snprintf(arg_num_len_buf, 12, "%d", arg_num) + 5;
size_t len, len_left, to_send; /* NOTE: we set MAX_EXECVE_AUDIT_LEN to a rather arbitrary limit, the
size_t max_execve_audit_len = MAX_EXECVE_AUDIT_LEN; * current value of 7500 is not as important as the fact that it
unsigned int i, has_cntl = 0, too_long = 0; * is less than 8k, a setting of 7500 gives us plenty of wiggle
int ret; * room if we go over a little bit in the logging below */
WARN_ON_ONCE(MAX_EXECVE_AUDIT_LEN > 7500);
/* strnlen_user includes the null we don't want to send */ len_max = MAX_EXECVE_AUDIT_LEN;
len_left = len = strnlen_user(p, MAX_ARG_STRLEN) - 1;
/* scratch buffer to hold the userspace args */
/* buf_head = kmalloc(MAX_EXECVE_AUDIT_LEN + 1, GFP_KERNEL);
* We just created this mm, if we can't find the strings if (!buf_head) {
* we just copied into it something is _very_ wrong. Similar audit_panic("out of memory for argv string");
* for strings that are too long, we should not have created return;
* any.
*/
if (unlikely((len == -1) || len > MAX_ARG_STRLEN - 1)) {
WARN_ON(1);
send_sig(SIGKILL, current, 0);
return -1;
} }
buf = buf_head;
/* walk the whole argument looking for non-ascii chars */ audit_log_format(*ab, "argc=%d", context->execve.argc);
len_rem = len_max;
len_buf = 0;
len_full = 0;
require_data = true;
encode = false;
iter = 0;
arg = 0;
do { do {
if (len_left > MAX_EXECVE_AUDIT_LEN) /* NOTE: we don't ever want to trust this value for anything
to_send = MAX_EXECVE_AUDIT_LEN; * serious, but the audit record format insists we
else * provide an argument length for really long arguments,
to_send = len_left; * e.g. > MAX_EXECVE_AUDIT_LEN, so we have no choice but
ret = copy_from_user(buf, tmp_p, to_send); * to use strncpy_from_user() to obtain this value for
/* * recording in the log, although we don't use it
* There is no reason for this copy to be short. We just * anywhere here to avoid a double-fetch problem */
* copied them here, and the mm hasn't been exposed to user- if (len_full == 0)
* space yet. len_full = strnlen_user(p, MAX_ARG_STRLEN) - 1;
*/
if (ret) { /* read more data from userspace */
WARN_ON(1); if (require_data) {
/* can we make more room in the buffer? */
if (buf != buf_head) {
memmove(buf_head, buf, len_buf);
buf = buf_head;
}
/* fetch as much as we can of the argument */
len_tmp = strncpy_from_user(&buf_head[len_buf], p,
len_max - len_buf);
if (len_tmp == -EFAULT) {
/* unable to copy from userspace */
send_sig(SIGKILL, current, 0); send_sig(SIGKILL, current, 0);
return -1; goto out;
} } else if (len_tmp == (len_max - len_buf)) {
buf[to_send] = '\0'; /* buffer is not large enough */
has_cntl = audit_string_contains_control(buf, to_send); require_data = true;
if (has_cntl) { /* NOTE: if we are going to span multiple
/* * buffers force the encoding so we stand
* hex messages get logged as 2 bytes, so we can only * a chance at a sane len_full value and
* send half as much in each message * consistent record encoding */
*/ encode = true;
max_execve_audit_len = MAX_EXECVE_AUDIT_LEN / 2; len_full = len_full * 2;
break; p += len_tmp;
} } else {
len_left -= to_send; require_data = false;
tmp_p += to_send; if (!encode)
} while (len_left > 0); encode = audit_string_contains_control(
buf, len_tmp);
len_left = len; /* try to use a trusted value for len_full */
if (len_full < len_max)
if (len > max_execve_audit_len) len_full = (encode ?
too_long = 1; len_tmp * 2 : len_tmp);
p += len_tmp + 1;
/* rewalk the argument actually logging the message */ }
for (i = 0; len_left > 0; i++) { len_buf += len_tmp;
int room_left; buf_head[len_buf] = '\0';
if (len_left > max_execve_audit_len) /* length of the buffer in the audit record? */
to_send = max_execve_audit_len; len_abuf = (encode ? len_buf * 2 : len_buf + 2);
else }
to_send = len_left;
/* write as much as we can to the audit log */
/* do we have space left to send this argument in this ab? */ if (len_buf > 0) {
room_left = MAX_EXECVE_AUDIT_LEN - arg_num_len - *len_sent; /* NOTE: some magic numbers here - basically if we
if (has_cntl) * can't fit a reasonable amount of data into the
room_left -= (to_send * 2); * existing audit buffer, flush it and start with
else * a new buffer */
room_left -= to_send; if ((sizeof(abuf) + 8) > len_rem) {
if (room_left < 0) { len_rem = len_max;
*len_sent = 0;
audit_log_end(*ab); audit_log_end(*ab);
*ab = audit_log_start(context, GFP_KERNEL, AUDIT_EXECVE); *ab = audit_log_start(context,
GFP_KERNEL, AUDIT_EXECVE);
if (!*ab) if (!*ab)
return 0; goto out;
} }
/* /* create the non-arg portion of the arg record */
* first record needs to say how long the original string was len_tmp = 0;
* so we can be sure nothing was lost. if (require_data || (iter > 0) ||
*/ ((len_abuf + sizeof(abuf)) > len_rem)) {
if ((i == 0) && (too_long)) if (iter == 0) {
audit_log_format(*ab, " a%d_len=%zu", arg_num, len_tmp += snprintf(&abuf[len_tmp],
has_cntl ? 2*len : len); sizeof(abuf) - len_tmp,
" a%d_len=%lu",
/* arg, len_full);
* 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);
return -1;
} }
buf[to_send] = '\0'; len_tmp += snprintf(&abuf[len_tmp],
sizeof(abuf) - len_tmp,
/* actually log it */ " a%d[%d]=", arg, iter++);
audit_log_format(*ab, " a%d", arg_num); } else
if (too_long) len_tmp += snprintf(&abuf[len_tmp],
audit_log_format(*ab, "[%d]", i); sizeof(abuf) - len_tmp,
audit_log_format(*ab, "="); " a%d=", arg);
if (has_cntl) WARN_ON(len_tmp >= sizeof(abuf));
audit_log_n_hex(*ab, buf, to_send); abuf[sizeof(abuf) - 1] = '\0';
else
audit_log_string(*ab, buf); /* log the arg in the audit record */
audit_log_format(*ab, "%s", abuf);
p += to_send; len_rem -= len_tmp;
len_left -= to_send; len_tmp = len_buf;
*len_sent += arg_num_len; if (encode) {
if (has_cntl) if (len_abuf > len_rem)
*len_sent += to_send * 2; len_tmp = len_rem / 2; /* encoding */
else audit_log_n_hex(*ab, buf, len_tmp);
*len_sent += to_send; len_rem -= len_tmp * 2;
len_abuf -= len_tmp * 2;
} else {
if (len_abuf > len_rem)
len_tmp = len_rem - 2; /* quotes */
audit_log_n_string(*ab, buf, len_tmp);
len_rem -= len_tmp + 2;
/* don't subtract the "2" because we still need
* to add quotes to the remaining string */
len_abuf -= len_tmp;
} }
/* include the null we didn't log */ len_buf -= len_tmp;
return len + 1; buf += len_tmp;
}
static void audit_log_execve_info(struct audit_context *context,
struct audit_buffer **ab)
{
int i, len;
size_t len_sent = 0;
const char __user *p;
char *buf;
p = (const char __user *)current->mm->arg_start;
audit_log_format(*ab, "argc=%d", context->execve.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");
return;
} }
for (i = 0; i < context->execve.argc; i++) { /* ready to move to the next argument? */
len = audit_log_single_execve_arg(context, ab, i, if ((len_buf == 0) && !require_data) {
&len_sent, p, buf); arg++;
if (len <= 0) iter = 0;
break; len_full = 0;
p += len; require_data = true;
encode = false;
} }
kfree(buf); } while (arg < context->execve.argc);
/* NOTE: the caller handles the final audit_log_end() call */
out:
kfree(buf_head);
} }
static void show_special(struct audit_context *context, int *call_panic) static void show_special(struct audit_context *context, int *call_panic)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment