core_pattern: fix truncation by core_pattern handler with long parameters

We met a parameter truncated issue, consider following:
> echo "|/root/core_pattern_pipe_test %p /usr/libexec/blah-blah-blah \
%s %c %p %u %g 11 12345678901234567890123456789012345678 %t" > \
/proc/sys/kernel/core_pattern

This is okay because the strings is less than CORENAME_MAX_SIZE.  "cat
/proc/sys/kernel/core_pattern" shows the whole string.  but after we run
core_pattern_pipe_test in man page, we found last parameter was truncated
like below:

        argc[10]=<12807486>

The root cause is core_pattern allows % specifiers, which need to be
replaced during parse time, but the replace may expand the strings to
larger than CORENAME_MAX_SIZE.  So if the last parameter is % specifiers,
the replace code is using snprintf(out_ptr, out_end - out_ptr, ...), this
will write out of corename array.

[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: Xiaotian Feng <dfeng@redhat.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Reviewed-by: Neil Horman <nhorman@tuxdriver.com>
Cc: Roland McGrath <roland@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Xiaotian Feng 2010-10-27 15:34:08 -07:00 committed by Linus Torvalds
parent 9b1bf12d5d
commit 1b0d300bd0

155
fs/exec.c
View File

@ -66,6 +66,12 @@ char core_pattern[CORENAME_MAX_SIZE] = "core";
unsigned int core_pipe_limit;
int suid_dumpable = 0;
struct core_name {
char *corename;
int used, size;
};
static atomic_t call_count = ATOMIC_INIT(1);
/* The maximal length of core_pattern is also specified in sysctl.c */
static LIST_HEAD(formats);
@ -1459,127 +1465,148 @@ void set_binfmt(struct linux_binfmt *new)
EXPORT_SYMBOL(set_binfmt);
static int expand_corename(struct core_name *cn)
{
char *old_corename = cn->corename;
cn->size = CORENAME_MAX_SIZE * atomic_inc_return(&call_count);
cn->corename = krealloc(old_corename, cn->size, GFP_KERNEL);
if (!cn->corename) {
kfree(old_corename);
return -ENOMEM;
}
return 0;
}
static int cn_printf(struct core_name *cn, const char *fmt, ...)
{
char *cur;
int need;
int ret;
va_list arg;
va_start(arg, fmt);
need = vsnprintf(NULL, 0, fmt, arg);
va_end(arg);
if (likely(need < cn->size - cn->used - 1))
goto out_printf;
ret = expand_corename(cn);
if (ret)
goto expand_fail;
out_printf:
cur = cn->corename + cn->used;
va_start(arg, fmt);
vsnprintf(cur, need + 1, fmt, arg);
va_end(arg);
cn->used += need;
return 0;
expand_fail:
return ret;
}
/* format_corename will inspect the pattern parameter, and output a
* name into corename, which must have space for at least
* CORENAME_MAX_SIZE bytes plus one byte for the zero terminator.
*/
static int format_corename(char *corename, long signr)
static int format_corename(struct core_name *cn, long signr)
{
const struct cred *cred = current_cred();
const char *pat_ptr = core_pattern;
int ispipe = (*pat_ptr == '|');
char *out_ptr = corename;
char *const out_end = corename + CORENAME_MAX_SIZE;
int rc;
int pid_in_pattern = 0;
int err = 0;
cn->size = CORENAME_MAX_SIZE * atomic_read(&call_count);
cn->corename = kmalloc(cn->size, GFP_KERNEL);
cn->used = 0;
if (!cn->corename)
return -ENOMEM;
/* Repeat as long as we have more pattern to process and more output
space */
while (*pat_ptr) {
if (*pat_ptr != '%') {
if (out_ptr == out_end)
if (*pat_ptr == 0)
goto out;
*out_ptr++ = *pat_ptr++;
err = cn_printf(cn, "%c", *pat_ptr++);
} else {
switch (*++pat_ptr) {
/* single % at the end, drop that */
case 0:
goto out;
/* Double percent, output one percent */
case '%':
if (out_ptr == out_end)
goto out;
*out_ptr++ = '%';
err = cn_printf(cn, "%c", '%');
break;
/* pid */
case 'p':
pid_in_pattern = 1;
rc = snprintf(out_ptr, out_end - out_ptr,
"%d", task_tgid_vnr(current));
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%d",
task_tgid_vnr(current));
break;
/* uid */
case 'u':
rc = snprintf(out_ptr, out_end - out_ptr,
"%d", cred->uid);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%d", cred->uid);
break;
/* gid */
case 'g':
rc = snprintf(out_ptr, out_end - out_ptr,
"%d", cred->gid);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%d", cred->gid);
break;
/* signal that caused the coredump */
case 's':
rc = snprintf(out_ptr, out_end - out_ptr,
"%ld", signr);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%ld", signr);
break;
/* UNIX time of coredump */
case 't': {
struct timeval tv;
do_gettimeofday(&tv);
rc = snprintf(out_ptr, out_end - out_ptr,
"%lu", tv.tv_sec);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%lu", tv.tv_sec);
break;
}
/* hostname */
case 'h':
down_read(&uts_sem);
rc = snprintf(out_ptr, out_end - out_ptr,
"%s", utsname()->nodename);
err = cn_printf(cn, "%s",
utsname()->nodename);
up_read(&uts_sem);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
break;
/* executable */
case 'e':
rc = snprintf(out_ptr, out_end - out_ptr,
"%s", current->comm);
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%s", current->comm);
break;
/* core limit size */
case 'c':
rc = snprintf(out_ptr, out_end - out_ptr,
"%lu", rlimit(RLIMIT_CORE));
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, "%lu",
rlimit(RLIMIT_CORE));
break;
default:
break;
}
++pat_ptr;
}
if (err)
return err;
}
/* Backward compatibility with core_uses_pid:
*
* If core_pattern does not include a %p (as is the default)
* and core_uses_pid is set, then .%pid will be appended to
* the filename. Do not do this for piped commands. */
if (!ispipe && !pid_in_pattern && core_uses_pid) {
rc = snprintf(out_ptr, out_end - out_ptr,
".%d", task_tgid_vnr(current));
if (rc > out_end - out_ptr)
goto out;
out_ptr += rc;
err = cn_printf(cn, ".%d", task_tgid_vnr(current));
if (err)
return err;
}
out:
*out_ptr = 0;
return ispipe;
}
@ -1856,7 +1883,7 @@ static int umh_pipe_setup(struct subprocess_info *info)
void do_coredump(long signr, int exit_code, struct pt_regs *regs)
{
struct core_state core_state;
char corename[CORENAME_MAX_SIZE + 1];
struct core_name cn;
struct mm_struct *mm = current->mm;
struct linux_binfmt * binfmt;
const struct cred *old_cred;
@ -1911,7 +1938,13 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
*/
clear_thread_flag(TIF_SIGPENDING);
ispipe = format_corename(corename, signr);
ispipe = format_corename(&cn, signr);
if (ispipe == -ENOMEM) {
printk(KERN_WARNING "format_corename failed\n");
printk(KERN_WARNING "Aborting core\n");
goto fail_corename;
}
if (ispipe) {
int dump_count;
@ -1948,7 +1981,7 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
goto fail_dropcount;
}
helper_argv = argv_split(GFP_KERNEL, corename+1, NULL);
helper_argv = argv_split(GFP_KERNEL, cn.corename+1, NULL);
if (!helper_argv) {
printk(KERN_WARNING "%s failed to allocate memory\n",
__func__);
@ -1961,7 +1994,7 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
argv_free(helper_argv);
if (retval) {
printk(KERN_INFO "Core dump to %s pipe failed\n",
corename);
cn.corename);
goto close_fail;
}
} else {
@ -1970,7 +2003,7 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
if (cprm.limit < binfmt->min_coredump)
goto fail_unlock;
cprm.file = filp_open(corename,
cprm.file = filp_open(cn.corename,
O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag,
0600);
if (IS_ERR(cprm.file))
@ -2012,6 +2045,8 @@ fail_dropcount:
if (ispipe)
atomic_dec(&core_dump_count);
fail_unlock:
kfree(cn.corename);
fail_corename:
coredump_finish(mm);
revert_creds(old_cred);
fail_creds: