xorl %eax, %eax

GRKERNSEC_HIDESYM Hide Kernel Symbols

leave a comment »

Anyone with basic knowledge of kernel exploitation knows how important information gathering is to reliable exploitation. This protection hides the kernel symbols from various places that an attacker could use during information gathering in pre-exploitation stage. Also, as we can read here:

config GRKERNSEC_HIDESYM
	bool "Hide kernel symbols"
	help
	  If you say Y here, getting information on loaded modules, and
	  displaying all kernel symbols through a syscall will be restricted
	  to users with CAP_SYS_MODULE.  For software compatibility reasons,
	  /proc/kallsyms will be restricted to the root user.  The RBAC
	  system can hide that entry even from root.

	  This option also prevents leaking of kernel addresses through
	  several /proc entries.

	  Note that this option is only effective provided the following
	  conditions are met:
	  1) The kernel using grsecurity is not precompiled by some distribution
	  2) You have also enabled GRKERNSEC_DMESG
	  3) You are using the RBAC system and hiding other files such as your
	     kernel image and System.map.  Alternatively, enabling this option
	     causes the permissions on /boot, /lib/modules, and the kernel
	     source directory to change at compile time to prevent 
	     reading by non-root users.
	  If the above conditions are met, this option will aid in providing a
	  useful protection against local kernel exploitation of overflows
	  and arbitrary read/write vulnerabilities.

The ‘/proc/kallsyms’ is by default accessible only by root user. Of course, the prerequisites are that the kernel hasn’t been already compiled since this protection starts taking effect during the compilation of the kernel as we’ll see later in this post. Additionally, the ‘GRKERNSEC_DMESG‘ option should be enabled (you’ll see later why) and the grsecurity’s RBAC system is enabled to hide other important files that could leak important information to attackers.
The patch begins in the ‘Makefile’ that grsecurity creates to compile the kernel. This file is stored at grsecurity/Makefile and among others, includes this:

ifdef CONFIG_GRKERNSEC_HIDESYM
extra-y := grsec_hidesym.o
$(obj)/grsec_hidesym.o:
	@-chmod -f 500 /boot
	@-chmod -f 500 /lib/modules
	@-chmod -f 700 .
	@echo '  grsec: protected kernel image paths'
endif

So, if enabled it will also change the permissions of ‘/boot’, ‘/lib/modules’ and the current directory’s to be only accessible by their owner which in this case is root user. This will prevent attackers from using kernel image or System.map files to obtain various kernel symbols and kernel modules’ information from ‘/lib/modules’.
The next file patched is the include/linux/kallsyms.h header file where grsecurity patches it to look like this:

#define KSYM_NAME_LEN 128
#define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s]") + (KSYM_NAME_LEN - 1) + \
                         2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + 1)

struct module;

#if !defined(__INCLUDED_BY_HIDESYM) || !defined(CONFIG_KALLSYMS)
#if defined(CONFIG_KALLSYMS) && !defined(CONFIG_GRKERNSEC_HIDESYM)
/* Lookup the address for a symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name);

There is a pre-processor change here that instead of simply checking that ‘CONFIG_KALLSYMS’ is enabled, it will initially check that either ‘__INCLUDED_BY_HIDESYM’ or ‘CONFIG_KALLSYMS’ are not set. If this is the case, it will perform a second C pre-processor check that ‘CONFIG_KALLSYMS’ is enabled and ‘CONFIG_GRKERNSEC_HIDESYM’ is disabled. This means that in systems with grsecurity’s ‘GRKERNSEC_HIDESYM’ enabled the ‘kallsyms’ routines will return nothing.
The next patch is applied to kernel/configs.c file and specifically it’s the following…

static int __init ikconfig_init(void)
{
        struct proc_dir_entry *entry;

        /* create the current config file */
