Blame bind-mount.c

Packit Service fee338
/* bubblewrap
Packit Service fee338
 * Copyright (C) 2016 Alexander Larsson
Packit Service fee338
 *
Packit Service fee338
 * This program is free software; you can redistribute it and/or
Packit Service fee338
 * modify it under the terms of the GNU Lesser General Public
Packit Service fee338
 * License as published by the Free Software Foundation; either
Packit Service fee338
 * version 2 of the License, or (at your option) any later version.
Packit Service fee338
 *
Packit Service fee338
 * This library is distributed in the hope that it will be useful,
Packit Service fee338
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service fee338
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Packit Service fee338
 * Lesser General Public License for more details.
Packit Service fee338
 *
Packit Service fee338
 * You should have received a copy of the GNU Lesser General Public
Packit Service fee338
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Packit Service fee338
 *
Packit Service fee338
 */
Packit Service fee338
Packit Service fee338
#include "config.h"
Packit Service fee338
Packit Service fee338
#include <sys/mount.h>
Packit Service fee338
Packit Service fee338
#include "utils.h"
Packit Service fee338
#include "bind-mount.h"
Packit Service fee338
Packit Service fee338
static char *
Packit Service fee338
skip_token (char *line, bool eat_whitespace)
Packit Service fee338
{
Packit Service fee338
  while (*line != ' ' && *line != '\n')
Packit Service fee338
    line++;
Packit Service fee338
Packit Service fee338
  if (eat_whitespace && *line == ' ')
Packit Service fee338
    line++;
Packit Service fee338
Packit Service fee338
  return line;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static char *
Packit Service fee338
unescape_inline (char *escaped)
Packit Service fee338
{
Packit Service fee338
  char *unescaped, *res;
Packit Service fee338
  const char *end;
Packit Service fee338
Packit Service fee338
  res = escaped;
Packit Service fee338
  end = escaped + strlen (escaped);
Packit Service fee338
Packit Service fee338
  unescaped = escaped;
Packit Service fee338
  while (escaped < end)
Packit Service fee338
    {
Packit Service fee338
      if (*escaped == '\\')
Packit Service fee338
        {
Packit Service fee338
          *unescaped++ =
Packit Service fee338
            ((escaped[1] - '0') << 6) |
Packit Service fee338
            ((escaped[2] - '0') << 3) |
Packit Service fee338
            ((escaped[3] - '0') << 0);
Packit Service fee338
          escaped += 4;
Packit Service fee338
        }
Packit Service fee338
      else
Packit Service fee338
        {
Packit Service fee338
          *unescaped++ = *escaped++;
Packit Service fee338
        }
Packit Service fee338
    }
Packit Service fee338
  *unescaped = 0;
Packit Service fee338
  return res;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static bool
Packit Service fee338
match_token (const char *token, const char *token_end, const char *str)
Packit Service fee338
{
Packit Service fee338
  while (token != token_end && *token == *str)
Packit Service fee338
    {
Packit Service fee338
      token++;
Packit Service fee338
      str++;
Packit Service fee338
    }
Packit Service fee338
  if (token == token_end)
Packit Service fee338
    return *str == 0;
Packit Service fee338
Packit Service fee338
  return FALSE;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static unsigned long
Packit Service fee338
decode_mountoptions (const char *options)
Packit Service fee338
{
Packit Service fee338
  const char *token, *end_token;
Packit Service fee338
  int i;
Packit Service fee338
  unsigned long flags = 0;
Packit Service fee338
  static const struct  { int   flag;
Packit Service fee338
                         char *name;
Packit Service fee338
  } flags_data[] = {
Packit Service fee338
    { 0, "rw" },
Packit Service fee338
    { MS_RDONLY, "ro" },
Packit Service fee338
    { MS_NOSUID, "nosuid" },
Packit Service fee338
    { MS_NODEV, "nodev" },
Packit Service fee338
    { MS_NOEXEC, "noexec" },
Packit Service fee338
    { MS_NOATIME, "noatime" },
Packit Service fee338
    { MS_NODIRATIME, "nodiratime" },
Packit Service fee338
    { MS_RELATIME, "relatime" },
Packit Service fee338
    { 0, NULL }
Packit Service fee338
  };
Packit Service fee338
Packit Service fee338
  token = options;
Packit Service fee338
  do
Packit Service fee338
    {
Packit Service fee338
      end_token = strchr (token, ',');
Packit Service fee338
      if (end_token == NULL)
Packit Service fee338
        end_token = token + strlen (token);
Packit Service fee338
Packit Service fee338
      for (i = 0; flags_data[i].name != NULL; i++)
Packit Service fee338
        {
Packit Service fee338
          if (match_token (token, end_token, flags_data[i].name))
Packit Service fee338
            {
Packit Service fee338
              flags |= flags_data[i].flag;
Packit Service fee338
              break;
Packit Service fee338
            }
Packit Service fee338
        }
Packit Service fee338
Packit Service fee338
      if (*end_token != 0)
Packit Service fee338
        token = end_token + 1;
Packit Service fee338
      else
Packit Service fee338
        token = NULL;
Packit Service fee338
    }
Packit Service fee338
  while (token != NULL);
Packit Service fee338
Packit Service fee338
  return flags;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
typedef struct MountInfo MountInfo;
Packit Service fee338
struct MountInfo {
Packit Service fee338
  char *mountpoint;
Packit Service fee338
  unsigned long options;
Packit Service fee338
};
Packit Service fee338
Packit Service fee338
typedef MountInfo *MountTab;
Packit Service fee338
Packit Service fee338
static void
Packit Service fee338
mount_tab_free (MountTab tab)
Packit Service fee338
{
Packit Service fee338
  int i;
Packit Service fee338
Packit Service fee338
  for (i = 0; tab[i].mountpoint != NULL; i++)
Packit Service fee338
    free (tab[i].mountpoint);
Packit Service fee338
  free (tab);
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static inline void
Packit Service fee338
cleanup_mount_tabp (void *p)
Packit Service fee338
{
Packit Service fee338
  void **pp = (void **) p;
Packit Service fee338
Packit Service fee338
  if (*pp)
Packit Service fee338
    mount_tab_free ((MountTab)*pp);
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
#define cleanup_mount_tab __attribute__((cleanup (cleanup_mount_tabp)))
Packit Service fee338
Packit Service fee338
typedef struct MountInfoLine MountInfoLine;
Packit Service fee338
struct MountInfoLine {
Packit Service fee338
  const char *mountpoint;
Packit Service fee338
  const char *options;
Packit Service fee338
  bool covered;
Packit Service fee338
  int id;
Packit Service fee338
  int parent_id;
Packit Service fee338
  MountInfoLine *first_child;
Packit Service fee338
  MountInfoLine *next_sibling;
Packit Service fee338
};
Packit Service fee338
Packit Service fee338
static unsigned int
Packit Service fee338
count_lines (const char *data)
Packit Service fee338
{
Packit Service fee338
  unsigned int count = 0;
Packit Service fee338
  const char *p = data;
Packit Service fee338
Packit Service fee338
  while (*p != 0)
Packit Service fee338
    {
Packit Service fee338
      if (*p == '\n')
Packit Service fee338
        count++;
Packit Service fee338
      p++;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  /* If missing final newline, add one */
Packit Service fee338
  if (p > data && *(p-1) != '\n')
Packit Service fee338
    count++;
Packit Service fee338
Packit Service fee338
  return count;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static int
Packit Service fee338
count_mounts (MountInfoLine *line)
Packit Service fee338
{
Packit Service fee338
  MountInfoLine *child;
Packit Service fee338
  int res = 0;
Packit Service fee338
Packit Service fee338
  if (!line->covered)
Packit Service fee338
    res += 1;
Packit Service fee338
Packit Service fee338
  child = line->first_child;
Packit Service fee338
  while (child != NULL)
Packit Service fee338
    {
Packit Service fee338
      res += count_mounts (child);
Packit Service fee338
      child = child->next_sibling;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  return res;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static MountInfo *
Packit Service fee338
collect_mounts (MountInfo *info, MountInfoLine *line)
Packit Service fee338
{
Packit Service fee338
  MountInfoLine *child;
Packit Service fee338
Packit Service fee338
  if (!line->covered)
Packit Service fee338
    {
Packit Service fee338
      info->mountpoint = xstrdup (line->mountpoint);
Packit Service fee338
      info->options = decode_mountoptions (line->options);
Packit Service fee338
      info ++;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  child = line->first_child;
Packit Service fee338
  while (child != NULL)
Packit Service fee338
    {
Packit Service fee338
      info = collect_mounts (info, child);
Packit Service fee338
      child = child->next_sibling;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  return info;
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
static MountTab
Packit Service fee338
parse_mountinfo (int  proc_fd,
Packit Service fee338
                 const char *root_mount)
Packit Service fee338
{
Packit Service fee338
  cleanup_free char *mountinfo = NULL;
Packit Service fee338
  cleanup_free MountInfoLine *lines = NULL;
Packit Service fee338
  cleanup_free MountInfoLine **by_id = NULL;
Packit Service fee338
  cleanup_mount_tab MountTab mount_tab = NULL;
Packit Service fee338
  MountInfo *end_tab;
Packit Service fee338
  int n_mounts;
Packit Service fee338
  char *line;
Packit Service fee338
  int i;
Packit Service fee338
  int max_id;
Packit Service fee338
  unsigned int n_lines;
Packit Service fee338
  int root;
Packit Service fee338
Packit Service fee338
  mountinfo = load_file_at (proc_fd, "self/mountinfo");
Packit Service fee338
  if (mountinfo == NULL)
Packit Service fee338
    die_with_error ("Can't open /proc/self/mountinfo");
Packit Service fee338
Packit Service fee338
  n_lines = count_lines (mountinfo);
Packit Service fee338
  lines = xcalloc (n_lines * sizeof (MountInfoLine));
Packit Service fee338
Packit Service fee338
  max_id = 0;
Packit Service fee338
  line = mountinfo;
Packit Service fee338
  i = 0;
Packit Service fee338
  root = -1;
Packit Service fee338
  while (*line != 0)
Packit Service fee338
    {
Packit Service fee338
      int rc, consumed = 0;
Packit Service fee338
      unsigned int maj, min;
Packit Service fee338
      char *end;
Packit Service fee338
      char *rest;
Packit Service fee338
      char *mountpoint;
Packit Service fee338
      char *mountpoint_end;
Packit Service fee338
      char *options;
Packit Service fee338
      char *options_end;
Packit Service fee338
      char *next_line;
Packit Service fee338
Packit Service fee338
      assert (i < n_lines);
Packit Service fee338
Packit Service fee338
      end = strchr (line, '\n');
Packit Service fee338
      if (end != NULL)
Packit Service fee338
        {
Packit Service fee338
          *end = 0;
Packit Service fee338
          next_line = end + 1;
Packit Service fee338
        }
Packit Service fee338
      else
Packit Service fee338
        next_line = line + strlen (line);
Packit Service fee338
Packit Service fee338
      rc = sscanf (line, "%d %d %u:%u %n", &lines[i].id, &lines[i].parent_id, &maj, &min, &consumed);
Packit Service fee338
      if (rc != 4)
Packit Service fee338
        die ("Can't parse mountinfo line");
Packit Service fee338
      rest = line + consumed;
Packit Service fee338
Packit Service fee338
      rest = skip_token (rest, TRUE); /* mountroot */
Packit Service fee338
      mountpoint = rest;
Packit Service fee338
      rest = skip_token (rest, FALSE); /* mountpoint */
Packit Service fee338
      mountpoint_end = rest++;
Packit Service fee338
      options = rest;
Packit Service fee338
      rest = skip_token (rest, FALSE); /* vfs options */
Packit Service fee338
      options_end = rest;
Packit Service fee338
Packit Service fee338
      *mountpoint_end = 0;
Packit Service fee338
      lines[i].mountpoint = unescape_inline (mountpoint);
Packit Service fee338
Packit Service fee338
      *options_end = 0;
Packit Service fee338
      lines[i].options = options;
Packit Service fee338
Packit Service fee338
      if (lines[i].id > max_id)
Packit Service fee338
        max_id = lines[i].id;
Packit Service fee338
      if (lines[i].parent_id > max_id)
Packit Service fee338
        max_id = lines[i].parent_id;
Packit Service fee338
Packit Service fee338
      if (path_equal (lines[i].mountpoint, root_mount))
Packit Service fee338
        root = i;
Packit Service fee338
Packit Service fee338
      i++;
Packit Service fee338
      line = next_line;
Packit Service fee338
    }
Packit Service fee338
  assert (i == n_lines);
Packit Service fee338
Packit Service fee338
  if (root == -1)
Packit Service fee338
    {
Packit Service fee338
      mount_tab = xcalloc (sizeof (MountInfo) * (1));
Packit Service fee338
      return steal_pointer (&mount_tab);
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  by_id = xcalloc ((max_id + 1) * sizeof (MountInfoLine*));
Packit Service fee338
  for (i = 0; i < n_lines; i++)
Packit Service fee338
    by_id[lines[i].id] = &lines[i];
Packit Service fee338
Packit Service fee338
  for (i = 0; i < n_lines; i++)
Packit Service fee338
    {
Packit Service fee338
      MountInfoLine *this = &lines[i];
Packit Service fee338
      MountInfoLine *parent = by_id[this->parent_id];
Packit Service fee338
      MountInfoLine **to_sibling;
Packit Service fee338
      MountInfoLine *sibling;
Packit Service fee338
      bool covered = FALSE;
Packit Service fee338
Packit Service fee338
      if (!has_path_prefix (this->mountpoint, root_mount))
Packit Service fee338
        continue;
Packit Service fee338
Packit Service fee338
      if (parent == NULL)
Packit Service fee338
        continue;
Packit Service fee338
Packit Service fee338
      if (strcmp (parent->mountpoint, this->mountpoint) == 0)
Packit Service fee338
        parent->covered = TRUE;
Packit Service fee338
Packit Service fee338
      to_sibling = &parent->first_child;
Packit Service fee338
      sibling = parent->first_child;
Packit Service fee338
      while (sibling != NULL)
Packit Service fee338
        {
Packit Service fee338
          /* If this mountpoint is a path prefix of the sibling,
Packit Service fee338
           * say this->mp=/foo/bar and sibling->mp=/foo, then it is
Packit Service fee338
           * covered by the sibling, and we drop it. */
Packit Service fee338
          if (has_path_prefix (this->mountpoint, sibling->mountpoint))
Packit Service fee338
            {
Packit Service fee338
              covered = TRUE;
Packit Service fee338
              break;
Packit Service fee338
            }
Packit Service fee338
Packit Service fee338
          /* If the sibling is a path prefix of this mount point,
Packit Service fee338
           * say this->mp=/foo and sibling->mp=/foo/bar, then the sibling
Packit Service fee338
           * is covered, and we drop it.
Packit Service fee338
            */
Packit Service fee338
          if (has_path_prefix (sibling->mountpoint, this->mountpoint))
Packit Service fee338
            *to_sibling = sibling->next_sibling;
Packit Service fee338
          else
Packit Service fee338
            to_sibling = &sibling->next_sibling;
Packit Service fee338
          sibling = sibling->next_sibling;
Packit Service fee338
        }
Packit Service fee338
Packit Service fee338
      if (covered)
Packit Service fee338
          continue;
Packit Service fee338
Packit Service fee338
      *to_sibling = this;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  n_mounts = count_mounts (&lines[root]);
Packit Service fee338
  mount_tab = xcalloc (sizeof (MountInfo) * (n_mounts + 1));
Packit Service fee338
Packit Service fee338
  end_tab = collect_mounts (&mount_tab[0], &lines[root]);
Packit Service fee338
  assert (end_tab == &mount_tab[n_mounts]);
Packit Service fee338
Packit Service fee338
  return steal_pointer (&mount_tab);
Packit Service fee338
}
Packit Service fee338
Packit Service fee338
int
Packit Service fee338
bind_mount (int           proc_fd,
Packit Service fee338
            const char   *src,
Packit Service fee338
            const char   *dest,
Packit Service fee338
            bind_option_t options)
Packit Service fee338
{
Packit Service fee338
  bool readonly = (options & BIND_READONLY) != 0;
Packit Service fee338
  bool devices = (options & BIND_DEVICES) != 0;
Packit Service fee338
  bool recursive = (options & BIND_RECURSIVE) != 0;
Packit Service fee338
  unsigned long current_flags, new_flags;
Packit Service fee338
  cleanup_mount_tab MountTab mount_tab = NULL;
Packit Service fee338
  cleanup_free char *resolved_dest = NULL;
Packit Service fee338
  int i;
Packit Service fee338
Packit Service fee338
  if (src)
Packit Service fee338
    {
Packit Service fee338
      if (mount (src, dest, NULL, MS_BIND | (recursive ? MS_REC : 0), NULL) != 0)
Packit Service fee338
        return 1;
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  /* The mount operation will resolve any symlinks in the destination
Packit Service fee338
     path, so to find it in the mount table we need to do that too. */
Packit Service fee338
  resolved_dest = realpath (dest, NULL);
Packit Service fee338
  if (resolved_dest == NULL)
Packit Service fee338
    return 2;
Packit Service fee338
Packit Service fee338
  mount_tab = parse_mountinfo (proc_fd, resolved_dest);
Packit Service fee338
  if (mount_tab[0].mountpoint == NULL)
Packit Service fee338
    {
Packit Service fee338
      errno = EINVAL;
Packit Service fee338
      return 2; /* No mountpoint at dest */
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  assert (path_equal (mount_tab[0].mountpoint, resolved_dest));
Packit Service fee338
  current_flags = mount_tab[0].options;
Packit Service fee338
  new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0);
Packit Service fee338
  if (new_flags != current_flags &&
Packit Service fee338
      mount ("none", resolved_dest,
Packit Service fee338
             NULL, MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
Packit Service fee338
    return 3;
Packit Service fee338
Packit Service fee338
  /* We need to work around the fact that a bind mount does not apply the flags, so we need to manually
Packit Service fee338
   * apply the flags to all submounts in the recursive case.
Packit Service fee338
   * Note: This does not apply the flags to mounts which are later propagated into this namespace.
Packit Service fee338
   */
Packit Service fee338
  if (recursive)
Packit Service fee338
    {
Packit Service fee338
      for (i = 1; mount_tab[i].mountpoint != NULL; i++)
Packit Service fee338
        {
Packit Service fee338
          current_flags = mount_tab[i].options;
Packit Service fee338
          new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0);
Packit Service fee338
          if (new_flags != current_flags &&
Packit Service fee338
              mount ("none", mount_tab[i].mountpoint,
Packit Service fee338
                     NULL, MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
Packit Service fee338
            {
Packit Service fee338
              /* If we can't read the mountpoint we can't remount it, but that should
Packit Service fee338
                 be safe to ignore because its not something the user can access. */
Packit Service fee338
              if (errno != EACCES)
Packit Service fee338
                return 5;
Packit Service fee338
            }
Packit Service fee338
        }
Packit Service fee338
    }
Packit Service fee338
Packit Service fee338
  return 0;
Packit Service fee338
}