You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2016/08/11 20:06:59 UTC

[1/2] mesos git commit: Introduced linux capabilities API.

Repository: mesos
Updated Branches:
  refs/heads/master 4c12c1e84 -> 8ab880272


Introduced linux capabilities API.

This change introduces basic API for linux capabilities. This is not a
comprehensive API but is strictly limited to the need for securing Mesos
containers using linux capabilities.

This patch is based on the work in https://reviews.apache.org/r/46370/.

Review: https://reviews.apache.org/r/50266/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/8ab88027
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/8ab88027
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/8ab88027

Branch: refs/heads/master
Commit: 8ab880272cbc020344855e4bb1d210bd6d648109
Parents: 98fe260
Author: Benjamin Bannier <be...@mesosphere.io>
Authored: Thu Aug 11 08:44:26 2016 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Thu Aug 11 13:06:48 2016 -0700

----------------------------------------------------------------------
 src/CMakeLists.txt         |   1 +
 src/Makefile.am            |   2 +
 src/linux/capabilities.cpp | 397 ++++++++++++++++++++++++++++++++++++++++
 src/linux/capabilities.hpp | 208 +++++++++++++++++++++
 4 files changed, 608 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6088c26..4362d47 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -152,6 +152,7 @@ set(HDFS_SRC
 
 set(LINUX_SRC
   ${LINUX_SRC}
+  linux/capabilities.cpp
   linux/cgroups.cpp
   linux/fs.cpp
   linux/ldcache.cpp

http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 33706e5..d389e02 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1036,6 +1036,7 @@ libmesos_no_3rdparty_la_SOURCES +=					\
   watcher/whitelist_watcher.hpp
 
 MESOS_LINUX_FILES =							\
+  linux/capabilities.cpp						\
   linux/cgroups.cpp							\
   linux/fs.cpp								\
   linux/ldcache.cpp							\
@@ -1065,6 +1066,7 @@ MESOS_LINUX_FILES =							\
   slave/containerizer/mesos/provisioner/backends/overlay.cpp
 
 MESOS_LINUX_FILES +=							\
+  linux/capabilities.hpp						\
   linux/cgroups.hpp							\
   linux/fs.hpp								\
   linux/ldcache.hpp							\

http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/linux/capabilities.cpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.cpp b/src/linux/capabilities.cpp
new file mode 100644
index 0000000..5f21dd4
--- /dev/null
+++ b/src/linux/capabilities.cpp
@@ -0,0 +1,397 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+
+#include <linux/capability.h>
+
+#include <sys/prctl.h>
+
+#include <set>
+#include <string>
+
+#include <stout/numify.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+
+#include "linux/capabilities.hpp"
+
+using std::hex;
+using std::ostream;
+using std::set;
+using std::string;
+using std::vector;
+
+// We declare two functions provided in the libcap headers here to
+// prevent introduction of that build-time dependency.
+extern "C" {
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, const cap_user_data_t data);
+}
+
+
+namespace mesos {
+namespace internal {
+namespace capabilities {
+
+constexpr char PROC_CAP_LAST_CAP[] = "/proc/sys/kernel/cap_last_cap";
+constexpr int CAPABIILITY_PROTOBUF_OFFSET = 1000;
+
+
+// System call payload (for linux capability version 3).
+struct SyscallPayload
+{
+  __user_cap_header_struct head;
+  __user_cap_data_struct set[_LINUX_CAPABILITY_U32S_3];
+
+  SyscallPayload()
+  {
+    memset(this, 0, sizeof(SyscallPayload));
+  }
+
+  uint64_t effective()
+  {
+    uint64_t low = static_cast<uint64_t>(set[0].effective);
+    uint64_t high = static_cast<uint64_t>(set[1].effective) << 32;
+    return low | high;
+  }
+
+  uint64_t permitted()
+  {
+    uint64_t low = static_cast<uint64_t>(set[0].permitted);
+    uint64_t high = static_cast<uint64_t>(set[1].permitted) << 32;
+    return low | high;
+  }
+
+  uint64_t inheritable()
+  {
+    uint64_t low = static_cast<uint64_t>(set[0].inheritable);
+    uint64_t high = static_cast<uint64_t>(set[1].inheritable) << 32;
+    return low | high;
+  }
+
+  void setEffective(uint64_t effective)
+  {
+    set[0].effective = static_cast<uint32_t>(effective);
+    set[1].effective = static_cast<uint32_t>(effective >> 32);
+  }
+
+  void setPermitted(uint64_t permitted)
+  {
+    set[0].permitted = static_cast<uint32_t>(permitted);
+    set[1].permitted = static_cast<uint32_t>(permitted >> 32);
+  }
+
+  void setInheritable(uint64_t inheritable)
+  {
+    set[0].inheritable = static_cast<uint32_t>(inheritable);
+    set[1].inheritable = static_cast<uint32_t>(inheritable >> 32);
+  }
+};
+
+
+// Helper funciton to convert capability set to bitset.
+static uint64_t toCapabilityBitset(const Set<Capability>& capabilities)
+{
+  uint64_t result = 0;
+
+  for (int i = 0; i < MAX_CAPABILITY; i++) {
+    if (capabilities.count(static_cast<Capability>(i)) > 0) {
+      result |= (1ULL << i);
+    }
+  }
+
+  return result;
+}
+
+
+// Helper function to convert capability bitset to Set.
+static Set<Capability> toCapabilitySet(uint64_t bitset)
+{
+  Set<Capability> result;
+
+  for (int i = 0; i < MAX_CAPABILITY; i++) {
+    if ((bitset & (1ULL << i)) != 0) {
+      result.insert(Capability(i));
+    }
+  }
+
+  return result;
+}
+
+
+Set<Capability> ProcessCapabilities::get(const Type& type) const
+{
+  switch (type) {
+    case EFFECTIVE:   return effective;
+    case INHERITABLE: return inheritable;
+    case PERMITTED:   return permitted;
+    case BOUNDING:    return bounding;
+  }
+
+  UNREACHABLE();
+}
+
+
+void ProcessCapabilities::set(
+    const Type& type,
+    const Set<Capability>& capabilities)
+{
+  switch (type) {
+    case EFFECTIVE:   effective = capabilities;
+    case PERMITTED:   permitted = capabilities;
+    case INHERITABLE: inheritable = capabilities;
+    case BOUNDING:    bounding = capabilities;
+    default:          UNREACHABLE();
+  }
+}
+
+
+Try<Capabilities> Capabilities::create()
+{
+  // Check for compatible linux capability version.
+  SyscallPayload payload;
+
+  if (capget(&payload.head, nullptr)) {
+    return ErrnoError("Failed to get linux capability version");
+  }
+
+  if (payload.head.version != _LINUX_CAPABILITY_VERSION_3) {
+    return Error(
+        "Unsupported linux capabilities version: " +
+        stringify(payload.head.version));
+  }
+
+  // Read and check the maximum capability value.
+  Try<string> _lastCap = os::read(PROC_CAP_LAST_CAP);
+  if (_lastCap.isError()) {
+    return Error(
+        "Failed to read '" + string(PROC_CAP_LAST_CAP) + "': " +
+        _lastCap.error());
+  }
+
+  Try<int> lastCap = numify<int>(strings::trim(
+      _lastCap.get(),
+      strings::SUFFIX,
+      "\n"));
+
+  if (lastCap.isError()) {
+    return Error(
+        "Failed to parse system last capability value '" +
+        _lastCap.get() + "': " + lastCap.error());
+  }
+
+  if (lastCap.get() >= MAX_CAPABILITY) {
+    return Error(
+        "System last capability value '" + stringify(lastCap.get()) +
+        "' is greater than maximum supported number of capabilities '" +
+        stringify(MAX_CAPABILITY) + "'");
+  }
+
+  return Capabilities(lastCap.get());
+}
+
+
+Try<ProcessCapabilities> Capabilities::get() const
+{
+  SyscallPayload payload;
+
+  payload.head.version = _LINUX_CAPABILITY_VERSION_3;
+  payload.head.pid = 0;
+
+  if (capget(&payload.head, &payload.set[0])) {
+    return ErrnoError("Failed to get capabilities");
+  }
+
+  ProcessCapabilities capabilities;
+
+  capabilities.effective = toCapabilitySet(payload.effective());
+  capabilities.permitted = toCapabilitySet(payload.permitted());
+  capabilities.inheritable = toCapabilitySet(payload.inheritable());
+
+  // TODO(jojy): Add support for BOUNDING capabilities.
+
+  return capabilities;
+}
+
+
+// We do two separate operations:
+//  1. Set the `bounding` capabilities for the process.
+//  2. Set the `effective`, `permitted` and `inheritable` capabilities.
+//
+// TODO(jojy): Is there a way to make this atomic? Ideally, we would
+// like to rollback any changes if any of the operation fails.
+Try<Nothing> Capabilities::set(const ProcessCapabilities& capabilities)
+{
+  // NOTE: We can only drop capabilities in the bounding set.
+  for (int i = 0; i <= lastCap; i++) {
+    if (capabilities.bounding.count(Capability(i)) > 0) {
+      continue;
+    }
+
+    VLOG(1) << "Dropping capability " << Capability(i);
+
+    if (prctl(PR_CAPBSET_DROP, i, 1) < 0) {
+      return ErrnoError(
+          "Failed to drop capability: "
+          "PR_CAPBSET_DROP failed for the process");
+    }
+  }
+
+  SyscallPayload payload;
+
+  payload.head.version = _LINUX_CAPABILITY_VERSION_3;
+  payload.head.pid = 0;
+
+  payload.setEffective(toCapabilityBitset(capabilities.effective));
+  payload.setPermitted(toCapabilityBitset(capabilities.permitted));
+  payload.setInheritable(toCapabilityBitset(capabilities.inheritable));
+
+  if (capset(&payload.head, &payload.set[0])) {
+    return ErrnoError("Failed to set capabilities");
+  }
+
+  return Nothing();
+}
+
+
+Try<Nothing> Capabilities::keepCapabilitiesOnSetUid()
+{
+  if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+    return ErrnoError("Failed to set PR_SET_KEEPCAPS for the process");
+  }
+
+  return Nothing();
+}
+
+
+Set<Capability> Capabilities::getAllSupportedCapabilities()
+{
+  Set<Capability> result;
+
+  for (int i = 0; i <= lastCap; i++) {
+    result.insert(Capability(i));
+  }
+
+  return result;
+}
+
+
+Capability convert(const CapabilityInfo::Capability& capability)
+{
+  int value = capability - CAPABIILITY_PROTOBUF_OFFSET;
+
+  CHECK_LE(0, value);
+  CHECK_GT(MAX_CAPABILITY, value);
+
+  return static_cast<Capability>(value);
+}
+
+
+CapabilityInfo convert(const Set<Capability>& capabilities)
+{
+  CapabilityInfo capabilityInfo;
+
+  foreach (const Capability& capability, capabilities) {
+    capabilityInfo.add_capabilities(static_cast<CapabilityInfo::Capability>(
+        capability + CAPABIILITY_PROTOBUF_OFFSET));
+  }
+
+  return capabilityInfo;
+}
+
+
+ostream& operator<<(ostream& stream, const Capability& capability)
+{
+  switch (capability) {
+    case CHOWN:             return stream << "CHOWN";
+    case DAC_OVERRIDE:      return stream << "DAC_OVERRIDE";
+    case DAC_READ_SEARCH:   return stream << "DAC_READ_SEARCH";
+    case FOWNER:            return stream << "FOWNER";
+    case FSETID:            return stream << "FSETID";
+    case KILL:              return stream << "KILL";
+    case SETGID:            return stream << "SETGID";
+    case SETUID:            return stream << "SETUID";
+    case SETPCAP:           return stream << "SETPCAP";
+    case LINUX_IMMUTABLE:   return stream << "LINUX_IMMUTABLE";
+    case NET_BIND_SERVICE:  return stream << "NET_BIND_SERVICE";
+    case NET_BROADCAST:     return stream << "NET_BROADCAST";
+    case NET_ADMIN:         return stream << "NET_ADMIN";
+    case NET_RAW:           return stream << "NET_RAW";
+    case IPC_LOCK:          return stream << "IPC_LOCK";
+    case IPC_OWNER:         return stream << "IPC_OWNER";
+    case SYS_MODULE:        return stream << "SYS_MODULE";
+    case SYS_RAWIO:         return stream << "SYS_RAWIO";
+    case SYS_CHROOT:        return stream << "SYS_CHROOT";
+    case SYS_PTRACE:        return stream << "SYS_PTRACE";
+    case SYS_PACCT:         return stream << "SYS_PACCT";
+    case SYS_ADMIN:         return stream << "SYS_ADMIN";
+    case SYS_BOOT:          return stream << "SYS_BOOT";
+    case SYS_NICE:          return stream << "SYS_NICE";
+    case SYS_RESOURCE:      return stream << "SYS_RESOURCE";
+    case SYS_TIME:          return stream << "SYS_TIME";
+    case SYS_TTY_CONFIG:    return stream << "SYS_TTY_CONFIG";
+    case MKNOD:             return stream << "MKNOD";
+    case LEASE:             return stream << "LEASE";
+    case AUDIT_WRITE:       return stream << "AUDIT_WRITE";
+    case AUDIT_CONTROL:     return stream << "AUDIT_CONTROL";
+    case SETFCAP:           return stream << "SETFCAP";
+    case MAC_OVERRIDE:      return stream << "MAC_OVERRIDE";
+    case MAC_ADMIN:         return stream << "MAC_ADMIN";
+    case SYSLOG:            return stream << "SYSLOG";
+    case WAKE_ALARM:        return stream << "WAKE_ALARM";
+    case BLOCK_SUSPEND:     return stream << "BLOCK_SUSPEND";
+    case AUDIT_READ:        return stream << "AUDIT_READ";
+    case MAX_CAPABILITY:    UNREACHABLE();
+  }
+
+  UNREACHABLE();
+}
+
+
+ostream& operator<<(ostream& stream, const Type& type)
+{
+  switch (type) {
+    case EFFECTIVE:   return stream << "eff";
+    case INHERITABLE: return stream << "inh";
+    case PERMITTED:   return stream << "perm";
+    case BOUNDING:    return stream << "bnd";
+  }
+
+  UNREACHABLE();
+}
+
+
+ostream& operator<<(ostream& stream, const Set<Capability>& capabilities)
+{
+  return stream << stringify(capabilities);
+}
+
+
+ostream& operator<<(ostream& stream, const ProcessCapabilities& capabilities)
+{
+  return stream
+    << "{"
+    << EFFECTIVE    << ": " << capabilities.effective   << ", "
+    << PERMITTED    << ": " << capabilities.permitted   << ", "
+    << INHERITABLE  << ": " << capabilities.inheritable << ", "
+    << BOUNDING     << ": " << capabilities.bounding
+    << "}";
+}
+
+} // namespace capabilities {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/linux/capabilities.hpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.hpp b/src/linux/capabilities.hpp
new file mode 100644
index 0000000..fd7a571
--- /dev/null
+++ b/src/linux/capabilities.hpp
@@ -0,0 +1,208 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __LINUX_CAPABILITIES_HPP__
+#define __LINUX_CAPABILITIES_HPP__
+
+#include <string>
+#include <vector>
+
+#include <stout/flags.hpp>
+#include <stout/nothing.hpp>
+#include <stout/protobuf.hpp>
+#include <stout/set.hpp>
+#include <stout/try.hpp>
+
+#include <mesos/mesos.hpp>
+
+namespace mesos {
+namespace internal {
+namespace capabilities {
+
+// Superset of all capabilities. This is the set currently supported
+// by linux (kernel 4.0).
+enum Capability : int
+{
+  CHOWN             = 0,
+  DAC_OVERRIDE      = 1,
+  DAC_READ_SEARCH   = 2,
+  FOWNER            = 3,
+  FSETID            = 4,
+  KILL              = 5,
+  SETGID            = 6,
+  SETUID            = 7,
+  SETPCAP           = 8,
+  LINUX_IMMUTABLE   = 9,
+  NET_BIND_SERVICE  = 10,
+  NET_BROADCAST     = 11,
+  NET_ADMIN         = 12,
+  NET_RAW           = 13,
+  IPC_LOCK          = 14,
+  IPC_OWNER         = 15,
+  SYS_MODULE        = 16,
+  SYS_RAWIO         = 17,
+  SYS_CHROOT        = 18,
+  SYS_PTRACE        = 19,
+  SYS_PACCT         = 20,
+  SYS_ADMIN         = 21,
+  SYS_BOOT          = 22,
+  SYS_NICE          = 23,
+  SYS_RESOURCE      = 24,
+  SYS_TIME          = 25,
+  SYS_TTY_CONFIG    = 26,
+  MKNOD             = 27,
+  LEASE             = 28,
+  AUDIT_WRITE       = 29,
+  AUDIT_CONTROL     = 30,
+  SETFCAP           = 31,
+  MAC_OVERRIDE      = 32,
+  MAC_ADMIN         = 33,
+  SYSLOG            = 34,
+  WAKE_ALARM        = 35,
+  BLOCK_SUSPEND     = 36,
+  AUDIT_READ        = 37,
+  MAX_CAPABILITY    = 38,
+};
+
+
+enum Type
+{
+  EFFECTIVE,
+  PERMITTED,
+  INHERITABLE,
+  BOUNDING,
+};
+
+
+/**
+ * Encapsulation of capability value sets.
+ */
+class ProcessCapabilities
+{
+public:
+  Set<Capability> get(const Type& type) const;
+  void set(const Type& type, const Set<Capability>& capabilities);
+
+private:
+  friend class Capabilities;
+
+  friend std::ostream& operator<<(
+      std::ostream& stream,
+      const ProcessCapabilities& set);
+
+  // Disallow default constructor.
+  ProcessCapabilities() {}
+
+  Set<Capability> effective;
+  Set<Capability> permitted;
+  Set<Capability> inheritable;
+  Set<Capability> bounding;
+};
+
+
+/**
+ * Provides wrapper for the linux process capabilities interface.
+ * Note: This is a class instead of an interface because it has state
+ * associated with it.
+ *
+ * TODO(jojy): Currently we only support linux capabilities. Consider
+ * refactoring the interface so that we can support a generic
+ * interface which can be used for other OSes(BSD, Windows etc).
+ */
+class Capabilities
+{
+public:
+  /**
+   * Factory method to create Capabilities object.
+   *
+   * @return `Capabilities` on success;
+   *          Error on falure. Failure conditions could be:
+   *           - Error getting system information (e.g, version).
+   *           - Unsupported linux kernel capabilities version.
+   *           - Maximum capability supported by kernel exceeds the
+   *             ones defined in the enum `Capabilities`.
+   */
+  static Try<Capabilities> create();
+
+  /**
+   * Gets capability set for the calling process.
+   *
+   * @return ProcessCapabilities on success.
+   *         Error on failure.
+   */
+  Try<ProcessCapabilities> get() const;
+
+  /**
+   * Sets capabilities for the calling process.
+   *
+   * @param `ProcessCapabilities` to be set for the process.
+   * @return Nothing on success.
+   *         Error on failure.
+   */
+  Try<Nothing> set(const ProcessCapabilities& processCapabilities);
+
+  /**
+   * Process control interface to enforce keeping the parent process's
+   * capabilities after a change in uid/gid.
+   *
+   * @return Nothing on success.
+   *         Error on failure.
+   */
+  Try<Nothing> keepCapabilitiesOnSetUid();
+
+  /**
+   * Get all capabilities supported by the system.
+   *
+   * @return the set of supported capabilities.
+   */
+  Set<Capability> getAllSupportedCapabilities();
+
+private:
+  explicit Capabilities(int _lastCap) : lastCap(_lastCap) {}
+
+  // Maximum count of capabilities supported by the system.
+  const int lastCap;
+};
+
+
+Capability convert(const CapabilityInfo::Capability& capabilityInfo);
+CapabilityInfo convert(const Set<Capability>& capabilitySet);
+
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Capability& capability);
+
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Type& type);
+
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Set<Capability>& capabilities);
+
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const ProcessCapabilities& capabilities);
+
+} // namespace capabilities {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __LINUX_CAPABILITIES_HPP__