#if defined(CONFIG_GRKERNSEC_PROC_ADD) || defined(CONFIG_GRKERNSEC_HIDESYM)
#if defined(CONFIG_GRKERNSEC_PROC_USER) || defined(CONFIG_GRKERNSEC_HIDESYM)
	entry = proc_create("config.gz", S_IFREG | S_IRUSR, NULL,
			    &ikconfig_file_ops);
#elif defined(CONFIG_GRKERNSEC_PROC_USERGROUP)
	entry = proc_create("config.gz", S_IFREG | S_IRUSR | S_IRGRP, NULL,
			    &ikconfig_file_ops);
#endif
#else
        entry = proc_create("config.gz", S_IFREG | S_IRUGO, NULL,
                            &ikconfig_file_ops);
#endif

        if (!entry)
                return -ENOMEM;

        entry->size = kernel_config_data_size;

        return 0;
}

As you can read, this is affected by other protections that I will discuss in future posts. So, if either the protection we’re dealing with here, ‘GRKERNSEC_HIDESYM’ is set or ‘GRKERNSEC_PROC_ADD’ or ‘GRKERNSEC_PROC_USER’ are enabled, it will create the requested configuration file ‘config.gz’ with ‘S_IRUSR’ mode (readable only by the owner) instead of the default ‘S_IRUGO’ which translates to readable by owner, group and other users which is commonly referred to as ‘world readable’ file. In case of the ‘GRKERNSEC_PROC_USERGROUP’ which is out of the scope of this post it will make it readable to its owner and owner’s group.
Continuing to the patched files we have kernel/kallsyms.c that includes the following definition if the discussed protection is enabled.

#ifdef CONFIG_GRKERNSEC_HIDESYM
#define __INCLUDED_BY_HIDESYM 1
#endif
#include <linux/kallsyms.h>

This simple constant was previously used in ‘kallsyms’ header file and next we have another patch in s_show() function which is shown below.

static int s_show(struct seq_file *m, void *p)
{
        struct kallsym_iter *iter = m->private;

#ifdef CONFIG_GRKERNSEC_HIDESYM
	if (current_uid())
		return 0;
#endif

        /* Some debugging symbols have no name.  Ignore them. */
        if (!iter->name[0])
                return 0;

        if (iter->module_name[0]) {
                char type;

                /*
                 * Label it "global" if it is exported,
                 * "local" if not exported.
                 */
                type = iter->exported ? toupper(iter->type) :
                                        tolower(iter->type);
                seq_printf(m, "%0*lx %c %s\t[%s]\n",
                           (int)(2 * sizeof(void *)),
                           iter->value, type, iter->name, iter->module_name);
        } else
                seq_printf(m, "%0*lx %c %s\n",
                           (int)(2 * sizeof(void *)),
                           iter->value, iter->type, iter->name);
        return 0;
}

That simple patch will disallow any user apart from root into viewing modules’ information. This protection also adds some code at kernel/module.c file’s /proc initialization routine which is this:

static int __init proc_modules_init(void)
{
#ifndef CONFIG_GRKERNSEC_HIDESYM
#ifdef CONFIG_GRKERNSEC_PROC_USER
	proc_create("modules", S_IRUSR, NULL, &proc_modules_operations);
#elif defined(CONFIG_GRKERNSEC_PROC_USERGROUP)
	proc_create("modules", S_IRUSR | S_IRGRP, NULL, &proc_modules_operations);
#else
 	proc_create("modules", 0, NULL, &proc_modules_operations);
#endif
#else
	proc_create("modules", S_IRUSR, NULL, &proc_modules_operations);
#endif
        return 0;
}
module_init(proc_modules_init);

Once again, there are a few more protections taken into account here but the main concept is that if ‘GRKERNSEC_HIDESYM’ isn’t enabled it will create the ‘modules’ /proc file with permissions based on the rest of the grsecurity options. However, if it’s set it will not allow the creation of ‘/proc/modules’ that contain valuable information from an exploit developer’s point of view.
The next file that is patched is the kernel/time/timer_list.c.

/*
 * This allows printing both to /proc/timer_list and
 * to the console (on SysRq-Q):
 */
