Bug 1213478 - VUL-0: open-vm-tools: vmware-user-suid-wrapper allows any user to get an open file descriptor for /dev/uinput and /run/vmblock-fuse/dev
Summary: VUL-0: open-vm-tools: vmware-user-suid-wrapper allows any user to get an open...
Status: RESOLVED DUPLICATE of bug 1216433
Alias: None
Product: SUSE Security Incidents
Classification: Novell Products
Component: Audits (show other bugs)
Version: unspecified
Hardware: Other Other
: P3 - Medium : Normal
Target Milestone: ---
Assignee: Kirk Allan
QA Contact: Security Team bot
URL: https://smash.suse.de/issue/372980/
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-07-19 12:13 UTC by Matthias Gerstner
Modified: 2023-10-27 07:04 UTC (History)
4 users (show)

See Also:
Found By: ---
Services Priority:
Business Priority:
Blocker: ---
Marketing QA Status: ---
IT Deployment: ---


Attachments
generic standalone exploit (8.46 KB, text/x-c)
2023-07-24 11:35 UTC, Matthias Gerstner
Details
simple graphical environment standalone exploit (6.62 KB, text/x-c)
2023-07-24 11:35 UTC, Matthias Gerstner
Details
making use of the /dev/uinput file descriptor (2.95 KB, text/x-c)
2023-07-24 13:54 UTC, Matthias Gerstner
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Matthias Gerstner 2023-07-19 12:13:02 UTC
+++ This bug was initially created as a clone of Bug #1204311

The setuid binary vmware-user-suid-wrapper caught my eye in the process of
reviewing X11 autostart entries. I've never looked closer into this binary and
the last review happened a long time ago.

At first the code for program looks pretty straight forward. It opens
/dev/uinput and/or /run/vmblock-fuse/dev as root, drops privileges properly to
the unpriviled user and then runs vmtoolsd in the background, passing it the
open file descriptors.

The issue that is there is the `execve()` part to run vmtoolsd. During this
the suid-dumpable attribute of the process is reset to 1. This means that
other processes running under the real UID can now ptrace() vmtoolsd or
perform other interesting operations to manipulate it.

I first tried to use `gdb` to inject different code into vmtoolsd. An even
easier approach is to obtain a pidfd and then use the new pidfd_getfd() to
simply get a copy of the open file descriptor.

The impact is not fully clear though. /dev/uinput is usually not accessible to
unprivileged users. This way the unprivileged user can create arbitrary input
devices. For the vmblock-fuse/dev I don't really know what the impact is,
because I don't have a working vmware environment to look into it. The device
file seems to come from the vmhgfs-fuse daemon which only runs in a vmware
environment.

I successfully tested a reproducer for snatching /dev/uinput from vmtoolsd by
building a slightly patched version of vmware-user-suid-wrapper that continues
operating even if there is no vmware environment around.
Comment 3 Matthias Gerstner 2023-07-19 12:27:18 UTC
Created attachment 868316 [details]
C++ exploit program
Comment 4 Matthias Gerstner 2023-07-19 12:42:59 UTC
In attachment 868316 [details] there is a first working version of the exploit to snatch
the /dev/uinput FD from vmtoolsd. It's a version based on my libcosmos
library, so to build:

    zypper in scons
    git clone https://github.com/gerstner-hub/libcosmos.git
    cd libcosmos
    scons libtype=static install -j5
    g++ /path/to/vmware-get-fd.cxx ./install/lib64/libcosmos.a -I./install/include -ovmware-get-fd

Then in one shell run vmware-user-suid-wrapper in a loop:

    # /dev/uinput is only opened if the setuid program believes we're running on
    # Wayland, so make sure this happens
    export XDG_SESSION_TYPE=wayland
    while true; do vmware-user-suid-wrapper; sleep 0.25; done

