github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/cmd/snap-seccomp-blacklist/snap-seccomp-blacklist.c (about)

     1  #include <errno.h>
     2  #include <fcntl.h>
     3  #include <limits.h>
     4  #include <stdarg.h>
     5  #include <stdbool.h>
     6  #include <stdio.h>
     7  #include <stdlib.h>
     8  #include <sys/ioctl.h>
     9  #include <sys/stat.h>
    10  #include <sys/types.h>
    11  #include <unistd.h>
    12  
    13  #include <seccomp.h>
    14  
    15  __attribute__((format(printf, 1, 2))) static void showerr(const char *fmt, ...);
    16  
    17  static void showerr(const char *fmt, ...) {
    18      va_list va;
    19      va_start(va, fmt);
    20      vfprintf(stderr, fmt, va);
    21      fputc('\n', stderr);
    22      va_end(va);
    23  }
    24  
    25  static int populate_filter(scmp_filter_ctx ctx, const uint32_t *arch_tags, size_t num_arch_tags) {
    26      int sc_err;
    27  
    28      /* If the native architecture is not one of the supported 64bit
    29       * architectures listed in main in le_arch_tags and be_arch_tags, then
    30       * remove it.
    31       *
    32       * Libseccomp automatically adds the native architecture to each new filter.
    33       * If the native architecture is a 32bit-one then we will hit a bug in libseccomp
    34       * and the generated BPF program is incorrect as described below. */
    35      uint32_t native_arch = seccomp_arch_native();
    36      bool remove_native_arch = true;
    37      for (size_t i = 0; i < num_arch_tags; ++i) {
    38          if (arch_tags[i] == native_arch) {
    39              remove_native_arch = false;
    40              break;
    41          }
    42      }
    43      if (remove_native_arch) {
    44          sc_err = seccomp_arch_remove(ctx, SCMP_ARCH_NATIVE);
    45          if (sc_err < 0) {
    46              showerr("cannot remove native architecture");
    47              return sc_err;
    48          }
    49      }
    50  
    51      /* Add 64-bit architectures supported by snapd into the seccomp filter.
    52       *
    53       * The documentation of seccomp_arch_add() is confusing. It says that after
    54       * this call any new rules will be added to this architecture. This is
    55       * correct. It doesn't, however, explain that the rules will be multiplied
    56       * and re-written as explained below. */
    57      for (size_t i = 0; i < num_arch_tags; ++i) {
    58          uint32_t arch_tag = arch_tags[i];
    59          sc_err = seccomp_arch_add(ctx, arch_tag);
    60          if (sc_err < 0 && sc_err != -EEXIST) {
    61              showerr("cannot add architecture %x", arch_tag);
    62              return sc_err;
    63          }
    64      }
    65  
    66      /* When the rule set doesn't match one of the architectures above then the
    67       * resulting action should be a "allow" rather than "kill". We don't add
    68       * any of the 32bit architectures since there is no need for any extra
    69       * filtering there. */
    70      sc_err = seccomp_attr_set(ctx, SCMP_FLTATR_ACT_BADARCH, SCMP_ACT_ALLOW);
    71      if (sc_err < 0) {
    72          showerr("cannot set action for unknown architectures");
    73          return sc_err;
    74      }
    75  
    76      /* Resolve the name of "ioctl" on this architecture. We are not using the
    77       * system call number as available through the appropriate linux-specific
    78       * header. This allows us to use a system call number that is not defined
    79       * for the current architecture. This does not matter here, in this
    80       * specific program, however it is more generic. In addition this is more
    81       * in sync with the snap-seccomp program, which does the same for every
    82       * system call. */
    83      int sys_ioctl_nr;
    84      sys_ioctl_nr = seccomp_syscall_resolve_name("ioctl");
    85      if (sys_ioctl_nr == __NR_SCMP_ERROR) {
    86          showerr("cannot resolve ioctl system call number");
    87          return -ESRCH;
    88      }
    89  
    90      /* All of the rules must be added for the native architecture (using native
    91       * system call numbers). When the final program is generated the set of
    92       * architectures added earlier will be used to determine the correct system
    93       * call number for each architecture.
    94       *
    95       * In other words, arguments to scmp_rule_add() must always use native
    96       * system call numbers. Translation for the correct architecture will be
    97       * performed internally. This is not documented in libseccomp, but correct
    98       * operation was confirmed using the pseudo-code program and the bpf_dbg
    99       * tool from the kernel tools/bpf directory.
   100       *
   101       * NOTE: not using scmp_rule_add_exact as that was not doing anything
   102       * at all (presumably due to having all the architectures defined). */
   103  
   104      const struct scmp_arg_cmp no_tty_inject = {
   105          /* We learned that existing programs make legitimate requests with all
   106           * bits set in the more significant 32bit word of the 64 bit double
   107           * word. While this kernel behavior remains suspect and presumably
   108           * undesired it is unlikely to change for backwards compatibility
   109           * reasons. As such we cannot block all requests with high-bits set.
   110           *
   111           * When faced with ioctl(fd, request); refuse to proceed when
   112           * request&0xffffffff == TIOCSTI. This specific way to encode the
   113           * filter has the following important properties:
   114           *
   115           * - it blocks ioctl(fd, TIOCSTI, ptr).
   116           * - it also blocks ioctl(fd, (1UL<<32) | TIOCSTI, ptr).
   117           * - it doesn't block ioctl(fd, (1UL<<32) | (request not equal to TIOCSTI), ptr); */
   118          .arg = 1,
   119          .op = SCMP_CMP_MASKED_EQ,
   120          .datum_a = 0xffffffffUL,
   121          .datum_b = TIOCSTI,
   122      };
   123      sc_err = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), sys_ioctl_nr, 1, no_tty_inject);
   124  
   125      if (sc_err < 0) {
   126          showerr("cannot add rule preventing the use high bits in ioctl");
   127          return sc_err;
   128      }
   129      return 0;
   130  }
   131  
   132  typedef struct arch_set {
   133      const char *name;
   134      const uint32_t *arch_tags;
   135      size_t num_arch_tags;
   136  } arch_set;
   137  
   138  int main(int argc, char **argv) {
   139      const uint32_t le_arch_tags[] = {
   140          SCMP_ARCH_X86_64,
   141          SCMP_ARCH_AARCH64,
   142          SCMP_ARCH_PPC64LE,
   143          SCMP_ARCH_S390X,
   144      };
   145      const uint32_t be_arch_tags[] = {
   146          SCMP_ARCH_S390X,
   147      };
   148      const arch_set arch_sets[] = {
   149          {"LE", le_arch_tags, sizeof le_arch_tags / sizeof *le_arch_tags},
   150          {"BE", be_arch_tags, sizeof be_arch_tags / sizeof *be_arch_tags},
   151      };
   152      int rc = -1;
   153  
   154      for (size_t i = 0; i < sizeof arch_sets / sizeof *arch_sets; ++i) {
   155          const arch_set *as = &arch_sets[i];
   156          int sc_err;
   157          int fd = -1;
   158          int fname_len;
   159          char fname[PATH_MAX];
   160  
   161          scmp_filter_ctx ctx = NULL;
   162          ctx = seccomp_init(SCMP_ACT_ALLOW);
   163          if (ctx == NULL) {
   164              showerr("cannot construct seccomp context");
   165              return -rc;
   166          }
   167          sc_err = populate_filter(ctx, as->arch_tags, as->num_arch_tags);
   168          if (sc_err < 0) {
   169              seccomp_release(ctx);
   170              return -rc;
   171          }
   172  
   173          /* Save pseudo-code program */
   174          fname_len = snprintf(fname, sizeof fname, "%s-blacklist.pfc", as->name);
   175          if (fname_len < 0 || fname_len >= sizeof fname) {
   176              showerr("cannot format file name (%s)", as->name);
   177              seccomp_release(ctx);
   178              return -rc;
   179          }
   180          fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0644);
   181          if (fd < 0) {
   182              showerr("cannot open file %s", fname);
   183              seccomp_release(ctx);
   184              return -rc;
   185          }
   186          sc_err = seccomp_export_pfc(ctx, fd);
   187          if (sc_err < 0) {
   188              showerr("cannot export PFC program %s", fname);
   189              seccomp_release(ctx);
   190              close(fd);
   191              return -rc;
   192          }
   193  
   194          close(fd);
   195  
   196          /* Save binary program. */
   197          fname_len = snprintf(fname, sizeof fname, "%s-blacklist.bpf", as->name);
   198          if (fname_len < 0 || fname_len >= sizeof fname) {
   199              showerr("cannot format file name (%s)", as->name);
   200              seccomp_release(ctx);
   201              return -rc;
   202          }
   203          fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0644);
   204          if (fd < 0) {
   205              showerr("cannot open file %s", fname);
   206              seccomp_release(ctx);
   207              return -rc;
   208          }
   209          sc_err = seccomp_export_bpf(ctx, fd);
   210          if (sc_err < 0) {
   211              showerr("cannot export BPF program %s", fname);
   212              seccomp_release(ctx);
   213              close(fd);
   214              return -rc;
   215          }
   216  
   217          close(fd);
   218          seccomp_release(ctx);
   219      }
   220      rc = 0;
   221      return -rc;
   222  }