[2/2] mesos git commit: Added stringify for Set in stout.

Posted by ji...@apache.org.
Added stringify for Set in stout.


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/98fe2606
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/98fe2606
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/98fe2606

Branch: refs/heads/master
Commit: 98fe2606040e72c73240026dcababe6dc0f06b1d
Parents: 4c12c1e
Author: Jie Yu <yu...@gmail.com>
Authored: Thu Aug 11 13:03:42 2016 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Thu Aug 11 13:06:48 2016 -0700

----------------------------------------------------------------------
 3rdparty/stout/include/stout/stringify.hpp | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/98fe2606/3rdparty/stout/include/stout/stringify.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/stout/stringify.hpp b/3rdparty/stout/include/stout/stringify.hpp
index 6b6e788..b4d3e61 100644
--- a/3rdparty/stout/include/stout/stringify.hpp
+++ b/3rdparty/stout/include/stout/stringify.hpp
@@ -23,6 +23,7 @@
 
 #include "abort.hpp"
 #include "hashmap.hpp"
+#include "set.hpp"
 
 template <typename T>
 std::string stringify(T t)
@@ -61,6 +62,23 @@ std::string stringify(const std::set<T>& set)
 
 
 template <typename T>
+std::string stringify(const Set<T>& set)
+{
+  std::ostringstream out;
+  out << "{ ";
+  typename Set<T>::const_iterator iterator = set.begin();
+  while (iterator != set.end()) {
+    out << stringify(*iterator);
+    if (++iterator != set.end()) {
+      out << ", ";
+    }
+  }
+  out << " }";
+  return out.str();
+}
+
+
+template <typename T>
 std::string stringify(const std::list<T>& list)
 {
   std::ostringstream out;