GRKERNSEC_BRUTE Exploit Bruteforcing Protection
This is another little grsecurity protection that can make an exploit developer’s work harder. As we can read from its configuration description…
config GRKERNSEC_BRUTE bool "Deter exploit bruteforcing" help If you say Y here, attempts to bruteforce exploits against forking daemons such as apache or sshd will be deterred. When a child of a forking daemon is killed by PaX or crashes due to an illegal instruction, the parent process will be delayed 30 seconds upon every subsequent fork until the administrator is able to assess the situation and restart the daemon. It is recommended that you also enable signal logging in the auditing section so that logs are generated when a process performs an illegal instruction.
this option will enable a feature that slows down the brute-forcing attempts on daemons that keep on spawning new processes when they crash. It’s obvious that this is quite common in exploit development. Brute-forcing exploits will attempt numerous times to exploit a vulnerability until they discover the right parameters and achieve code execution. Their failed attempts will usually end up in crashes but because of some daemons’ feature of forking this is no important issue.
So, if we move to grsecurity/grsec_sig.c we’ll see the following routine:
void gr_handle_brute_attach(struct task_struct *p) { #ifdef CONFIG_GRKERNSEC_BRUTE read_lock(&tasklist_lock); read_lock(&grsec_exec_file_lock); if (p->real_parent && p->real_parent->exec_file == p->exec_file) p->real_parent->brute = 1; read_unlock(&grsec_exec_file_lock); read_unlock(&tasklist_lock); #endif return; }
It’s clear that this is functional only if ‘CONFIG_GRKERNSEC_BRUTE’ option is enabled. If this is the case, it will check that its real parent process exists and that the parent’s executable file is the same as the current task’s one. This is done to ensure that we’re dealing with the same daemon’s executable and not a different one. Finally, it sets the ‘brute’ variable to ‘1’. Most people that remember ‘task_struct’ would have noticed that some of these variables aren’t part of the Linux kernel’s structure. That’s because they’re grsecurity specific and defined like this:
#ifdef CONFIG_GRKERNSEC /* grsecurity */ struct dentry *gr_chroot_dentry; struct acl_subject_label *acl; struct acl_role_label *role; struct file *exec_file; u16 acl_role_id; u8 acl_sp_role; u8 is_writable; u8 brute; u8 gr_is_chrooted; #endif
Back to grsecurity/grsec_sig.c we also find the following function:
void gr_handle_brute_check(void) { #ifdef CONFIG_GRKERNSEC_BRUTE if (current->brute) msleep(30 * 1000); #endif return; }
This one is very simple. It takes no argument and it just sleeps for 30 seconds if the current task’s ‘brute’ value is non-zero.
By now, you should probably understand how gr_handle_brute_attach() and gr_handle_brute_check() are used to avoid the brute-forcing… As simple as this:
long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; ... if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); gr_handle_brute_check(); if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; ... return nr; }
Which is from kernel/fork.c. So, fork will check the value of ‘brute’ before spawning a new process and gr_handle_brute_attach() is used in do_coredump() located at fs/exec.c
void do_coredump(long signr, int exit_code, struct pt_regs *regs) { struct core_state core_state; char corename[CORENAME_MAX_SIZE + 1]; ... clear_thread_flag(TIF_SIGPENDING); if (signr == SIGKILL || signr == SIGILL) gr_handle_brute_attach(current); ... fail: return; }
which is triggered when a process crashes. As you can read, if the process’ exit signal was either ‘SIGKILL’ (Killed Process) or ‘SIGILL’ (Illegal Instruction) it will invoke gr_handle_brute_attach() to handle it.
So, if a daemon exit because of an illegal instruction or it was killed (for example from stack-smashing protection), it will set its brute-force value to ‘1’ using gr_handle_brute_attach() and when it’ll attempt to fork the new process, do_fork() will check that brute-force value and sleep for 30 seconds if it’s set. Of course, the exploitation is still possible but that 30 seconds gap between spawning processes will make it more time consuming and less reliable. Also, as the description says, the aim here isn’t to stop the exploitation attempt but to slow it down in order to allow the system administrator handle the situation.
Leave a Reply