Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20110809121632.18aef937@notabene.brown>
Date: Tue, 9 Aug 2011 12:16:32 +1000
From: NeilBrown <neilb@...e.de>
To: Vasiliy Kulikov <segoon@...nwall.com>
Cc: Linus Torvalds <torvalds@...ux-foundation.org>,
	linux-kernel@...r.kernel.org, Greg Kroah-Hartman <gregkh@...e.de>,
	Andrew Morton <akpm@...ux-foundation.org>,
	"David S. Miller" <davem@...emloft.net>, Jiri Slaby <jslaby@...e.cz>,
	James Morris <jmorris@...ei.org>, kernel-hardening@...ts.openwall.com
Subject: Re: [PATCH v3 -resend] move RLIMIT_NPROC check from set_user() to
 do_execve_common()

On Mon, 8 Aug 2011 19:02:04 +0400 Vasiliy Kulikov <segoon@...nwall.com> wrote:

> The patch http://lkml.org/lkml/2003/7/13/226 introduced an RLIMIT_NPROC
> check in set_user() to check for NPROC exceeding via setuid() and
> similar functions.  Before the check there was a possibility to greatly
> exceed the allowed number of processes by an unprivileged user if the
> program relied on rlimit only.  But the check created new security
> threat: many poorly written programs simply don't check setuid() return
> code and believe it cannot fail if executed with root privileges.  So,
> the check is removed in this patch because of too often privilege
> escalations related to buggy programs.
> 
> The NPROC can still be enforced in the common code flow of daemons
> spawning user processes.  Most of daemons do fork()+setuid()+execve().
> The check introduced in execve() (1) enforces the same limit as in
> setuid() and (2) doesn't create similar security issues.
> 
> Neil Brown suggested to track what specific process has exceeded the
> limit by setting PF_NPROC_EXCEEDED process flag.  With the change only
> this process would fail on execve(), and other processes' execve()
> behaviour is not changed.
> 
> Solar Designer suggested to re-check whether NPROC limit is still
> exceeded at the moment of execve().  If the process was sleeping for
> days between set*uid() and execve(), and the NPROC counter step down
> under the limit, the defered execve() failure because NPROC limit was
> exceeded days ago would be unexpected.  If the limit is not exceeded
> anymore, we clear the flag on successful calls to execve() and fork().
> 
> The flag is also cleared on successful calls to set_user() as the limit
> was exceeded for the previous user, not the current one.
> 
> Similar check was introduced in -ow patches (without the process flag).
> 
> v3 - clear PF_NPROC_EXCEEDED on successful calls to set_user().
> 
> Reviewed-by: James Morris <jmorris@...ei.org>
> Signed-off-by: Vasiliy Kulikov <segoon@...nwall.com>

Acked-by: NeilBrown <neilb@...e.de>

I'm not 100% happy with this for reasons that have been mentioned, but there
is a real problem, and this is a real fix, and I think it is as good as we
are likely to be able to achieve.

Thanks for persisting.

NeilBrown



> ---
>  fs/exec.c             |   17 +++++++++++++++++
>  include/linux/sched.h |    1 +
>  kernel/cred.c         |    6 ++----
>  kernel/fork.c         |    1 +
>  kernel/sys.c          |   15 +++++++++++----
>  5 files changed, 32 insertions(+), 8 deletions(-)
> 
> diff --git a/fs/exec.c b/fs/exec.c
> index da80612..25dcbe5 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1459,6 +1459,23 @@ static int do_execve_common(const char *filename,
>  	struct files_struct *displaced;
>  	bool clear_in_exec;
>  	int retval;
> +	const struct cred *cred = current_cred();
> +
> +	/*
> +	 * We move the actual failure in case of RLIMIT_NPROC excess from
> +	 * set*uid() to execve() because too many poorly written programs
> +	 * don't check setuid() return code.  Here we additionally recheck
> +	 * whether NPROC limit is still exceeded.
> +	 */
> +	if ((current->flags & PF_NPROC_EXCEEDED) &&
> +	    atomic_read(&cred->user->processes) > rlimit(RLIMIT_NPROC)) {
> +		retval = -EAGAIN;
> +		goto out_ret;
> +	}
> +
> +	/* We're below the limit (still or again), so we don't want to make
> +	 * further execve() calls fail. */
> +	current->flags &= ~PF_NPROC_EXCEEDED;
>  
>  	retval = unshare_files(&displaced);
>  	if (retval)
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 20b03bf..4ac2c05 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1767,6 +1767,7 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
>  #define PF_DUMPCORE	0x00000200	/* dumped core */
>  #define PF_SIGNALED	0x00000400	/* killed by a signal */
>  #define PF_MEMALLOC	0x00000800	/* Allocating memory */
> +#define PF_NPROC_EXCEEDED 0x00001000	/* set_user noticed that RLIMIT_NPROC was exceeded */
>  #define PF_USED_MATH	0x00002000	/* if unset the fpu must be initialized before use */
>  #define PF_FREEZING	0x00004000	/* freeze in progress. do not account to load */
>  #define PF_NOFREEZE	0x00008000	/* this thread should not be frozen */
> diff --git a/kernel/cred.c b/kernel/cred.c
> index 174fa84..8ef31f5 100644
> --- a/kernel/cred.c
> +++ b/kernel/cred.c
> @@ -508,10 +508,8 @@ int commit_creds(struct cred *new)
>  		key_fsgid_changed(task);
>  
>  	/* do it
> -	 * - What if a process setreuid()'s and this brings the
> -	 *   new uid over his NPROC rlimit?  We can check this now
> -	 *   cheaply with the new uid cache, so if it matters
> -	 *   we should be checking for it.  -DaveM
> +	 * RLIMIT_NPROC limits on user->processes have already been checked
> +	 * in set_user().
>  	 */
>  	alter_cred_subscribers(new, 2);
>  	if (new->user != old->user)
> diff --git a/kernel/fork.c b/kernel/fork.c
> index e7ceaca..8e6b6f4 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -1111,6 +1111,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
>  		    p->real_cred->user != INIT_USER)
>  			goto bad_fork_free;
>  	}
> +	current->flags &= ~PF_NPROC_EXCEEDED;
>  
>  	retval = copy_creds(p, clone_flags);
>  	if (retval < 0)
> diff --git a/kernel/sys.c b/kernel/sys.c
> index a101ba3..dd948a1 100644
> --- a/kernel/sys.c
> +++ b/kernel/sys.c
> @@ -621,11 +621,18 @@ static int set_user(struct cred *new)
>  	if (!new_user)
>  		return -EAGAIN;
>  
> +	/*
> +	 * We don't fail in case of NPROC limit excess here because too many
> +	 * poorly written programs don't check set*uid() return code, assuming
> +	 * it never fails if called by root.  We may still enforce NPROC limit
> +	 * for programs doing set*uid()+execve() by harmlessly deferring the
> +	 * failure to the execve() stage.
> +	 */
>  	if (atomic_read(&new_user->processes) >= rlimit(RLIMIT_NPROC) &&
> -			new_user != INIT_USER) {
> -		free_uid(new_user);
> -		return -EAGAIN;
> -	}
> +			new_user != INIT_USER)
> +		current->flags |= PF_NPROC_EXCEEDED;
> +	else
> +		current->flags &= ~PF_NPROC_EXCEEDED;
>  
>  	free_uid(new->user);
>  	new->user = new_user;

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.