github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/libsnap-confine-private/device-cgroup-support.c (about)

     1  /*
     2   * Copyright (C) 2021 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  #include "config.h"
    18  
    19  #include <errno.h>
    20  #include <fcntl.h>
    21  #include <stdarg.h>
    22  #include <string.h>
    23  #include <sys/stat.h>
    24  
    25  #include "cgroup-support.h"
    26  #include "cleanup-funcs.h"
    27  #include "snap.h"
    28  #include "string-utils.h"
    29  #include "utils.h"
    30  
    31  #include "device-cgroup-support.h"
    32  
    33  typedef struct sc_cgroup_fds {
    34      int devices_allow_fd;
    35      int devices_deny_fd;
    36      int cgroup_procs_fd;
    37  } sc_cgroup_fds;
    38  
    39  static sc_cgroup_fds sc_cgroup_fds_new(void) {
    40      /* Note that -1 is the neutral value for a file descriptor.
    41       * This is relevant as a cleanup handler for sc_cgroup_fds,
    42       * closes all file descriptors that are not -1. */
    43      sc_cgroup_fds empty = {-1, -1, -1};
    44      return empty;
    45  }
    46  
    47  struct sc_device_cgroup {
    48      bool is_v2;
    49      char *security_tag;
    50      union {
    51          struct {
    52              sc_cgroup_fds fds;
    53          } v1;
    54          struct {
    55              int cgroup_fd;
    56              int devmap_fd;
    57              char *tag;
    58          } v2;
    59      };
    60  };
    61  
    62  __attribute__((format(printf, 2, 3))) static void sc_dprintf(int fd, const char *format, ...);
    63  
    64  static int sc_udev_open_cgroup_v1(const char *security_tag, int flags, sc_cgroup_fds *fds);
    65  static void sc_cleanup_cgroup_fds(sc_cgroup_fds *fds);
    66  
    67  static int _sc_cgroup_v1_init(sc_device_cgroup *self, int flags) {
    68      self->v1.fds = sc_cgroup_fds_new();
    69  
    70      /* initialize to something sane */
    71      if (sc_udev_open_cgroup_v1(self->security_tag, flags, &self->v1.fds) < 0) {
    72          if (flags == SC_DEVICE_CGROUP_FROM_EXISTING) {
    73              return -1;
    74          }
    75          die("cannot prepare cgroup v1 device hierarchy");
    76      }
    77      /* Deny device access by default.
    78       *
    79       * Write 'a' to devices.deny to remove all existing devices that were added
    80       * in previous launcher invocations, then add the static and assigned
    81       * devices. This ensures that at application launch the cgroup only has
    82       * what is currently assigned. */
    83      sc_dprintf(self->v1.fds.devices_deny_fd, "a");
    84      return 0;
    85  }
    86  
    87  static void _sc_cgroup_v1_close(sc_device_cgroup *self) { sc_cleanup_cgroup_fds(&self->v1.fds); }
    88  
    89  static void _sc_cgroup_v1_action(int fd, int kind, int major, int minor) {
    90      if ((uint32_t)minor != SC_DEVICE_MINOR_ANY) {
    91          sc_dprintf(fd, "%c %u:%u rwm\n", (kind == S_IFCHR) ? 'c' : 'b', major, minor);
    92      } else {
    93          /* use a mask to allow/deny all minor devices for that major */
    94          sc_dprintf(fd, "%c %u:* rwm\n", (kind == S_IFCHR) ? 'c' : 'b', major);
    95      }
    96  }
    97  
    98  static void _sc_cgroup_v1_allow(sc_device_cgroup *self, int kind, int major, int minor) {
    99      _sc_cgroup_v1_action(self->v1.fds.devices_allow_fd, kind, major, minor);
   100  }
   101  
   102  static void _sc_cgroup_v1_deny(sc_device_cgroup *self, int kind, int major, int minor) {
   103      _sc_cgroup_v1_action(self->v1.fds.devices_deny_fd, kind, major, minor);
   104  }
   105  
   106  static void _sc_cgroup_v1_attach_pid(sc_device_cgroup *self, pid_t pid) {
   107      sc_dprintf(self->v1.fds.cgroup_procs_fd, "%i\n", getpid());
   108  }
   109  
   110  static void sc_device_cgroup_close(sc_device_cgroup *self);
   111  
   112  sc_device_cgroup *sc_device_cgroup_new(const char *security_tag, int flags) {
   113      sc_device_cgroup *self = calloc(1, sizeof(sc_device_cgroup));
   114      if (self == NULL) {
   115          die("cannot allocate device cgroup wrapper");
   116      }
   117      self->is_v2 = sc_cgroup_is_v2();
   118      self->security_tag = sc_strdup(security_tag);
   119  
   120      int ret = 0;
   121      if (!self->is_v2) {
   122          ret = _sc_cgroup_v1_init(self, flags);
   123      }
   124  
   125      if (ret < 0) {
   126          sc_device_cgroup_close(self);
   127          return NULL;
   128      }
   129  
   130      return self;
   131  }
   132  
   133  static void sc_device_cgroup_close(sc_device_cgroup *self) {
   134      if (!self->is_v2) {
   135          _sc_cgroup_v1_close(self);
   136      }
   137      sc_cleanup_string(&self->security_tag);
   138      free(self);
   139  }
   140  
   141  void sc_device_cgroup_cleanup(sc_device_cgroup **self) {
   142      if (*self == NULL) {
   143          return;
   144      }
   145      sc_device_cgroup_close(*self);
   146      *self = NULL;
   147  }
   148  
   149  int sc_device_cgroup_allow(sc_device_cgroup *self, int kind, int major, int minor) {
   150      if (kind != S_IFCHR && kind != S_IFBLK) {
   151          die("unsupported device kind 0x%04x", kind);
   152      }
   153      if (!self->is_v2) {
   154          _sc_cgroup_v1_allow(self, kind, major, minor);
   155      }
   156      return 0;
   157  }
   158  
   159  int sc_device_cgroup_deny(sc_device_cgroup *self, int kind, int major, int minor) {
   160      if (kind != S_IFCHR && kind != S_IFBLK) {
   161          die("unsupported device kind 0x%04x", kind);
   162      }
   163      if (!self->is_v2) {
   164          _sc_cgroup_v1_deny(self, kind, major, minor);
   165      }
   166      return 0;
   167  }
   168  
   169  int sc_device_cgroup_attach_pid(sc_device_cgroup *self, pid_t pid) {
   170      if (!self->is_v2) {
   171          _sc_cgroup_v1_attach_pid(self, pid);
   172      }
   173      return 0;
   174  }
   175  
   176  static void sc_dprintf(int fd, const char *format, ...) {
   177      va_list ap1;
   178      va_list ap2;
   179      int n_expected, n_actual;
   180  
   181      va_start(ap1, format);
   182      va_copy(ap2, ap1);
   183      n_expected = vsnprintf(NULL, 0, format, ap2);
   184      n_actual = vdprintf(fd, format, ap1);
   185      if (n_actual == -1 || n_expected != n_actual) {
   186          die("cannot write to fd %d", fd);
   187      }
   188      va_end(ap2);
   189      va_end(ap1);
   190  }
   191  
   192  static int sc_udev_open_cgroup_v1(const char *security_tag, int flags, sc_cgroup_fds *fds) {
   193      /* Open /sys/fs/cgroup */
   194      const char *cgroup_path = "/sys/fs/cgroup";
   195      int SC_CLEANUP(sc_cleanup_close) cgroup_fd = -1;
   196      cgroup_fd = open(cgroup_path, O_PATH | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
   197      if (cgroup_fd < 0) {
   198          die("cannot open %s", cgroup_path);
   199      }
   200  
   201      /* Open devices relative to /sys/fs/cgroup */
   202      const char *devices_relpath = "devices";
   203      int SC_CLEANUP(sc_cleanup_close) devices_fd = -1;
   204      devices_fd = openat(cgroup_fd, devices_relpath, O_PATH | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
   205      if (devices_fd < 0) {
   206          die("cannot open %s/%s", cgroup_path, devices_relpath);
   207      }
   208  
   209      /* Open snap.$SNAP_NAME.$APP_NAME relative to /sys/fs/cgroup/devices,
   210       * creating the directory if necessary. Note that we always chown the
   211       * resulting directory to root:root. */
   212      const char *security_tag_relpath = security_tag;
   213      sc_identity old = sc_set_effective_identity(sc_root_group_identity());
   214      if (mkdirat(devices_fd, security_tag_relpath, 0755) < 0) {
   215          if (errno != EEXIST) {
   216              die("cannot create directory %s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath);
   217          }
   218      }
   219      (void)sc_set_effective_identity(old);
   220  
   221      int SC_CLEANUP(sc_cleanup_close) security_tag_fd = -1;
   222      security_tag_fd = openat(devices_fd, security_tag_relpath, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
   223      if (security_tag_fd < 0) {
   224          die("cannot open %s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath);
   225      }
   226  
   227      /* Open devices.allow relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */
   228      const char *devices_allow_relpath = "devices.allow";
   229      int SC_CLEANUP(sc_cleanup_close) devices_allow_fd = -1;
   230      devices_allow_fd = openat(security_tag_fd, devices_allow_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
   231      if (devices_allow_fd < 0) {
   232          die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, devices_allow_relpath);
   233      }
   234  
   235      /* Open devices.deny relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */
   236      const char *devices_deny_relpath = "devices.deny";
   237      int SC_CLEANUP(sc_cleanup_close) devices_deny_fd = -1;
   238      devices_deny_fd = openat(security_tag_fd, devices_deny_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
   239      if (devices_deny_fd < 0) {
   240          if (flags == SC_DEVICE_CGROUP_FROM_EXISTING && errno == ENOENT) {
   241              return -1;
   242          }
   243          die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, devices_deny_relpath);
   244      }
   245  
   246      /* Open cgroup.procs relative to /sys/fs/cgroup/devices/snap.$SNAP_NAME.$APP_NAME */
   247      const char *cgroup_procs_relpath = "cgroup.procs";
   248      int SC_CLEANUP(sc_cleanup_close) cgroup_procs_fd = -1;
   249      cgroup_procs_fd = openat(security_tag_fd, cgroup_procs_relpath, O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
   250      if (cgroup_procs_fd < 0) {
   251          if (flags == SC_DEVICE_CGROUP_FROM_EXISTING && errno == ENOENT) {
   252              return -1;
   253          }
   254          die("cannot open %s/%s/%s/%s", cgroup_path, devices_relpath, security_tag_relpath, cgroup_procs_relpath);
   255      }
   256  
   257      /* Everything worked so pack the result and "move" the descriptors over so
   258       * that they are not closed by the cleanup functions associated with the
   259       * individual variables. */
   260      fds->devices_allow_fd = devices_allow_fd;
   261      fds->devices_deny_fd = devices_deny_fd;
   262      fds->cgroup_procs_fd = cgroup_procs_fd;
   263      /* Reset the locals so that they are not closed by the cleanup handlers. */
   264      devices_allow_fd = -1;
   265      devices_deny_fd = -1;
   266      cgroup_procs_fd = -1;
   267      return 0;
   268  }
   269  
   270  static void sc_cleanup_cgroup_fds(sc_cgroup_fds *fds) {
   271      if (fds != NULL) {
   272          sc_cleanup_close(&fds->devices_allow_fd);
   273          sc_cleanup_close(&fds->devices_deny_fd);
   274          sc_cleanup_close(&fds->cgroup_procs_fd);
   275      }
   276  }