xorl %eax, %eax

GRKERNSEC_KERN_LOCKOUT Active Kernel Exploit Response

leave a comment »

This is a brand new feature of “Address Space Protection” that grsecurity offers. Its configuration option is very clear and it is implemented by adding just two new routines in the existing patch.

config GRKERNSEC_KERN_LOCKOUT
	bool "Active kernel exploit response"
	depends on X86
	help
	  If you say Y here, when a PaX alert is triggered due to suspicious
	  activity in the kernel (from KERNEXEC/UDEREF/USERCOPY)
	  or an OOPs occurs due to bad memory accesses, instead of just
	  terminating the offending process (and potentially allowing
	  a subsequent exploit from the same user), we will take one of two
	  actions:
	   If the user was root, we will panic the system
	   If the user was non-root, we will log the attempt, terminate
	   all processes owned by the user, then prevent them from creating
	   any new processes until the system is restarted
	  This deters repeated kernel exploitation/bruteforcing attempts
	  and is useful for later forensics.

First of all, the ‘user_struct’ at include/linux/sched.h was updated to include two new members that will be used to keep track of the banned users. Here is the code snippet that shows the newly added members.

/*
 * Some day this will be a full-fledged user tracking system..
 */
struct user_struct {
   ...
        struct key *session_keyring;    /* UID's default session keyring */
#endif
 
#if defined(CONFIG_GRKERNSEC_KERN_LOCKOUT) || defined(CONFIG_GRKERNSEC_BRUTE)
	unsigned int banned;
	unsigned long ban_expires;
#endif
   ...
};

Next we can have a look at grsecurity/grsec_sig.c to see the first function which is responsible for handling the banned users.

int gr_process_user_ban(void)
{
#if defined(CONFIG_GRKERNSEC_KERN_LOCKOUT) || defined(CONFIG_GRKERNSEC_BRUTE)
	if (unlikely(current->cred->user->banned)) {
		struct user_struct *user = current->cred->user;
		if (user->ban_expires != ~0UL && time_after_eq(get_seconds(), user->ban_expires)) {
			user->banned = 0;
			user->ban_expires = 0;
			free_uid(user);
		} else
			return -EPERM;
	}
#endif
	return 0;
}

What it does is checking if the user is banned and if this is the case, wait for ‘user->ban_expires’ to reset its status. Of course, this does not apply to users with values of ‘~0UL’ in ‘ban_expires’ variable. Those users will be banned until the system is restarted.

The next routine also located in the same source code file is this one.

void gr_handle_kernel_exploit(void)
{
#ifdef CONFIG_GRKERNSEC_KERN_LOCKOUT
	const struct cred *cred;
	struct task_struct *tsk, *tsk2;
	struct user_struct *user;
	uid_t uid;

	if (in_irq() || in_serving_softirq() || in_nmi())
		panic("grsec: halting the system due to suspicious kernel crash caused in interrupt context");

	uid = current_uid();

	if (uid == 0)
		panic("grsec: halting the system due to suspicious kernel crash caused by root");
	else {
		/* kill all the processes of this user, hold a reference
		   to their creds struct, and prevent them from creating
		   another process until system reset
		*/
		printk(KERN_ALERT "grsec: banning user with uid %u until system restart for suspicious kernel crash\n", uid);
		/* we intentionally leak this ref */
		user = get_uid(current->cred->user);
		if (user) {
			user->banned = 1;
			user->ban_expires = ~0UL;
		}

		read_lock(&tasklist_lock);
		do_each_thread(tsk2, tsk) {
			cred = __task_cred(tsk);
			if (cred->uid == uid)
				gr_fake_force_sig(SIGKILL, tsk);
		} while_each_thread(tsk2, tsk);
		read_unlock(&tasklist_lock);
	}
#endif
}

So, if this is called in the context of an interrupt (IRQ, SoftIRQ or NMI) or the current user is root, it will immediately invoke panic() to halt the system and avoid any possible further exploitation of a kernel vulnerability. In any other case it will log the event and ban that user by updating the ‘user->banned’ and ‘user->ban_expires’ members of the ‘user_struct’ structure. The final ‘while_each_thread’ loop will use gr_fake_force_sig() which is shown below to terminate (by sending kill signal) every task owned by the user who triggered the event.

#ifdef CONFIG_GRKERNSEC
extern int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t);

int gr_fake_force_sig(int sig, struct task_struct *t)
{
	unsigned long int flags;
	int ret, blocked, ignored;
	struct k_sigaction *action;

	spin_lock_irqsave(&t->sighand->siglock, flags);
	action = &t->sighand->action[sig-1];
	ignored = action->sa.sa_handler == SIG_IGN;
	blocked = sigismember(&t->blocked, sig);
	if (blocked || ignored) {
		action->sa.sa_handler = SIG_DFL;
		if (blocked) {
			sigdelset(&t->blocked, sig);
			recalc_sigpending_and_wake(t);
		}
	}
	if (action->sa.sa_handler == SIG_DFL)
		t->signal->flags &= ~SIGNAL_UNKILLABLE;
	ret = specific_send_sig_info(sig, SEND_SIG_PRIV, t);

	spin_unlock_irqrestore(&t->sighand->siglock, flags);

	return ret;
}
#endif

This routine will send the requested signal to the process.

So, now to the actual patching, the first patched code is the __kprobes oops_end() routine located at arch/x86/kernel/dumpstack.c file.

void __kprobes oops_end(unsigned long flags, struct pt_regs *regs, int signr)
{
   ...
 	if (panic_on_oops)
 		panic("Fatal exception");

	gr_handle_kernel_exploit();

	do_group_exit(signr);
}

This is triggered at the last step of a kernel OOPS. Consequently, it’s an ideal location to place this protection. Next we have the ‘execve’ routines that are invoked for spawning new processes. Specifically, the compat_do_execve() you see here from fs/compat.c file.

/*
 * compat_do_execve() is mostly a copy of do_execve(), with the exception
 * that it processes 32 bit argv and envp pointers.
 */
int compat_do_execve(char * filename,
        compat_uptr_t __user *argv,
        compat_uptr_t __user *envp,
        struct pt_regs * regs)
{
   ...
 	bprm->interp = filename;
 
	if (gr_process_user_ban()) {
		retval = -EPERM;
		goto out_file;
	}
   ...
out_ret:
        return retval;
}

Which is where it checks if the user is banned. Of course, similar check is also included in the do_execve() system call from fs/exec.c.

/*
 * sys_execve() executes a new program.
 */
int do_execve(const char * filename,
        const char __user *const __user *argv,
        const char __user *const __user *envp,
        struct pt_regs * regs)
{
   ...
 	bprm->interp = filename;
 
	if (gr_process_user_ban()) {
		retval = -EPERM;
		goto out_file;
	}
   ...
out_ret:
        return retval;
}

Finally, the pax_report_usercopy() is updated to handle the possible attacks using the new locking-out feature.

	
void pax_report_usercopy(const void *ptr, unsigned long len, bool to, const char *type)
{
	if (current->signal->curr_ip)
		printk(KERN_ERR "PAX: From %pI4: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
			&current->signal->curr_ip, to ? "leak" : "overwrite", to ? "from" : "to", ptr, type ? : "unknown", len);
	else
		printk(KERN_ERR "PAX: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
			to ? "leak" : "overwrite", to ? "from" : "to", ptr, type ? : "unknown", len);

	dump_stack();
	gr_handle_kernel_exploit();
	do_group_exit(SIGKILL);
}

Written by xorl

April 27, 2011 at 22:43

Posted in grsecurity, linux, security

Leave a comment