#define SEQ_printf(m, x...)                     \
 do {                                           \
        if (m)                                  \
                seq_printf(m, x);               \
        else                                    \
                printk(x);                      \
 } while (0)

static void print_name_offset(struct seq_file *m, void *sym)
{
#ifdef CONFIG_GRKERNSEC_HIDESYM
	SEQ_printf(m, "<%p>", NULL);
#else
        char symname[KSYM_NAME_LEN];

        if (lookup_symbol_name((unsigned long)sym, symname) < 0)
                SEQ_printf(m, "<%p>", sym);
        else
                SEQ_printf(m, "%s", symname);
#endif
}

I included the SEQ_printf() macro to understand the usage of the patched function. The patch is fairly simple here, it will print nothing in case of ‘HIDESYM’ being enabled. Actually, it will always print NULL pointer.
In the same source code file there is also a patch at print_base() which is used to print the timer’s base address as well as other information.

static void
print_base(struct seq_file *m, struct hrtimer_clock_base *base, u64 now)
{
#ifdef CONFIG_GRKERNSEC_HIDESYM
	SEQ_printf(m, "  .base:       %p\n", NULL);
#else
        SEQ_printf(m, "  .base:       %p\n", base);
#endif
        SEQ_printf(m, "  .index:      %d\n",
                        base->index);
        SEQ_printf(m, "  .resolution: %Lu nsecs\n",
                        (unsigned long long)ktime_to_ns(base->resolution));
        SEQ_printf(m,   "  .get_time:   ");
        print_name_offset(m, base->get_time);
        SEQ_printf(m,   "\n");
#ifdef CONFIG_HIGH_RES_TIMERS
        SEQ_printf(m, "  .offset:     %Lu nsecs\n",
                   (unsigned long long) ktime_to_ns(base->offset));
#endif
        SEQ_printf(m,   "active timers:\n");
        print_active_timers(m, base, now);
}

So basically, it will dump almost the entire structure passed to it in the default Linux kernel. Using grsecurity it limits this to just the ‘base’ member’s address of that structure and in case of ‘HIDESYM’ it will print nothing at all.
The discussed protection also patches kernel/time/timer_stats.c file like this:

static void print_name_offset(struct seq_file *m, unsigned long addr)
{
#ifdef CONFIG_GRKERNSEC_HIDESYM
	seq_printf(m, "<%p>", NULL);
#else
        char symname[KSYM_NAME_LEN];

        if (lookup_symbol_name(addr, symname) < 0)
                seq_printf(m, "<%p>", (void *)addr);
        else
                seq_printf(m, "%s", symname);
#endif
}

Which is the exact same approach that was used before. It will print a NULL pointer instead of the symbol’s address or name. Following up is lib/Kconfig.debug file.

config LATENCYTOP
        bool "Latency measuring infrastructure"
        depends on HAVE_LATENCYTOP_SUPPORT
        depends on DEBUG_KERNEL
        depends on STACKTRACE_SUPPORT
        depends on PROC_FS
        depends on !GRKERNSEC_HIDESYM
        select FRAME_POINTER if !MIPS && !PPC && !S390 && !MICROBLAZE
        select KALLSYMS
        select KALLSYMS_ALL
        select STACKTRACE
        select SCHEDSTATS
        select SCHED_DEBUG
        help
          Enable this option if you want to use the LatencyTOP tool
          to find out which userspace is blocking on what kernel operations.

The dependencies of the latency measuring infrastructure now include that ‘HIDESYM’ is not enabled. Since this functionality requires a lot of debugging information having ‘HIDESYM’ will be useless. The next file patched is lib/vsprintf.c that initially includes a constant definition.

#ifdef CONFIG_GRKERNSEC_HIDESYM
#define __INCLUDED_BY_HIDESYM 1
#endif
 #include <stdarg.h>
 #include <linux/module.h>

Then, if we have a look at symbol_string() function we’ll see:

static noinline_for_stack
char *symbol_string(char *buf, char *end, void *ptr,
                    struct printf_spec spec, char ext)
{
        unsigned long value = (unsigned long) ptr;
#ifdef CONFIG_KALLSYMS
        char sym[KSYM_SYMBOL_LEN];
        if (ext != 'f' && ext != 's' && ext != 'a')
                sprint_symbol(sym, value);
        else
                kallsyms_lookup(value, NULL, NULL, NULL, sym);

        return string(buf, end, sym, spec);
#else
        spec.field_width = 2 * sizeof(void *);
        spec.flags |= SPECIAL | SMALL | ZEROPAD;
        spec.base = 16;

        return number(buf, end, value, spec);
#endif
}

that there’s an additional check for ‘a’ handles that lead to kallsyms_lookup() and a few lines below we can read why this is included…

  * - 'S' For symbolic direct pointers with offset
  * - 's' For symbolic direct pointers without offset
  * - 'A' For symbolic direct pointers with offset approved for use with GRKERNSEC_HIDESYM
  * - 'a' For symbolic direct pointers without offset approved for use with GRKERNSEC_HIDESYM
  * - 'R' For decoded struct resource, e.g., [mem 0x0-0x1f 64bit pref]

and this leads us to the function below that comment section which is patched to work with such symbols.

static noinline_for_stack
char *pointer(const char *fmt, char *buf, char *end, void *ptr,
              struct printf_spec spec)
{
        if (!ptr)
                return string(buf, end, "(null)", spec);

        switch (*fmt) {
     ...
                /* Fallthrough */
        case 'S':
        case 's':
#ifdef CONFIG_GRKERNSEC_HIDESYM
		break;
#else
		return symbol_string(buf, end, ptr, spec, *fmt);
#endif
	case 'A':
	case 'a':
                return symbol_string(buf, end, ptr, spec, *fmt);
        case 'R':
        case 'r':
                return resource_string(buf, end, ptr, spec, fmt);
     ...
        return number(buf, end, (unsigned long) ptr, spec);
}

So, if ‘HIDESYM’ is enabled it will not allow any request on direct pointers with or without offset by immediately breaking out of the ‘switch’ statement. The ATM /proc interface’s code located at net/atm/proc.c is also patched to avoid this printing:

static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc)
{
        struct sock *sk = sk_atm(vcc);

#ifdef CONFIG_GRKERNSEC_HIDESYM
	seq_printf(seq, "%p ", NULL);
#else
 	seq_printf(seq, "%p ", vcc);
#endif

        seq_printf(seq, "%p ", vcc);
        if (!vcc->dev)
                seq_printf(seq, "Unassigned    ");
     ...
        seq_printf(seq, " %04lx  %5d %7d/%7d %7d/%7d [%d]\n",
                   vcc->flags, sk->sk_err,
                   sk_wmem_alloc_get(sk), sk->sk_sndbuf,
                   sk_rmem_alloc_get(sk), sk->sk_rcvbuf,
                   atomic_read(&sk->sk_refcnt));
}

that was normally printing almost the entire ATM VCC structure passed to it. Normally, grsecurity will limit this to just the pointer of that structure and with ‘HIDESYM’ enabled it will disable it by always printing a NULL pointer.
Networking code has similar possible information leaks, the next one is located at net/ipv4/inet_diag.c and it’s shown here:

static int inet_csk_diag_fill(struct sock *sk,
                              struct sk_buff *skb,
                              int ext, u32 pid, u32 seq, u16 nlmsg_flags,
                              const struct nlmsghdr *unlh)
{
        const struct inet_sock *inet = inet_sk(sk);
        const struct inet_connection_sock *icsk = inet_csk(sk);
        struct inet_diag_msg *r;
     ...
        r->id.idiag_if = sk->sk_bound_dev_if;

#ifdef CONFIG_GRKERNSEC_HIDESYM
	r->id.idiag_cookie[0] = 0;
	r->id.idiag_cookie[1] = 0;
#else
 	r->id.idiag_cookie[0] = (u32)(unsigned long)sk;
 	r->id.idiag_cookie[1] = (u32)(((unsigned long)sk >> 31) >> 1);
#endif
     ...
rtattr_failure:
nlmsg_failure:
        nlmsg_trim(skb, b);
        return -EMSGSIZE;
}

