gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/website/blog/2019-11-18-security-basics.md (about)

     1  # gVisor Security Basics - Part 1
     2  
     3  This blog is a space for engineers and community members to share perspectives
     4  and deep dives on technology and design within the gVisor project. Though our
     5  logo suggests we're in the business of space exploration (or perhaps fighting
     6  sea monsters), we're actually in the business of sandboxing Linux containers.
     7  When we created gVisor, we had three specific goals in mind; _container-native
     8  security_, _resource efficiency_, and _platform portability_. To put it simply,
     9  gVisor provides _efficient defense-in-depth for containers anywhere_.
    10  
    11  This post addresses gVisor's _container-native security_, specifically how
    12  gVisor provides strong isolation between an application and the host OS. Future
    13  posts will address _resource efficiency_ (how gVisor preserves container
    14  benefits like fast starts, smaller snapshots, and less memory overhead than VMs)
    15  and _platform portability_ (run gVisor wherever Linux OCI containers run).
    16  Delivering on each of these goals requires careful security considerations and a
    17  robust design.
    18  
    19  ## What does "sandbox" mean?
    20  
    21  gVisor allows the execution of untrusted containers, preventing them from
    22  adversely affecting the host. This means that the untrusted container is
    23  prevented from attacking or spying on either the host kernel or any other peer
    24  userspace processes on the host.
    25  
    26  For example, if you are a cloud container hosting service, running containers
    27  from different customers on the same virtual machine means that compromises
    28  expose customer data. Properly configured, gVisor can provide sufficient
    29  isolation to allow different customers to run containers on the same host. There
    30  are many aspects to the proper configuration, including limiting file and
    31  network access, which we will discuss in future posts.
    32  
    33  ## The cost of compromise
    34  
    35  gVisor was designed around the premise that any security boundary could
    36  potentially be compromised with enough time and resources. We tried to optimize
    37  for a solution that was as costly and time-consuming for an attacker as
    38  possible, at every layer.
    39  
    40  Consequently, gVisor was built through a combination of intentional design
    41  principles and specific technology choices that work together to provide the
    42  security isolation needed for running hostile containers on a host. We'll dig
    43  into it in the next section!
    44  
    45  # Design Principles
    46  
    47  gVisor was designed with some common
    48  [secure design](https://en.wikipedia.org/wiki/Secure_by_design) principles in
    49  mind: Defense-in-Depth, Principle of Least-Privilege, Attack Surface Reduction
    50  and Secure-by-Default[^1].
    51  
    52  In general, Design Principles outline good engineering practices, but in the
    53  case of security, they also can be thought of as a set of tactics. In a
    54  real-life castle, there is no single defensive feature. Rather, there are many
    55  in combination: redundant walls, scattered draw bridges, small bottle-neck
    56  entrances, moats, etc.
    57  
    58  A simplified version of the design is below
    59  ([more detailed version](/docs/))[^2]:
    60  
    61  ![Figure 1](/assets/images/2019-11-18-security-basics-figure1.png "Simplified design of gVisor.")
    62  
    63  In order to discuss design principles, the following components are important to
    64  know:
    65  
    66  *   runsc - binary that packages the Sentry, platform, and Gofer(s) that run
    67      containers. runsc is the drop-in binary for running gVisor in Docker and
    68      Kubernetes.
    69  *   Untrusted Application - container running in the sandbox. Untrusted
    70      application/container are used interchangeably in this article.
    71  *   Platform Syscall Switcher - intercepts syscalls from the application and
    72      passes them to the Sentry with no further handling.
    73  *   Sentry - The "application kernel" in userspace that serves the untrusted
    74      application. Each application instance has its own Sentry. The Sentry
    75      handles syscalls, routes I/O to gofers, and manages memory and CPU, all in
    76      userspace. The Sentry is allowed to make limited, filtered syscalls to the
    77      host OS.
    78  *   Gofer - a process that specifically handles different types of I/O for the
    79      Sentry (usually disk I/O). Gofers are also allowed to make filtered syscalls
    80      to the Host OS.
    81  *   Host OS - the actual OS on which gVisor containers are running, always some
    82      flavor of Linux (sorry, Windows/MacOS users).
    83  
    84  It is important to emphasize what is being protected from the untrusted
    85  application in this diagram: the host OS and other userspace applications.
    86  
    87  In this post, we are only discussing security-related features of gVisor, and
    88  you might ask, "What about performance, compatibility and stability?" We will
    89  cover these considerations in future posts.
    90  
    91  ## Defense-in-Depth
    92  
    93  For gVisor, Defense-in-Depth means each component of the software stack trusts
    94  the other components as little as possible.
    95  
    96  It may seem strange that we would want our own software components to distrust
    97  each other. But by limiting the trust between small, discrete components, each
    98  component is forced to defend itself against potentially malicious input. And
    99  when you stack these components on top of each other, you can ensure that
   100  multiple security barriers must be overcome by an attacker.
   101  
   102  And this leads us to how Defense-in-Depth is applied to gVisor: no single
   103  vulnerability should compromise the host.
   104  
   105  In the "Attacker's Advantage / Defender's Dilemma," the defender must succeed
   106  all the time while the attacker only needs to succeed once. Defense in Depth
   107  inverts this principle: once the attacker successfully compromises any given
   108  software component, they are immediately faced with needing to compromise a
   109  subsequent, distinct layer in order to move laterally or acquire more privilege.
   110  
   111  For example, the untrusted container is isolated from the Sentry. The Sentry is
   112  isolated from host I/O operations by serving those requests in separate
   113  processes called Gofers. And both the untrusted container and its associated
   114  Gofers are isolated from the host process that is running the sandbox.
   115  
   116  An additional benefit is that this generally leads to more robust and stable
   117  software, forcing interfaces to be strictly defined and tested to ensure all
   118  inputs are properly parsed and bounds checked.
   119  
   120  ## Least-Privilege
   121  
   122  The principle of Least-Privilege implies that each software component has only
   123  the permissions it needs to function, and no more.
   124  
   125  Least-Privilege is applied throughout gVisor. Each component and more
   126  importantly, each interface between the components, is designed so that only the
   127  minimum level of permission is required for it to perform its function.
   128  Specifically, the closer you are to the untrusted application, the less
   129  privilege you have.
   130  
   131  ![Figure 2](/assets/images/2019-11-18-security-basics-figure2.png "runsc components and their privileges.")
   132  
   133  This is evident in how runsc (the drop in gVisor binary for Docker/Kubernetes)
   134  constructs the sandbox. The Sentry has the least privilege possible (it can't
   135  even open a file!). Gofers are only allowed file access, so even if it were
   136  compromised, the host network would be unavailable. Only the runsc binary itself
   137  has full access to the host OS, and even runsc's access to the host OS is often
   138  limited through capabilities / chroot / namespacing.
   139  
   140  Designing a system with Defense-in-Depth and Least-Privilege in mind encourages
   141  small, separate, single-purpose components, each with very restricted
   142  privileges.
   143  
   144  ## Attack Surface Reduction
   145  
   146  There are no bugs in unwritten code. In other words, gVisor supports a feature
   147  if and only if it is needed to run host Linux containers.
   148  
   149  ### Host Application/Sentry Interface:
   150  
   151  There are a lot of things gVisor does not need to do. For example, it does not
   152  need to support arbitrary device drivers, nor does it need to support video
   153  playback. By not implementing what will not be used, we avoid introducing
   154  potential bugs in our code.
   155  
   156  That is not to say gVisor has limited functionality! Quite the opposite, we
   157  analyzed what is actually needed to run Linux containers and today the Sentry
   158  supports 237 syscalls[^3]<sup>,</sup>[^4], along with the range of critical
   159  /proc and /dev files. However, gVisor does not support every syscall in the
   160  Linux kernel. There are about 350 syscalls[^5] within the 5.3.11 version of the
   161  Linux kernel, many of which do not apply to Linux containers that typically host
   162  cloud-like workloads. For example, we don't support old versions of epoll
   163  (epoll_ctl_old, epoll_wait_old), because they are deprecated in Linux and no
   164  supported workloads use them.
   165  
   166  Furthermore, any exploited vulnerabilities in the implemented syscalls (or
   167  Sentry code in general) only apply to gaining control of the Sentry. More on
   168  this in a later post.
   169  
   170  ### Sentry/Host OS Interface:
   171  
   172  The Sentry's interactions with the Host OS are restricted in many ways. For
   173  instance, no syscall is "passed-through" from the untrusted application to the
   174  host OS. All syscalls are intercepted and interpreted. In the case where the
   175  Sentry needs to call the Host OS, we severely limit the syscalls that the Sentry
   176  itself is allowed to make to the host kernel[^6].
   177  
   178  For example, there are many file-system based attacks, where manipulation of
   179  files or their paths, can lead to compromise of the host[^7]. As a result, the
   180  Sentry does not allow any syscall that creates or opens a file descriptor. All
   181  file descriptors must be donated to the sandbox. By disallowing open or creation
   182  of file descriptors, we eliminate entire categories of these file-based attacks.
   183  
   184  This does not affect functionality though. For example, during startup, runsc
   185  will donate FDs the Sentry that allow for mapping STDIN/STDOUT/STDERR to the
   186  sandboxed application. Also the Gofer may donate an FD to the Sentry, allowing
   187  for direct access to some files. And most files will be remotely accessed
   188  through the Gofers, in which case no FDs are donated to the Sentry.
   189  
   190  The Sentry itself is only allowed access to specific
   191  [allowlisted syscalls](https://github.com/google/gvisor/blob/master/runsc/config/config.go).
   192  Without networking, the Sentry needs 53 host syscalls in order to function, and
   193  with networking, it uses an additional 15[^8]. By limiting the allowlist to only
   194  these needed syscalls, we radically reduce the amount of host OS attack surface.
   195  If any attempts are made to call something outside the allowlist, it is
   196  immediately blocked and the sandbox is killed by the Host OS.
   197  
   198  ### Sentry/Gofer Interface:
   199  
   200  The Sentry communicates with the Gofer through a local unix domain socket (UDS)
   201  via a version of the 9P protocol[^9]. The UDS file descriptor is passed to the
   202  sandbox during initialization and all communication between the Sentry and Gofer
   203  happens via 9P. We will go more into how Gofers work in future posts.
   204  
   205  ### End Result
   206  
   207  So, of the 350 syscalls in the Linux kernel, the Sentry needs to implement only
   208  237 of them to support containers. At most, the Sentry only needs to call 68 of
   209  the host Linux syscalls. In other words, with gVisor, applications get the vast
   210  majority (and growing) functionality of Linux containers for only 68 possible
   211  syscalls to the Host OS. 350 syscalls to 68 is attack surface reduction.
   212  
   213  ![Figure 3](/assets/images/2019-11-18-security-basics-figure3.png "Reduction of Attack Surface of the Syscall Table. Note that the Senty's Syscall Emulation Layer keeps the Containerized Process from ever calling the Host OS.")
   214  
   215  ## Secure-by-default
   216  
   217  The default choice for a user should be safe. If users need to run a less secure
   218  configuration of the sandbox for the sake of performance or application
   219  compatibility, they must make the choice explicitly.
   220  
   221  An example of this might be a networking application that is performance
   222  sensitive. Instead of using the safer, Go-based Netstack in the Sentry, the
   223  untrusted container can instead use the host Linux networking stack directly.
   224  However, this means the untrusted container will be directly interacting with
   225  the host, without the safety benefits of the sandbox. It also means that an
   226  attack could directly compromise the host through his path.
   227  
   228  These less secure configurations are **not** the default. In fact, the user must
   229  take action to change the configuration and run in a less secure mode.
   230  Additionally, these actions make it very obvious that a less secure
   231  configuration is being used.
   232  
   233  This can be as simple as forcing a default runtime flag option to the secure
   234  option. gVisor does this by always using its internal netstack by default.
   235  However, for certain performance sensitive applications, we allow the usage of
   236  the host OS networking stack, but it requires the user to actively set a
   237  flag[^10].
   238  
   239  # Technology Choices
   240  
   241  Technology choices for gVisor mainly involve things that will give us a security
   242  boundary.
   243  
   244  At a higher level, boundaries in software might be describing a great many
   245  things. It may be discussing the boundaries between threads, boundaries between
   246  processes, boundaries between CPU privilege levels, and more.
   247  
   248  Security boundaries are interfaces that are designed and built so that entire
   249  classes of bugs/vulnerabilities are eliminated.
   250  
   251  For example, the Sentry and Gofers are implemented using Go. Go was chosen for a
   252  number of the features it provided. Go is a fast, statically-typed, compiled
   253  language that has efficient multi-threading support, garbage collection and a
   254  constrained set of "unsafe" operations.
   255  
   256  Using these features enabled safe array and pointer handling. This means entire
   257  classes of vulnerabilities were eliminated, such as buffer overflows and
   258  use-after-free.
   259  
   260  Another example is our use of very strict syscall switching to ensure that the
   261  Sentry is always the first software component that parses and interprets the
   262  calls being made by the untrusted container. Here is an instance where different
   263  platforms use different solutions, but all of them share this common trait,
   264  whether it is through the use of ptrace "a la PTRACE_ATTACH"[^11] or kvm's
   265  ring0[^12].
   266  
   267  Finally, one of the most restrictive choices was to use seccomp, to restrict the
   268  Sentry from being able to open or create a file descriptor on the host. All file
   269  I/O is required to go through Gofers. Preventing the opening or creation of file
   270  descriptions eliminates whole categories of bugs around file permissions
   271  [like this one](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-4557)[^13].
   272  
   273  # To be continued - Part 2
   274  
   275  In part 2 of this blog post, we will explore gVisor from an attacker's point of
   276  view. We will use it as an opportunity to examine the specific strengths and
   277  weaknesses of each gVisor component.
   278  
   279  We will also use it to introduce Google's Vulnerability Reward Program[^14], and
   280  other ways the community can contribute to help make gVisor safe, fast and
   281  stable.
   282  <br>
   283  <br>
   284  **Updated (2021-07-14):** this post was updated to use more inclusive language.
   285  <br>
   286  
   287  --------------------------------------------------------------------------------
   288  
   289  [^1]: [https://en.wikipedia.org/wiki/Secure_by_design](https://en.wikipedia.org/wiki/Secure_by_design)
   290  [^2]: [https://gvisor.dev/docs/architecture_guide](https://gvisor.dev/docs/architecture_guide/)
   291  [^3]: [https://github.com/google/gvisor/blob/master/pkg/sentry/syscalls/linux/linux64_amd64.go](https://github.com/google/gvisor/blob/master/pkg/sentry/syscalls/syscalls.go)
   292  
   293  <!-- mdformat off(mdformat formats this into multiple lines) -->
   294  [^4]: Internally that is, it doesn't call to the Host OS to implement them, in fact that is explicitly disallowed, more on that in the future.
   295  <!-- mdformat on -->
   296  
   297  [^5]: [https://elixir.bootlin.com/linux/latest/source/arch/x86/entry/syscalls/syscall_64.tbl#L345](https://elixir.bootlin.com/linux/latest/source/arch/x86/entry/syscalls/syscall_64.tbl#L345)
   298  [^6]: [https://github.com/google/gvisor/tree/master/runsc/boot/filter](https://github.com/google/gvisor/tree/master/runsc/boot/filter)
   299  [^7]: [https://en.wikipedia.org/wiki/Dirty_COW](https://en.wikipedia.org/wiki/Dirty_COW)
   300  [^8]: [https://github.com/google/gvisor/blob/master/runsc/boot/config.go](https://github.com/google/gvisor/blob/master/runsc/boot/config.go)
   301  
   302  <!-- mdformat off(mdformat breaks this url by escaping the parenthesis) -->
   303  [^9]: [https://en.wikipedia.org/wiki/9P_(protocol)](https://en.wikipedia.org/wiki/9P_(protocol))
   304  <!-- mdformat on -->
   305  
   306  [^10]: [https://gvisor.dev/docs/user_guide/networking/#network-passthrough](https://gvisor.dev/docs/user_guide/networking/#network-passthrough)
   307  [^11]: [https://github.com/google/gvisor/blob/c7e901f47a09eaac56bd4813227edff016fa6bff/pkg/sentry/platform/ptrace/subprocess.go#L390](https://github.com/google/gvisor/blob/c7e901f47a09eaac56bd4813227edff016fa6bff/pkg/sentry/platform/ptrace/subprocess.go#L390)
   308  [^12]: [https://github.com/google/gvisor/blob/c7e901f47a09eaac56bd4813227edff016fa6bff/pkg/sentry/platform/ring0/kernel_amd64.go#L182](https://github.com/google/gvisor/blob/c7e901f47a09eaac56bd4813227edff016fa6bff/pkg/sentry/platform/ring0/kernel_amd64.go#L182)
   309  [^13]: [https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-4557](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-4557)
   310  [^14]: [https://www.google.com/about/appsecurity/reward-program/index.html](https://www.google.com/about/appsecurity/reward-program/index.html)