This is just to give the exploit program a couple of chances to win the race
to grab the file descriptor from vmtoolsd. Note that this works only if you
actually run it in a VMWare guest or use the patched version which you can
find in my branched OBS package here [1]. There is a slim chance that is also
works without the patched version, but vmtools will quickly exit if it detects
no vmware environment, so the race window will be pretty small.

In another shell then run `vmware-get-fd` a couple of times. On success it
will run a new sub-shell with the snatched /dev/uinput file descriptor already
open (check /proc/self/fd).

For upstream I will likely provide a pure C version of the exploit, but this
will be less fun to write and take more time.

[1]: home:mgerstner:branches:Virtualization:VMware/open-vm-tools
Comment 5 Matthias Gerstner 2023-07-24 11:35:24 UTC
Created attachment 868396 [details]
generic standalone exploit
Comment 6 Matthias Gerstner 2023-07-24 11:35:43 UTC
Created attachment 868397 [details]
simple graphical environment standalone exploit
Comment 7 Matthias Gerstner 2023-07-24 11:43:21 UTC
I have a better overview of this security issue by now. I reproduced the
exploit in two ways:

- running inside an actual VMWare guest in a graphical environment. In such an
  environment `vmtoolsd` will stay running in the background, owning the
  privileged file descriptors. Getting a copy of the file descriptors in such
  a scenario is straight forward and always succeeds as is shown in the
  program in attachment 868397 [details].
- running on an arbitrary system where vmware-user-suid-wrapper is available.
  Here the time window for the exploit is small. The suid-wrapper will still
  start vmtoolsd, it will quickly detect it's not running as a vmware guest,
  though, and exit. The program in attachment 868396 [details] attempts to win this race
  condition. In my tests the exploit was quickly successful. This shows the
  problem can also affect non-vmware environments if the setuid-root program
  is installed.

Regarding the file in /run/vmblock-fuse/dev its purpose is documented here:

https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/vmblock-fuse/design.txt

The fuse file system is used for implementing folders shared with the VMWare
host. The dev file is a command control file which allows to write arbitrary
(raw binary) blocks to the shared folders. This means the impact is roughly
this:

- writing to shared folders operated on by other users in the guest system.
- possibly writing arbitrary data to the shared folder(s), possibly even
  extending to code execution if e.g. a malicious file is placed there which
  gets executed by unsuspecting users.
- corrupting existing data in shared folders.

I am still investigating the impact of the /dev/uinput file. I will then
assemble a complete report for upstream and start the coordinated disclosure
process.
Comment 8 Matthias Gerstner 2023-07-24 13:54:15 UTC
Created attachment 868401 [details]
making use of the /dev/uinput file descriptor
Comment 9 Matthias Gerstner 2023-07-24 13:57:39 UTC
In attachment 868401 [details] you can find a PoC program that shows how owning a valid
file descriptor for /dev/uinput can be used for synthesysing arbitrary input
in local text or graphical sessions.

Thus this file descriptor leak is pretty severe and could be used to escalate
privileges when a component with little privileges is already compromised. It
could also be used on a multi user machine to have an evil program dormant
that waits for somebody else to login and then produces exploit input.

With this my analysis of the impact is complete. I will assemble a formal
report for upstream tomorrow.
Comment 11 Matthias Gerstner 2023-07-26 07:44:13 UTC
This is the formal report that I have sent privately to security@vmware.com
yesterday:

Introduction
============

During a routine review of the setuid-root binary "vmware-user-suid-wrapper"
from the open-vm-tools [1] repository I discovered the vulnerability described
in this report. The version under review was open-vm-tools version 12.2.0. The
setuid-root binary's source code in the open-vm-tools repository did not
change since version 10.3.0 (released in 2018), however, so likely most
current installations of open-vm-tools are affected by this finding.

Behaviour of vmware-user-suid-wrapper
=====================================

On first look the vmware-user-suid-wrapper seems to be small and harmless:

- it opens /dev/uinput as root, if it believes to be running on Wayland. The
  latter is determined by inspecting the value of the environment variable
  `XDG_SESSION_TYPE`, checking whether it is set to "wayland".
- it opens /var/run/vmblock-fuse/dev, if existing, as `root`.
- it permanently drops all privileges to the real (unprivileged) user and
  group ids and executes /usr/bin/vmtoolsd, inheriting to it any of the
  previously opened file descriptors.
- the new `vmtoolsd` process will inspect the environment, e.g. check whether
  the current host is running in a vmware guest environment and whether a
  graphical session is available. If one of these is not fulfilled then the
  process terminates quickly again. On success the daemon keeps running,
  providing its services, keeping the privileged file descriptors open.

So it seems everything is in order, the program opens up to two privileged
files, drops privileges and passes the open files on to `vmtoolsd` to use them
in the calling user's context.

The Vulnerability
=================

The (somewhat surprising) problem here is the combination of dropping
privileges to the real uid / gid and the following `execve()` to execute
the non-setuid program `vmtoolsd`. During the `execve()` the process's
"dumpable" attribute is reset to the value of 1.

From the man page `prctl(5)` we can learn the following about a process's
dumpable attribute:

    Normally, the "dumpable" attribute is set to 1. However, it is reset to
    the current value contained in the file /proc/sys/fs/suid_dumpable (which by
    default has the value 0), in the following circumstances:
    
    [...]
    
    - The process executes (execve(2)) a set-user-ID or set-group-ID program,
      resulting in a change of either the effective user ID or the effective
      group ID.
    
    [...]
    
    Processes that are not dumpable can not be attached via ptrace(2)
    PTRACE_ATTACH; see ptrace(2) for further details.

On most Linux distributions the global `suid_dumpable` setting is set either
to 0 (setuid programs may not dump core at all) or 2 (setuid programs may dump
core but only in safe file system locations). Consequently when
`vmware-user-suid-wrapper` runs, its dumpable attribute is set to 2 on openSUSE
Tumbleweed, which I have been using while researching this issue. However
after the `execve()` this changes, as is also documented in the `execve(2)`
man page:

    The following Linux-specific process attributes are also not preserved
    during an execve():
    
    - The process's "dumpable" attribute is set to the value 1, unless a
      set-user-ID program, a set-group-ID program, or a program with
      capabilities is being executed, [...].

Consequently when `vmtoolsd` is executed with dropped privileges, the
process's "dumpable" attribute will be reset to 1.

The problem with this is that the unprivileged user that originally invoked
`vmware-user-suid-wrapper` now is allowed to `ptrace()` the `vmtoolsd` process
along with a number of other operations that have not been allowed on the
setuid-root process before.

The interesting resources that `vmtoolsd` has from a unprivileged user's
perspective are the open file descriptors for /dev/uinput and/or
/var/run/vmblock-fuse/dev. With the help of `ptrace()` malicious code could be
injected into the `vmtoolsd` process to get access to the privileged file
descriptors. An even easier approach is to use modern Linux's pidfd API
`pidfd_open()` and `pidfd_getfd()` to obtain a copy of the privileged file
descriptors. In the man page `pidfd_getfd(2)` we can find:

    Permission to duplicate another process's file descriptor is governed by a
    ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)).

In this context this again boils down to the process's "dumpable" attribute
which is now set to 1, and thus the operation is allowed.

Exploiting the Issue
====================

`vmware-user-suid-wrapper` can be forced to open /dev/uinput even if not
running on Wayland by setting the user controlled environment variable
`XDG_SESSION_TYPE=wayland`. This means the file descriptor for this device
file will always be a valid attacker target independently of the actual
situation on a system.

There are two different scenarios to look at regarding the exploitability of
the issue. The easier case is when a valid environment for `vmtoolsd` is
available i.e. a graphical desktop session is existing and the check for
running in a VMware guest machine is succeeding (function call
`VMCheck_IsVirtualWorld()`). In this case `vmtoolsd` will continue running
permanently and there is no race condition to be won. Exploiting the issue is
straightforward, as is demonstrated in the attached PoC program
`vmware-get-fd.c`.