While filling the information structure for the given socket it will initialize the socket identity’s cookie with socket pointer. To avoid leaks of this information using ‘HIDESYM’ it will always set it to zero. Similar patch is added to inet_twsk_diag_fill() and inet_diag_fill_req() too.
Inside IPv4 code there is another patch at inet_diag_get_exact() function which is shown here.

static int inet_diag_get_exact(struct sk_buff *in_skb,
                               const struct nlmsghdr *nlh)
{
        int err;
        struct sock *sk;
     ...
        if (sk == NULL)
                goto unlock;

#ifndef CONFIG_GRKERNSEC_HIDESYM
        err = -ESTALE;
        if ((req->id.idiag_cookie[0] != INET_DIAG_NOCOOKIE ||
             req->id.idiag_cookie[1] != INET_DIAG_NOCOOKIE) &&
            ((u32)(unsigned long)sk != req->id.idiag_cookie[0] ||
             (u32)((((unsigned long)sk) >> 31) >> 1) != req->id.idiag_cookie[1]))
                goto out;
#endif
     ...
        return err;
}

There is a cookie test in case of socket that is in ‘-ESTALE’ state (meaning “Stale NFS file handle”). Since those are changed by grsecurity’s protection they will not be executed if ‘HIDESYM’ is enabled. Next we have net/ipv4/tcp_ipv4.c file…

static void get_openreq4(struct sock *sk, struct request_sock *req,
                         struct seq_file *f, int i, int uid, int *len)
{
        const struct inet_request_sock *ireq = inet_rsk(req);
        int ttd = req->expires - jiffies;

        seq_printf(f, "%4d: %08X:%04X %08X:%04X"
                " %02X %08X:%08X %02X:%08lX %08X %5d %8d %u %d %p%n",
                i,
                ireq->loc_addr,
                ntohs(inet_sk(sk)->inet_sport),
                ireq->rmt_addr,
                ntohs(ireq->rmt_port),
                TCP_SYN_RECV,
                0, 0, /* could print option size, but that is af dependent. */
                1,    /* timers active (only the expire timer) */
                jiffies_to_clock_t(ttd),
                req->retrans,
                uid,
                0,  /* non standard timer */
                0, /* open_requests have no inode */
                atomic_read(&sk->sk_refcnt),
#ifdef CONFIG_GRKERNSEC_HIDESYM
		NULL,
#else
                req,
#endif
                len);
}

That’s a lot of information but there is also a pointer location which is restricted by ‘HIDESYM’ protection. It’s the request socket’s pointer. Similar patches are applied to:
– get_tcp4_sock() to hide socket pointer
– get_timewait4_sock() to hide time-wait socket’s pointer
– udp4_format_sock() to hide socket’s pointer
– raw6_sock_seq_show() to hide socket’s pointer
– get_openreq6() to hide request structure’s pointer
– get_tcp6_sock() to hide socket pointer
– get_timewait6_sock() to hide time-wait socket’s pointer
– udp6_sock_seq_show() to hide socket’s pointer
– pfkey_seq_show() to hide socket’s pointer
– netlink_seq_show() to hide socket’s pointer and callback’s pointer
– packet_seq_show() to hide socket’s pointer
– pn_sock_seq_show() to hide socket’s pointer
– sctp_eps_seq_show() to hide socket and end-point SCTP structure
– sctp_assocs_seq_show() to hide socket and SCTP association
– unix_seq_show() to hide socket
After those networking code patches we have another patch at drivers/message/fusion/mptbase.c which is Fusion MPT base driver.

