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