github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap-device-helper/snap-device-helper.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 <errno.h>
    18  #include <libgen.h>
    19  #include <limits.h>
    20  #include <stdbool.h>
    21  #include <stddef.h>
    22  #include <stdio.h>
    23  #include <stdlib.h>
    24  #include <string.h>
    25  #include <sys/stat.h>
    26  #include <unistd.h>
    27  
    28  #include "../libsnap-confine-private/cleanup-funcs.h"
    29  #include "../libsnap-confine-private/device-cgroup-support.h"
    30  #include "../libsnap-confine-private/snap.h"
    31  #include "../libsnap-confine-private/string-utils.h"
    32  #include "../libsnap-confine-private/utils.h"
    33  
    34  #include "snap-device-helper.h"
    35  
    36  static unsigned long must_strtoul(char *str) {
    37      char *end = str;
    38      unsigned long val = strtoul(str, &end, 10);
    39      if (*end != '\0') {
    40          die("malformed number \"%s\"", str);
    41      }
    42      return val;
    43  }
    44  
    45  /* udev_to_security_tag converts a udev tag (snap_foo_bar) to security tag
    46   * (snap.foo.bar) */
    47  static char *udev_to_security_tag(const char *udev_tag) {
    48      if (!sc_startswith(udev_tag, "snap_")) {
    49          die("malformed tag \"%s\"", udev_tag);
    50      }
    51      char *tag = sc_strdup(udev_tag);
    52      /* possible udev tags are:
    53       * snap_foo_bar
    54       * snap_foo_instance_bar
    55       * snap_foo_hook_hookname
    56       * snap_foo_instance_hook_hookname
    57       * convert those to:
    58       * snap.foo.bar
    59       * snap.foo_instance.bar
    60       * snap.foo.hook.hookname
    61       * snap.foo_instance.hook.hookname
    62       */
    63      size_t tag_len = strlen(tag);
    64      if (tag_len < strlen("snap_a_b") || tag_len > SNAP_SECURITY_TAG_MAX_LEN) {
    65          die("tag \"%s\" length %zu is incorrect", udev_tag, tag_len);
    66      }
    67      const size_t snap_prefix_len = strlen("snap_");
    68      /* we know that the tag at least has a snap_ prefix because it was checked
    69       * before */
    70      tag[snap_prefix_len - 1] = '.';
    71      char *snap_name_start = tag + snap_prefix_len;
    72      char *snap_name_end = NULL;
    73  
    74      /* find the last separator */
    75      char *last_sep = strrchr(tag, '_');
    76      if (last_sep == NULL) {
    77          die("missing app name in tag \"%s\"", udev_tag);
    78      }
    79      *last_sep = '.';
    80      /* we are left with the following possibilities:
    81       * snap.foo.bar
    82       * snap.foo_instance.bar
    83       * snap.foo_instance_hook.hookname
    84       * snap.foo_hook.hookname
    85       */
    86      char *more_sep = strchr(tag, '_');
    87      if (more_sep == NULL) {
    88          /* no more separators, we have snap.foo.bar */
    89          snap_name_end = last_sep;
    90      } else {
    91          /* we are left with the following possibilities:
    92           * snap.foo_instance.bar
    93           * snap.foo_instance_hook.hookname
    94           * snap.foo_hook.hookname
    95           */
    96  
    97          /* do we have another separator? */
    98          char *another_sep = strchr(more_sep + 1, '_');
    99          if (another_sep == NULL) {
   100              /* no, so we are left with the following possibilities:
   101               * snap.foo_instance.bar
   102               * snap.foo_hook.hookname
   103               *
   104               * there is ambiguity and we cannot correctly handle an instance named
   105               * 'hook' as snap.foo_hook.bar could be snap.foo.hook.bar or
   106               * snap.foo_hook.bar, for simplicity assume snap.foo.hook.bar more likely.
   107               */
   108              if (sc_startswith(more_sep, "_hook.")) {
   109                  /* snap.foo_hook.bar -> snap.foo.hook.bar */
   110                  *more_sep = '.';
   111                  snap_name_end = more_sep;
   112              } else {
   113                  snap_name_end = last_sep;
   114              }
   115          } else {
   116              /* we have found 2 separators, so this is the only possibility:
   117               * snap.foo_instance_hook.hookname
   118               * which should be converted to:
   119               * snap.foo_instance.hook.hookname
   120               */
   121              *another_sep = '.';
   122              snap_name_end = another_sep;
   123          }
   124      }
   125      if (snap_name_end <= snap_name_start) {
   126          die("missing snap name in tag \"%s\"", udev_tag);
   127      }
   128  
   129      /* let's validate the tag, but we need to extract the snap name first */
   130      char snap_instance[SNAP_INSTANCE_LEN + 1] = {0};
   131      size_t snap_instance_len = (size_t)(snap_name_end - snap_name_start);
   132      if (snap_instance_len >= sizeof(snap_instance)) {
   133          die("snap instance of tag \"%s\" is too long", udev_tag);
   134      }
   135      memcpy(snap_instance, snap_name_start, snap_instance_len);
   136      debug("snap instance \"%s\"", snap_instance);
   137  
   138      if (!sc_security_tag_validate(tag, snap_instance)) {
   139          die("security tag \"%s\" for snap \"%s\" is not valid", tag, snap_instance);
   140      }
   141  
   142      return tag;
   143  }
   144  
   145  /* sysroot can be mocked in tests */
   146  const char *sysroot = "/";
   147  
   148  int snap_device_helper_run(const struct sdh_invocation *inv) {
   149      const char *action = inv->action;
   150      const char *udev_tagname = inv->tagname;
   151      const char *devpath = inv->devpath;
   152      const char *majmin = inv->majmin;
   153  
   154      bool allow = false;
   155  
   156      if (strlen(majmin) < 3) {
   157          die("no or malformed major/minor \"%s\"", majmin);
   158      }
   159      if (strlen(devpath) <= strlen("/devices/")) {
   160          die("no or malformed devpath \"%s\"", devpath);
   161      }
   162      if (sc_streq(action, "add") || sc_streq(action, "change")) {
   163          allow = true;
   164      } else if (sc_streq(action, "remove")) {
   165          allow = false;
   166      } else {
   167          die("ERROR: unknown action \"%s\"", action);
   168      }
   169  
   170      char *security_tag SC_CLEANUP(sc_cleanup_string) = udev_to_security_tag(udev_tagname);
   171  
   172      int devtype = S_IFCHR;
   173      /* find out the actual subsystem */
   174      char sysdevsubsystem[PATH_MAX] = {0};
   175      char fullsubsystem[PATH_MAX] = {0};
   176      sc_must_snprintf(sysdevsubsystem, sizeof(sysdevsubsystem), "%s/sys/%s/subsystem", sysroot, devpath);
   177      if (readlink(sysdevsubsystem, fullsubsystem, sizeof(fullsubsystem)) < 0) {
   178          die("cannot read symlink %s", sysdevsubsystem);
   179      }
   180      char *subsystem = basename(fullsubsystem);
   181      if (sc_streq(subsystem, "block")) {
   182          devtype = S_IFBLK;
   183      }
   184      sc_device_cgroup *cgroup = sc_device_cgroup_new(security_tag, SC_DEVICE_CGROUP_FROM_EXISTING);
   185      if (!cgroup) {
   186          if (errno == ENOENT) {
   187              debug("device cgroup does not exist");
   188              return 0;
   189          }
   190          die("cannot create device cgroup wrapper");
   191      }
   192  
   193      /* the format is <major>:<minor> */
   194      char *major SC_CLEANUP(sc_cleanup_string) = sc_strdup(majmin);
   195      char *sep = strchr(major, ':');
   196      // sep is always \0 terminated so this checks if the part after ":" is empty
   197      if (sep == NULL || sep[1] == '\0') {
   198          /* not found, or a last character */
   199          die("malformed major:minor string: %s", major);
   200      }
   201      /* set an end for the major number string */
   202      *sep = '\0';
   203      sep++;
   204      char *minor = sep;
   205  
   206      int devmajor = must_strtoul(major);
   207      int devminor = must_strtoul(minor);
   208      debug("%s device type is %s, %d:%d", inv->action, (devtype == S_IFCHR) ? "char" : "block", devmajor, devminor);
   209      if (allow) {
   210          sc_device_cgroup_allow(cgroup, devtype, devmajor, devminor);
   211      } else {
   212          sc_device_cgroup_deny(cgroup, devtype, devmajor, devminor);
   213      }
   214  
   215      return 0;
   216  }