static int mpt_iocinfo_proc_show(struct seq_file *m, void *v)
{
        MPT_ADAPTER     *ioc = m->private;
        char             expVer[32];
        int              sz;
        int              p;

        mpt_get_fw_exp_ver(expVer, ioc);

     ...
        seq_printf(m, "  MaxChainDepth = 0x%02x frames\n", ioc->facts.MaxChainDepth);
        seq_printf(m, "  MinBlockSize = 0x%02x bytes\n", 4*ioc->facts.BlockSize);

#ifdef CONFIG_GRKERNSEC_HIDESYM
	seq_printf(m, "  RequestFrames @ 0x%p (Dma @ 0x%p)\n", NULL, NULL);
#else
 	seq_printf(m, "  RequestFrames @ 0x%p (Dma @ 0x%p)\n",
 					(void *)ioc->req_frames, (void *)(ulong)ioc->req_frames_dma);
#endif

        /*
         *  Rounding UP to nearest 4-kB boundary here...
         */
     ...
        return 0;
}

The default behavior here was to print the two pointers of ‘ioc->req_frames’ and ‘ioc->req_frames_dma’. To avoid leaking this information ‘HIDESYM’ will always print NULL pointers. The next changed code in the Linux kernel for the discussed patch resides at fs/proc/array.c where the following routine is placed.

static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
                        struct pid *pid, struct task_struct *task, int whole)
{
        unsigned long vsize, eip, esp, wchan = ~0UL;
        long priority, nice;
        int tty_pgrp = -1, tty_nr = 0;
     ...
#ifdef CONFIG_GRKERNSEC_HIDESYM
	wchan = 0;
	eip =0;
	esp =0;
#endif
     ...
        seq_printf(m, "%d (%s) %c %d %d %d %d %d %u %lu \
%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
%lu %lu %lu %lu %lu %lu %lu %lu %d %d %u %u %llu %lu %ld\n",
                pid_nr_ns(pid, ns),
                tcomm,
                state,
                ppid,
                pgid,
                sid,
                tty_nr,
                tty_pgrp,
                task->flags,
                min_flt,
                cmin_flt,
                maj_flt,
                cmaj_flt,
                cputime_to_clock_t(utime),
                cputime_to_clock_t(stime),
                cputime_to_clock_t(cutime),
                cputime_to_clock_t(cstime),
                priority,
                nice,
                num_threads,
                start_time,
                vsize,
                mm ? get_mm_rss(mm) : 0,
                rsslim,
                mm ? mm->start_code : 0,
                mm ? mm->end_code : 0,
                (permitted && mm) ? mm->start_stack : 0,
                esp,
                eip,
                /* The signal information here is obsolete.
                 * It must be decimal for Linux 2.0 compatibility.
                 * Use /proc/#/status for real-time signals.
                 */
                task->pending.signal.sig[0] & 0x7fffffffUL,
                task->blocked.sig[0] & 0x7fffffffUL,
                sigign      .sig[0] & 0x7fffffffUL,
                sigcatch    .sig[0] & 0x7fffffffUL,
                wchan,
                0UL,
                0UL,
                task->exit_signal,
                task_cpu(task),
                task->rt_priority,
                task->policy,
                (unsigned long long)delayacct_blkio_ticks(task),
                cputime_to_clock_t(gtime),
                cputime_to_clock_t(cgtime));

        if (mm)
                mmput(mm);
        return 0;
}

It’s quite clear that this function provides countless information from an attacker’s point of view but giving the addresses of instruction pointer (EIP), stack pointer (ESP) and ‘wchan’ (kernel function where the process is sleeping) it can make an attacker’s work easier and by far more reliable than guessing or brute-forcing those addresses. To fix this, ‘HIDESYM’ will zero out those values before reaching the seq_printf() call.
We then move to fs/proc/base.c in order to have a look at this:

#if defined(CONFIG_KALLSYMS) && !defined(CONFIG_GRKERNSEC_HIDESYM)
/*
 * Provides a wchan file via kallsyms in a proper one-value-per-file format.
 * Returns the resolved symbol.  If that fails, simply return the address.
 */