The more difficult case is when an attacker is either not running a graphical
environment or not even running in a VMware guest environment. In the worst
case `vmtoolsd` will terminate quickly, because of the failing
`VMCheck_IsVirtualWorld()` check. Thus the time window for actually operating
on the vulnerable process is short. A variant of the PoC program
`vmware-race-fd.c` is attached, which starts the `vmware-user-suid-wrapper`
continuously and attempts to snatch the privileged file descriptors from the
short-lived `vmtoolsd` process. In my tests this often succeeded quickly
(even on the first attempt), likely when the `vmtoolsd` resources have not yet
been cached by the kernel. Later attempts often take a longer time to succeed
but still succeeded after 10 to 20 seconds.

In summary the existence of the setuid-root program `vmware-user-suid-wrapper`
is enough to exploit the issue for /dev/uinput. The attacker needs no special
permissions (even the `nobody` user can exploit it) and the operating system
doesn't even need to be running as a VMware guest. This can be relevant in
situations when open-vm-tools are distributed by default in generic Linux
distributions / images, or in environments where unprivileged users are
allowed to install additional software from trusted sources without root
authentication (a model that is e.g. supported by the PackageKit project).

Vulnerability Impact
====================

/dev/uinput
-----------

Getting access to a file descriptor for the /dev/uinput device allows an
attacker to create arbitrary userspace based input devices and register them
with the kernel. This includes the possibility to send synthesized key or
mouse events to the kernel. The attached example program "uinput-inject.c"
demonstrates how this can be used to cause arbitrary key strokes to be
injected into local user sessions both graphical or on textual login consoles.
Thus this attack vector borders the area of arbitrary code execution with the
restriction that a local interactive user needs to be present.

This aspect of the vulnerability could be used to increase privileges after
gaining low privilege access e.g. through a remote security hole. On multi
user machines with shared access it could be used to prepare an attack where a
background process waits for a victim user to log into the machine and then
inject malicious input into its session.

Since /dev/uinput is not VMware specific, this attack vector is basically also
available in non-VMware environments.

The following is an example exploit run using the attached programs, provided
the `vmware-user-suid-wrapper` is already installed and a compiler is
available:

    user$ gcc -O2 vmware-race-fd.c -ovmware-race-fd
    user$ gcc -O2 uinput-inject.c -ouinput-inject
    
    user$ ./vmware-race-fd
    vmware-user: could not open /proc/fs/vmblock/dev
    vmware-user: could not open /proc/fs/vmblock/dev
    [...]
    /usr/bin/vmtoolsd running at 12226
    Found fd 3 for /dev/uinput in /usr/bin/vmtoolsd
    Executing sub shell which will inherit the snatched file descriptor 4 (check /proc/self/fd)
    
    user$ ls -l /proc/self/fd/4
    l-wx------ 1 user group 64 Jul 25 13:43 /proc/self/fd/4 -> /dev/uinput
    
    user$ ./uinput-inject 4
    Sleeping 3 seconds for input subsystem to settle
    completed one iteration
    completed one iteration

This will continuously write the line "you have been hacked" onto whatever
session is currently selected on the system's display.

/var/run/vmblock-fuse/dev
-------------------------

As far as I understand, this file is created by the `vmware-vmblock-fuse`
daemon and represents a control file. The FUSE file system is used to
implement access to folders shared between the VMware host and VMware guests.
This file allows, according to documentation [5], to add, delete or list
blocks in shared folders.

As a result access to this file descriptor breaks the boundary between
different users in the guest system regarding shared folder access.
The integrity of the shared folder content can be violated. It might
also be possible to leak information from shared folders into the unprivileged
user's context.

Depending on the actual attack environment it might allow to result in code
execution if e.g. malicious code is written to shared folders that could then
be executed even on the VMware host system.

