Exploit-DB #45407
Description: Chrome OS 10820.0.0 dev-channel - app->VM via garcon TCP Command Socket
Type: local
Platform: linux
Date: 2018-09-13
Author: Google Security Research

Exploit:
======================= BUG DESCRIPTION =======================
There is a variety of RPC communication channels between the Chrome OS host
system and the crosvm guest. This bug report focuses on communication on TCP
port 8889, which is used by the "garcon" service.

Among other things, garcon is responsible for:
 - sending URLs to be opened from the guest to the host
 - telling the host which applications (more precisely, .desktop files) are
   installed in the guest, so that the host can display them in the launcher
 - telling the guest to launch applications when the user clicks on stuff in the
   launcher

garcon uses gRPC, which is an RPC protocol that sends protobufs over plaintext
HTTP/2. (Other system components communicate with the VM over gRPC-over-vsock,
but garcon uses gRPC-over-TCP.) For some command types, the TCP connection is
initiated by the host; for others, it is initiated by the guest. Both guest and
host are listening on [::]:8889; however, the iptables rules of the host prevent
an outside host from simply connecting to those sockets.

However, Chrome OS apps running on the host (and I think also Android apps, but
I haven't tested that) are not affected by such restrictions. This means that a
Chrome OS app with the "Exchange data with any device on the local network or
internet" permission can open a gRPC socket to the guest's garcon (no
authentication required) and send a vm_tools.container.Garcon.LaunchApplication
RPC call to the guest. This RPC call takes two arguments:

  // Request protobuf for launching an application in the container.
  message LaunchApplicationRequest {
    // The ID of the application to launch. This should correspond to an
    // identifier for a .desktop file available in the container.
    string desktop_file_id = 1;

    // Files to pass as arguments when launching the application, if any, given
    // as absolute paths within the container's filesystem.
    repeated string files = 2;
  }

"desktop_file_id" is actually a relative path to a .desktop file without the
".desktop". One preinstalled .desktop file in the VM is for the VIM editor.
"files" is an array of "paths" that should be provided to the application as
arguments - but actually, you can just pass in arbitrary arguments this way.
This only works for applications whose .desktop files permit passing arguments,
but vim.desktop does permit that.

VIM permits passing a VIM command to be executed on startup using the "--cmd"
flag, and a VIM command that starts with an exclamation mark is interpreted as
a shell command.

So in summary, you can execute arbitrary shell commands by sending a gRPC like
this to the VM:
vm_tools.container.Garcon.LaunchApplication({
  desktop_file_id: 'vim',
  files: [
    '--cmd',
    '!id>/tmp/owned'
  ]
})

For my PoC, since I didn't want to try directly speaking gRPC from inside a
Chrome app, I instead built an app that forwards the garcon port to the outside
network, and then connected to garcon from a normal Linux workstation.
Repro instructions are at the bottom of this bug report, under
"REPRO INSTRUCTIONS", but first I'll list a few other things about crosvm that
look interesting from a security perspective.

Because of this issue, and because of the routing issue described below, I
strongly recommend getting rid of TCP-based communication with the VM entirely.
The various other inter-VM socket types that are already used should be more
than sufficient.



======================= RANDOM OBSERVATIONS =======================
This section lists a bunch of things that I haven't written full PoCs for so far,
but that look interesting or bad from a security perspective:

------------------- ROUTING TABLE -------------------
It is possible for a malicious wifi router to get packets from the host-side
garcon RPC endpoint. You might be able to do bad stuff to a Chrome OS
device over the network using this, but I haven't fully tested this yet.

Normally, when you connect the Chrome OS device to a wifi network, the routing
table looks as follows:

localhost / # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.246.1   0.0.0.0         UG    1      0        0 wlan0
100.115.92.0    0.0.0.0         255.255.255.252 U     0      0        0 arcbr0
100.115.92.4    0.0.0.0         255.255.255.252 U     0      0        0 vmtap0
100.115.92.192  100.115.92.6    255.255.255.240 UG    0      0        0 vmtap0
192.168.246.0   0.0.0.0         255.255.255.0   U     0      0        0 wlan0

Routing precedence depends on prefix length; more specific routes take
precedence over the default route. The 100.115.92.192/28 route is the most
specific one for traffic to the VM at 100.115.92.204, so that is the route used
for RPC calls to that IP address.

However, the wifi router can supply routing information that influences the
routing table. By modifying the wifi router's settings and reconnecting the
Chrome OS device, I get the following routing table in Chrome OS:

localhost / # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         100.115.92.201  0.0.0.0         UG    1      0        0 wlan0
8.8.8.8         100.115.92.201  255.255.255.255 UGH   0      0        0 wlan0
100.115.92.0    0.0.0.0         255.255.255.252 U     0      0        0 arcbr0
100.115.92.4    0.0.0.0         255.255.255.252 U     0      0        0 vmtap0
100.115.92.192  100.115.92.6    255.255.255.240 UG    0      0        0 vmtap0
100.115.92.200  0.0.0.0         255.255.255.248 U     0      0        0 wlan0

Now the 100.115.92.200/29 route pointing to wlan0 takes precedence over the
legitimate 100.115.92.192/28 route. I then told my router to respond to ARP
queries for 100.115.92.204 (the VM's IP) and ran wireshark on the wifi router
while co