static int proc_pid_wchan(struct task_struct *task, char *buffer)
{
        unsigned long wchan;
    ...
}
#endif /* CONFIG_KALLSYMS */

#if defined(CONFIG_STACKTRACE) && !defined(CONFIG_GRKERNSEC_HIDESYM)

#define MAX_STACK_TRACE_DEPTH   64

static int proc_pid_stack(struct seq_file *m, struct pid_namespace *ns,
                          struct pid *pid, struct task_struct *task)
{
    ...
}
#endif

The first thing to notice here is that proc_pid_wchan() will only be enabled if ‘HIDESYM’ grsecurity protection is disabled and Linux kernel’s ‘CONFIG_KALLSYMS’ is enabled. This routine is used to provide the ‘wchan’ information for the given task on the /proc filesystem. The same approach is used in the proc_pid_stack() that provides the task’s stack trace under /proc filesystem.
In the same source code file we have a few more patches. Here is the next one…

/*
 * Thread groups
 */
static const struct file_operations proc_task_operations;
static const struct inode_operations proc_task_inode_operations;

static const struct pid_entry tgid_base_stuff[] = {
    ...
#ifdef CONFIG_SECURITY
        DIR("attr",       S_IRUGO|S_IXUGO, proc_attr_dir_inode_operations, proc_attr_dir_operations),
#endif
#ifdef defined(CONFIG_KALLSYMS) && !defined(CONFIG_GRKERNSEC_HIDESYM)
        INF("wchan",      S_IRUGO, proc_pid_wchan),
#endif
#ifdef defined(CONFIG_STACKTRACE) && !defined(CONFIG_GRKERNSEC_HIDESYM)
        ONE("stack",      S_IRUSR, proc_pid_stack),
#endif
    ...
};

The files of /proc for the previously discussed routines are put inside the same C pre-processor conditions to ensure that they will only be created if ‘HIDESYM’ is disabled and ‘KALLSYMS’ and/or ‘STACKTRACE’ are/is enabled for each of the two funtions respectively.
A few lines below we also have:

/*
 * Tasks
 */
static const struct pid_entry tid_base_stuff[] = {
    ...
#if defined(CONFIG_KALLSYMS) && !defined(CONFIG_GRKERNSEC_HIDESYM)
        INF("wchan",     S_IRUGO, proc_pid_wchan),
#endif
#if defined(CONFIG_STACKTRACE) && !defined(CONFIG_GRKERNSEC_HIDESYM)
        ONE("stack",      S_IRUSR, proc_pid_stack),
#endif
    ...
};

Which is the equivalent code for tasks instead of thread groups. The final file being patched by ‘GRKERNSEC_HIDESYM’ is the fs/proc/kcore.c file.

static int open_kcore(struct inode *inode, struct file *filp)
{
#if defined(CONFIG_GRKERNSEC_PROC_ADD) || defined(CONFIG_GRKERNSEC_HIDESYM)
	return -EPERM;
#endif
        if (!capable(CAP_SYS_RAWIO))
                return -EPERM;
        if (kcore_need_update)
                kcore_update_ram();
        if (i_size_read(inode) != proc_root_kcore->size) {
                mutex_lock(&inode->i_mutex);
                i_size_write(inode, proc_root_kcore->size);
                mutex_unlock(&inode->i_mutex);
        }
        return 0;
}

This function is responsible for opening ‘/proc/kcore’ which is the file that has direct access to computer’s memory. Normally, only processes with ‘CAP_SYS_RAWIO’ (System Raw I/O) capability are able to view its contents but for security purposes ‘HIDESYM’ will always return ‘-EPERM’ (Permission Denied) to any process attempting to open it since there might be vulnerable applications with the required POSIX capability.

Written by xorl

November 20, 2010 at 13:51

Posted in grsecurity, linux, security

Leave a comment