The vmware-fuse documentation [5] mentions the outlook to allow unprivileged
users access to this control file, but this idea seems not safe to me in its
current form.

I did not look more closely into practical exploits of this.

Suggested Fix
=============

To fix this problem it must be prevented that the "dumpable" attribute of the
`vmware-user-suid-wrapper` process is reset when executing `vmtoolsd`. One way
to achieve this could be to move the privilege drop logic into `vmtoolsd`
instead. As long as the process is running in the setuid-root context, the
"dumpable" attribute will not be reset. `vmtoolsd` can then drop privileges
and also mark the privileged file descriptors with the `O_CLOEXEC` flag to
prevent them to be inherited unintendedly to further child processes, which
might result in the same problem again.

As a first aid and/or hardening measure, access to the
`vmware-user-suid-wrapper` could be limited to members of a privileged group
e.g. vmware-users. This would reduce the attack surface and prevent e.g. a
compromised `nobody` user account to exploit this.

In terms of hardening, the `vmware-user-suid-wrapper` could also add some code
to sanitize the environment variables passed from the unprivileged context,
which is a frequent source of security issues in setuid-root binaries. At
least the PATH variable should be reset to a safe value to avoid any future
surprises when looking up executable for `execve()`.

References
==========

[1]: https://github.com/vmware/open-vm-tools
[2]: https://github.com/vmware/open-vm-tools/tree/master/open-vm-tools/vmware-user-suid-wrapper
[3]: https://en.opensuse.org/openSUSE:Security_disclosure_policy
[4]: https://www.openwall.com/lists/oss-security/
[5]: https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/vmblock-fuse/design.txt
Comment 12 Matthias Gerstner 2023-07-26 07:48:47 UTC
Kirk, I'm assigning the to you now, since my part is finished.

As long as this issue is still under embargo there is nothing to do. I will
update the bug when there is news from upstream like a publication date,
patches etc.

Your job will be to (in private) prepare updates for all codestreams once a
patch is available. Please refer to comment 2 for the formal documentation of
the process. Also remember not to leak any information about this finding
anywhere else outside or inside the company before we mark this bug public.

If you have any question feel free to approach us.
Comment 13 Kirk Allan 2023-07-26 14:22:26 UTC
(In reply to Matthias Gerstner from comment #12)
> Kirk, I'm assigning the to you now, since my part is finished.
> 
> As long as this issue is still under embargo there is nothing to do. I will
> update the bug when there is news from upstream like a publication date,
> patches etc.

OK, I'll apply the patches when they become available.
Comment 14 Matthias Gerstner 2023-08-04 12:11:06 UTC
VMware gave us some first initial replies. They want to perform coordinated
disclosure. I have no technical analysis of a potential release date from
their end yet.
Comment 16 Matthias Gerstner 2023-08-23 10:18:46 UTC
We just got an update from VMware upstream. It seems they need even more than
the 90 days maximum embargo time we offer them. They stated the CRD from
comment 15 as the latest release date, but it might be a couple of days
earlier.

This does not shine the best light on their processes, since this issue
shouldn't be _that_ hard to fix. Still since they show willigness to fix the
issue in a defined time frame I agreed to extend the CRD by the requested
amount. This never happened before and I really don't want to extend it any
further than that - I also communicated this to them.

On the technical level I have no news from them, but asked them once more if
they can share any analysis, CVE or patch with us.
Comment 17 Matthias Gerstner 2023-10-23 08:41:38 UTC
Reactive security created a duplicate bug for this issue when they received the report via the Distros mailing list. Since they already use their bug in update references we will close this older bug as duplicate instead.

It seems upstream wants to release an update earlier after all on Oct 26 already. Please check the other bug for more details.

*** This bug has been marked as a duplicate of bug 1216433 ***
Comment 18 Matthias Gerstner 2023-10-27 07:04:12 UTC
Upstream has published this today.