github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/cmd/snap-seccomp/main_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"math/rand"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"strconv"
    30  	"strings"
    31  	"testing"
    32  
    33  	. "gopkg.in/check.v1"
    34  
    35  	"github.com/mvo5/libseccomp-golang"
    36  
    37  	"github.com/snapcore/snapd/arch"
    38  	main "github.com/snapcore/snapd/cmd/snap-seccomp"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/testutil"
    42  )
    43  
    44  // Hook up check.v1 into the "go test" runner
    45  func Test(t *testing.T) { TestingT(t) }
    46  
    47  type snapSeccompSuite struct {
    48  	seccompBpfLoader     string
    49  	seccompSyscallRunner string
    50  	canCheckCompatArch   bool
    51  }
    52  
    53  var _ = Suite(&snapSeccompSuite{})
    54  
    55  const (
    56  	Deny = iota
    57  	Allow
    58  )
    59  
    60  var seccompBpfLoaderContent = []byte(`
    61  #include <fcntl.h>
    62  #include <inttypes.h>
    63  #include <stdint.h>
    64  #include <stdio.h>
    65  #include <stdlib.h>
    66  #include <string.h>
    67  #include <sys/prctl.h>
    68  #include <unistd.h>
    69  
    70  #include <linux/filter.h>
    71  #include <linux/seccomp.h>
    72  
    73  #define MAX_BPF_SIZE 32 * 1024
    74  
    75  int sc_apply_seccomp_bpf(const char* profile_path)
    76  {
    77      unsigned char bpf[MAX_BPF_SIZE + 1]; // account for EOF
    78      FILE* fp;
    79      fp = fopen(profile_path, "rb");
    80      if (fp == NULL) {
    81          fprintf(stderr, "cannot read %s\n", profile_path);
    82          return -1;
    83      }
    84  
    85      // set 'size' to 1; to get bytes transferred
    86      size_t num_read = fread(bpf, 1, sizeof(bpf), fp);
    87  
    88      if (ferror(fp) != 0) {
    89          perror("fread()");
    90          return -1;
    91      } else if (feof(fp) == 0) {
    92          fprintf(stderr, "file too big\n");
    93          return -1;
    94      }
    95      fclose(fp);
    96  
    97      struct sock_fprog prog = {
    98          .len = num_read / sizeof(struct sock_filter),
    99          .filter = (struct sock_filter*)bpf,
   100      };
   101  
   102      // Set NNP to allow loading seccomp policy into the kernel without
   103      // root
   104      if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
   105          perror("prctl(PR_NO_NEW_PRIVS, 1, 0, 0, 0)");
   106          return -1;
   107      }
   108  
   109      if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
   110          perror("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...) failed");
   111          return -1;
   112      }
   113      return 0;
   114  }
   115  
   116  int main(int argc, char* argv[])
   117  {
   118      int rc = 0;
   119      if (argc < 2) {
   120          fprintf(stderr, "Usage: %s <bpf file> [prog ...]\n", argv[0]);
   121          return 1;
   122      }
   123  
   124      rc = sc_apply_seccomp_bpf(argv[1]);
   125      if (rc != 0)
   126          return -rc;
   127  
   128      execv(argv[2], (char* const*)&argv[2]);
   129      perror("execv failed");
   130      return 1;
   131  }
   132  `)
   133  
   134  var seccompSyscallRunnerContent = []byte(`
   135  #define _GNU_SOURCE
   136  #include <errno.h>
   137  #include <stdlib.h>
   138  #include <sys/syscall.h>
   139  #include <unistd.h>
   140  #include <inttypes.h>
   141  int main(int argc, char** argv)
   142  {
   143      uint32_t l[7];
   144      int syscall_ret, ret = 0;
   145      for (int i = 0; i < 7 && argv[i+1] != NULL; i++) {
   146          errno = 0;
   147          l[i] = strtoll(argv[i + 1], NULL, 10);
   148  	// exit '11' let's us know strtoll failed
   149          if (errno != 0)
   150              syscall(SYS_exit, 11, 0, 0, 0, 0, 0);
   151      }
   152      // There might be architecture-specific requirements. see "man syscall"
   153      // for details.
   154      syscall_ret = syscall(l[0], l[1], l[2], l[3], l[4], l[5], l[6]);
   155      // 911 is our mocked errno
   156      if (syscall_ret < 0 && errno == 911) {
   157          ret = 10;
   158      }
   159      syscall(SYS_exit, ret, 0, 0, 0, 0, 0);
   160      return 0;
   161  }
   162  `)
   163  
   164  func (s *snapSeccompSuite) SetUpSuite(c *C) {
   165  	main.MockErrnoOnDenial(911)
   166  
   167  	// build seccomp-load helper
   168  	s.seccompBpfLoader = filepath.Join(c.MkDir(), "seccomp_bpf_loader")
   169  	err := ioutil.WriteFile(s.seccompBpfLoader+".c", seccompBpfLoaderContent, 0644)
   170  	c.Assert(err, IsNil)
   171  	cmd := exec.Command("gcc", "-Werror", "-Wall", s.seccompBpfLoader+".c", "-o", s.seccompBpfLoader)
   172  	cmd.Stdout = os.Stdout
   173  	cmd.Stderr = os.Stderr
   174  	err = cmd.Run()
   175  	c.Assert(err, IsNil)
   176  
   177  	// build syscall-runner helper
   178  	s.seccompSyscallRunner = filepath.Join(c.MkDir(), "seccomp_syscall_runner")
   179  	err = ioutil.WriteFile(s.seccompSyscallRunner+".c", seccompSyscallRunnerContent, 0644)
   180  	c.Assert(err, IsNil)
   181  
   182  	cmd = exec.Command("gcc", "-std=c99", "-Werror", "-Wall", "-static", s.seccompSyscallRunner+".c", "-o", s.seccompSyscallRunner, "-Wl,-static", "-static-libgcc")
   183  	cmd.Stdout = os.Stdout
   184  	cmd.Stderr = os.Stderr
   185  	err = cmd.Run()
   186  	c.Assert(err, IsNil)
   187  
   188  	// Amazon Linux 2 is 64bit only and there is no multilib support
   189  	s.canCheckCompatArch = !release.DistroLike("amzn")
   190  
   191  	// Build 32bit runner on amd64 to test non-native syscall handling.
   192  	// Ideally we would build for ppc64el->powerpc and arm64->armhf but
   193  	// it seems tricky to find the right gcc-multilib for this.
   194  	if arch.DpkgArchitecture() == "amd64" && s.canCheckCompatArch {
   195  		cmd = exec.Command(cmd.Args[0], cmd.Args[1:]...)
   196  		cmd.Args = append(cmd.Args, "-m32")
   197  		for i, k := range cmd.Args {
   198  			if k == s.seccompSyscallRunner {
   199  				cmd.Args[i] = s.seccompSyscallRunner + ".m32"
   200  			}
   201  		}
   202  		if output, err := cmd.CombinedOutput(); err != nil {
   203  			fmt.Printf("cannot build multi-lib syscall runner: %v\n%s", err, output)
   204  		}
   205  	}
   206  }
   207  
   208  // Runs the policy through the kernel:
   209  //  1. runs main.Compile()
   210  //  2. the program in seccompBpfLoaderContent with the output file as an
   211  //     argument
   212  //  3. the program in seccompBpfLoaderContent loads the output file BPF into
   213  //     the kernel and executes the program in seccompBpfRunnerContent with the
   214  //     syscall and arguments specified by the test
   215  //
   216  // In this manner, in addition to verifying policy syntax we are able to
   217  // unit test the resulting bpf in several ways.
   218  //
   219  // Full testing of applied policy is done elsewhere via spread tests.
   220  //
   221  // Note that we skip testing prctl(PR_SET_ENDIAN) - it causes havoc when
   222  // it is run. We will also need to skip: fadvise64_64,
   223  //   ftruncate64, posix_fadvise, pread64, pwrite64, readahead,
   224  //   sync_file_range, and truncate64.
   225  // Once we start using those. See `man syscall`
   226  func (s *snapSeccompSuite) runBpf(c *C, seccompWhitelist, bpfInput string, expected int) {
   227  	// Common syscalls we need to allow for a minimal statically linked
   228  	// c program.
   229  	//
   230  	// If we compile a test program for each test we can get away with
   231  	// a even smaller set of syscalls: execve,exit essentially. But it
   232  	// means a much longer test run (30s vs 2s). Commit d288d89 contains
   233  	// the code for this.
   234  	common := `
   235  execve
   236  uname
   237  brk
   238  arch_prctl
   239  readlink
   240  access
   241  sysinfo
   242  exit
   243  # i386
   244  set_thread_area
   245  # armhf
   246  set_tls
   247  # arm64
   248  readlinkat
   249  faccessat
   250  # i386 from amd64
   251  restart_syscall
   252  # libc6 2.31/gcc-9.3
   253  mprotect
   254  `
   255  	bpfPath := filepath.Join(c.MkDir(), "bpf")
   256  	err := main.Compile([]byte(common+seccompWhitelist), bpfPath)
   257  	c.Assert(err, IsNil)
   258  
   259  	// default syscall runner
   260  	syscallRunner := s.seccompSyscallRunner
   261  
   262  	// syscallName;arch;arg1,arg2...
   263  	l := strings.Split(bpfInput, ";")
   264  	syscallName := l[0]
   265  	syscallArch := "native"
   266  	if len(l) > 1 {
   267  		syscallArch = l[1]
   268  	}
   269  
   270  	syscallNr, err := seccomp.GetSyscallFromName(syscallName)
   271  	c.Assert(err, IsNil)
   272  
   273  	// Check if we want to test non-native architecture
   274  	// handling. Doing this via the in-kernel tests is tricky as
   275  	// we need a kernel that can run the architecture and a
   276  	// compiler that can produce the required binaries. Currently
   277  	// we only test amd64 running i386 here.
   278  	if syscallArch != "native" {
   279  		syscallNr, err = seccomp.GetSyscallFromNameByArch(syscallName, main.DpkgArchToScmpArch(syscallArch))
   280  		c.Assert(err, IsNil)
   281  
   282  		switch syscallArch {
   283  		case "amd64":
   284  			// default syscallRunner
   285  		case "i386":
   286  			syscallRunner = s.seccompSyscallRunner + ".m32"
   287  		default:
   288  			c.Errorf("unexpected non-native arch: %s", syscallArch)
   289  		}
   290  	}
   291  	switch {
   292  	case syscallNr == -101:
   293  		// "socket" needs to be demuxed
   294  		switch arch.DpkgArchitecture() {
   295  		case "ppc64el":
   296  			// see libseccomp: _ppc64_syscall_demux()
   297  			syscallNr = 326
   298  		default:
   299  			// see libseccomp: _s390x_sock_demux(),
   300  			// _x86_sock_demux() the -101 is translated to
   301  			// 359 (socket)
   302  			syscallNr = 359
   303  		}
   304  	case syscallNr == -10165:
   305  		// "mknod" on arm64 is not available at all on arm64
   306  		// only "mknodat" but libseccomp will not generate a
   307  		// "mknodat" whitelist, it geneates a whitelist with
   308  		// syscall -10165 (!?!) so we cannot test this.
   309  		c.Skip("skipping mknod tests on arm64")
   310  	case syscallNr < 0:
   311  		c.Errorf("failed to resolve %v: %v", l[0], syscallNr)
   312  		return
   313  	}
   314  
   315  	var syscallRunnerArgs [7]string
   316  	syscallRunnerArgs[0] = strconv.FormatInt(int64(syscallNr), 10)
   317  	if len(l) > 2 {
   318  		args := strings.Split(l[2], ",")
   319  		for i := range args {
   320  			// init with random number argument
   321  			syscallArg := (uint64)(rand.Uint32())
   322  			// override if the test specifies a specific number;
   323  			// this must match main.go:readNumber()
   324  			if nr, ok := main.SeccompResolver[args[i]]; ok {
   325  				syscallArg = nr
   326  			} else if nr, err := strconv.ParseUint(args[i], 10, 32); err == nil {
   327  				syscallArg = nr
   328  			} else if nr, err := strconv.ParseInt(args[i], 10, 32); err == nil {
   329  				syscallArg = uint64(uint32(nr))
   330  			}
   331  			syscallRunnerArgs[i+1] = strconv.FormatUint(syscallArg, 10)
   332  		}
   333  	}
   334  
   335  	cmd := exec.Command(s.seccompBpfLoader, bpfPath, syscallRunner, syscallRunnerArgs[0], syscallRunnerArgs[1], syscallRunnerArgs[2], syscallRunnerArgs[3], syscallRunnerArgs[4], syscallRunnerArgs[5], syscallRunnerArgs[6])
   336  	cmd.Stdin = os.Stdin
   337  	cmd.Stdout = os.Stdout
   338  	cmd.Stderr = os.Stderr
   339  	err = cmd.Run()
   340  	// the exit code of the test binary is either 0 or 10, everything
   341  	// else is unexpected (segv, strtoll failure, ...)
   342  	exitCode, e := osutil.ExitCode(err)
   343  	c.Assert(e, IsNil)
   344  	c.Assert(exitCode == 0 || exitCode == 10, Equals, true, Commentf("unexpected exit code: %v for %v - test setup broken", exitCode, seccompWhitelist))
   345  	switch expected {
   346  	case Allow:
   347  		if err != nil {
   348  			c.Fatalf("unexpected error for %q (failed to run %q)", seccompWhitelist, err)
   349  		}
   350  	case Deny:
   351  		if err == nil {
   352  			c.Fatalf("unexpected success for %q %q (ran but should have failed)", seccompWhitelist, bpfInput)
   353  		}
   354  	default:
   355  		c.Fatalf("unknown expected result %v", expected)
   356  	}
   357  }
   358  
   359  func (s *snapSeccompSuite) TestUnrestricted(c *C) {
   360  	inp := "@unrestricted\n"
   361  	outPath := filepath.Join(c.MkDir(), "bpf")
   362  	err := main.Compile([]byte(inp), outPath)
   363  	c.Assert(err, IsNil)
   364  
   365  	c.Check(outPath, testutil.FileEquals, inp)
   366  }
   367  
   368  // TestCompile iterates over a range of textual seccomp whitelist rules and
   369  // mocked kernel syscall input. For each rule, the test consists of compiling
   370  // the rule into a bpf program and then running that program on a virtual bpf
   371  // machine and comparing the bpf machine output to the specified expected
   372  // output and seccomp operation. Eg:
   373  //    {"<rule>", "<mocked kernel input>", <seccomp result>}
   374  //
   375  // Eg to test that the rule 'read >=2' is allowed with 'read(2)' and 'read(3)'
   376  // and denied with 'read(1)' and 'read(0)', add the following tests:
   377  //    {"read >=2", "read;native;2", Allow},
   378  //    {"read >=2", "read;native;3", Allow},
   379  //    {"read >=2", "read;native;1", main.SeccompRetKill},
   380  //    {"read >=2", "read;native;0", main.SeccompRetKill},
   381  func (s *snapSeccompSuite) TestCompile(c *C) {
   382  
   383  	for _, t := range []struct {
   384  		seccompWhitelist string
   385  		bpfInput         string
   386  		expected         int
   387  	}{
   388  		// special
   389  		{"@complain", "execve", Allow},
   390  
   391  		// trivial allow
   392  		{"read", "read", Allow},
   393  		{"read\nwrite\nexecve\n", "write", Allow},
   394  
   395  		// trivial denial
   396  		{"read", "ioctl", Deny},
   397  
   398  		// test argument filtering syntax, we currently support:
   399  		//   >=, <=, !, <, >, |
   400  		// modifiers.
   401  
   402  		// reads >= 2 are ok
   403  		{"read >=2", "read;native;2", Allow},
   404  		{"read >=2", "read;native;3", Allow},
   405  		// but not reads < 2, those get killed
   406  		{"read >=2", "read;native;1", Deny},
   407  		{"read >=2", "read;native;0", Deny},
   408  
   409  		// reads <= 2 are ok
   410  		{"read <=2", "read;native;0", Allow},
   411  		{"read <=2", "read;native;1", Allow},
   412  		{"read <=2", "read;native;2", Allow},
   413  		// but not reads >2, those get killed
   414  		{"read <=2", "read;native;3", Deny},
   415  		{"read <=2", "read;native;4", Deny},
   416  
   417  		// reads that are not 2 are ok
   418  		{"read !2", "read;native;1", Allow},
   419  		{"read !2", "read;native;3", Allow},
   420  		// but not 2, this gets killed
   421  		{"read !2", "read;native;2", Deny},
   422  
   423  		// reads > 2 are ok
   424  		{"read >2", "read;native;4", Allow},
   425  		{"read >2", "read;native;3", Allow},
   426  		// but not reads <= 2, those get killed
   427  		{"read >2", "read;native;2", Deny},
   428  		{"read >2", "read;native;1", Deny},
   429  
   430  		// reads < 2 are ok
   431  		{"read <2", "read;native;0", Allow},
   432  		{"read <2", "read;native;1", Allow},
   433  		// but not reads >= 2, those get killed
   434  		{"read <2", "read;native;2", Deny},
   435  		{"read <2", "read;native;3", Deny},
   436  
   437  		// FIXME: test maskedEqual better
   438  		{"read |1", "read;native;1", Allow},
   439  		{"read |1", "read;native;2", Deny},
   440  
   441  		// exact match, reads == 2 are ok
   442  		{"read 2", "read;native;2", Allow},
   443  		// but not those != 2
   444  		{"read 2", "read;native;3", Deny},
   445  		{"read 2", "read;native;1", Deny},
   446  
   447  		// test actual syscalls and their expected usage
   448  		{"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow},
   449  		{"ioctl - TIOCSTI", "ioctl;native;-,99", Deny},
   450  		{"ioctl - !TIOCSTI", "ioctl;native;-,TIOCSTI", Deny},
   451  
   452  		// test_bad_seccomp_filter_args_clone
   453  		{"setns - CLONE_NEWNET", "setns;native;-,99", Deny},
   454  		{"setns - CLONE_NEWNET", "setns;native;-,CLONE_NEWNET", Allow},
   455  
   456  		// test_bad_seccomp_filter_args_mknod
   457  		{"mknod - |S_IFIFO", "mknod;native;-,S_IFIFO", Allow},
   458  		{"mknod - |S_IFIFO", "mknod;native;-,99", Deny},
   459  
   460  		// test_bad_seccomp_filter_args_prctl
   461  		{"prctl PR_CAP_AMBIENT_RAISE", "prctl;native;PR_CAP_AMBIENT_RAISE", Allow},
   462  		{"prctl PR_CAP_AMBIENT_RAISE", "prctl;native;99", Deny},
   463  
   464  		// test_bad_seccomp_filter_args_prio
   465  		{"setpriority PRIO_PROCESS 0 >=0", "setpriority;native;PRIO_PROCESS,0,19", Allow},
   466  		{"setpriority PRIO_PROCESS 0 >=0", "setpriority;native;99", Deny},
   467  
   468  		// test_bad_seccomp_filter_args_quotactl
   469  		{"quotactl Q_GETQUOTA", "quotactl;native;Q_GETQUOTA", Allow},
   470  		{"quotactl Q_GETQUOTA", "quotactl;native;99", Deny},
   471  
   472  		// test_bad_seccomp_filter_args_termios
   473  		{"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow},
   474  		{"ioctl - TIOCSTI", "ioctl;native;-,99", Deny},
   475  
   476  		// u:root g:root
   477  		{"fchown - u:root g:root", "fchown;native;-,0,0", Allow},
   478  		{"fchown - u:root g:root", "fchown;native;-,99,0", Deny},
   479  		{"chown - u:root g:root", "chown;native;-,0,0", Allow},
   480  		{"chown - u:root g:root", "chown;native;-,99,0", Deny},
   481  
   482  		// u:root -1
   483  		{"chown - u:root -1", "chown;native;-,0,-1", Allow},
   484  		{"chown - u:root -1", "chown;native;-,99,-1", Deny},
   485  		{"chown - -1 u:root", "chown;native;-,-1,0", Allow},
   486  		{"chown - -1 u:root", "chown;native;-,99,0", Deny},
   487  		{"chown - -1 -1", "chown;native;-,-1,-1", Allow},
   488  		{"chown - -1 -1", "chown;native;-,99,-1", Deny},
   489  	} {
   490  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   491  	}
   492  }
   493  
   494  // TestCompileSocket runs in a separate tests so that only this part
   495  // can be skipped when "socketcall()" is used instead of "socket()".
   496  //
   497  // Some architectures (i386, s390x) use the "socketcall" syscall instead
   498  // of "socket". This is the case on Ubuntu 14.04, 17.04, 17.10
   499  func (s *snapSeccompSuite) TestCompileSocket(c *C) {
   500  	if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" {
   501  		c.Skip("14.04/i386 uses socketcall which cannot be tested here")
   502  	}
   503  
   504  	for _, t := range []struct {
   505  		seccompWhitelist string
   506  		bpfInput         string
   507  		expected         int
   508  	}{
   509  
   510  		// test_bad_seccomp_filter_args_socket
   511  		{"socket AF_UNIX", "socket;native;AF_UNIX", Allow},
   512  		{"socket AF_UNIX", "socket;native;99", Deny},
   513  		{"socket - SOCK_STREAM", "socket;native;-,SOCK_STREAM", Allow},
   514  		{"socket - SOCK_STREAM", "socket;native;-,99", Deny},
   515  		{"socket AF_CONN", "socket;native;AF_CONN", Allow},
   516  		{"socket AF_CONN", "socket;native;99", Deny},
   517  	} {
   518  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   519  	}
   520  
   521  }
   522  
   523  func (s *snapSeccompSuite) TestCompileBadInput(c *C) {
   524  	for _, t := range []struct {
   525  		inp    string
   526  		errMsg string
   527  	}{
   528  		// test_bad_seccomp_filter_args_clone (various typos in input)
   529  		{"setns - CLONE_NEWNE", `cannot parse line: cannot parse token "CLONE_NEWNE" \(line "setns - CLONE_NEWNE"\)`},
   530  		{"setns - CLONE_NEWNETT", `cannot parse line: cannot parse token "CLONE_NEWNETT" \(line "setns - CLONE_NEWNETT"\)`},
   531  		{"setns - CL0NE_NEWNET", `cannot parse line: cannot parse token "CL0NE_NEWNET" \(line "setns - CL0NE_NEWNET"\)`},
   532  
   533  		// test_bad_seccomp_filter_args_mknod (various typos in input)
   534  		{"mknod - |S_IFIF", `cannot parse line: cannot parse token "S_IFIF" \(line "mknod - |S_IFIF"\)`},
   535  		{"mknod - |S_IFIFOO", `cannot parse line: cannot parse token "S_IFIFOO" \(line "mknod - |S_IFIFOO"\)`},
   536  		{"mknod - |S_!FIFO", `cannot parse line: cannot parse token "S_IFIFO" \(line "mknod - |S_!FIFO"\)`},
   537  
   538  		// test_bad_seccomp_filter_args_null
   539  		{"socket S\x00CK_STREAM", `cannot parse line: cannot parse token .*`},
   540  		{"socket SOCK_STREAM\x00bad stuff", `cannot parse line: cannot parse token .*`},
   541  
   542  		// test_bad_seccomp_filter_args
   543  		{"setpriority bar", `cannot parse line: cannot parse token "bar" .*`},
   544  		{"setpriority -1", `cannot parse line: cannot parse token "-1" .*`},
   545  		{"setpriority 0 - -1 0", `cannot parse line: cannot parse token "-1" .*`},
   546  		{"setpriority --10", `cannot parse line: cannot parse token "--10" .*`},
   547  		{"setpriority 0:10", `cannot parse line: cannot parse token "0:10" .*`},
   548  		{"setpriority 0-10", `cannot parse line: cannot parse token "0-10" .*`},
   549  		{"setpriority 0,1", `cannot parse line: cannot parse token "0,1" .*`},
   550  		{"setpriority 0x0", `cannot parse line: cannot parse token "0x0" .*`},
   551  		{"setpriority a1", `cannot parse line: cannot parse token "a1" .*`},
   552  		{"setpriority 1a", `cannot parse line: cannot parse token "1a" .*`},
   553  		{"setpriority 1-", `cannot parse line: cannot parse token "1-" .*`},
   554  		{"setpriority 1\\ 2", `cannot parse line: cannot parse token "1\\\\" .*`},
   555  		{"setpriority 1\\n2", `cannot parse line: cannot parse token "1\\\\n2" .*`},
   556  		// 1 bigger than uint32
   557  		{"chown 0 4294967296", `cannot parse line: cannot parse token "4294967296" .*`},
   558  		// 1 smaller than int32
   559  		{"chown - 0 -2147483649", `cannot parse line: cannot parse token "-2147483649" .*`},
   560  		{"setpriority 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", `cannot parse line: cannot parse token "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" .*`},
   561  		{"mbind - - - - - - 7", `cannot parse line: too many arguments specified for syscall 'mbind' in line.*`},
   562  		{"mbind 1 2 3 4 5 6 7", `cannot parse line: too many arguments specified for syscall 'mbind' in line.*`},
   563  		// test_bad_seccomp_filter_args_prctl
   564  		{"prctl PR_GET_SECCOM", `cannot parse line: cannot parse token "PR_GET_SECCOM" .*`},
   565  		{"prctl PR_GET_SECCOMPP", `cannot parse line: cannot parse token "PR_GET_SECCOMPP" .*`},
   566  		{"prctl PR_GET_SECC0MP", `cannot parse line: cannot parse token "PR_GET_SECC0MP" .*`},
   567  		{"prctl PR_CAP_AMBIENT_RAIS", `cannot parse line: cannot parse token "PR_CAP_AMBIENT_RAIS" .*`},
   568  		{"prctl PR_CAP_AMBIENT_RAISEE", `cannot parse line: cannot parse token "PR_CAP_AMBIENT_RAISEE" .*`},
   569  		// test_bad_seccomp_filter_args_prio
   570  		{"setpriority PRIO_PROCES 0 >=0", `cannot parse line: cannot parse token "PRIO_PROCES" .*`},
   571  		{"setpriority PRIO_PROCESSS 0 >=0", `cannot parse line: cannot parse token "PRIO_PROCESSS" .*`},
   572  		{"setpriority PRIO_PR0CESS 0 >=0", `cannot parse line: cannot parse token "PRIO_PR0CESS" .*`},
   573  		// test_bad_seccomp_filter_args_quotactl
   574  		{"quotactl Q_GETQUOT", `cannot parse line: cannot parse token "Q_GETQUOT" .*`},
   575  		{"quotactl Q_GETQUOTAA", `cannot parse line: cannot parse token "Q_GETQUOTAA" .*`},
   576  		{"quotactl Q_GETQU0TA", `cannot parse line: cannot parse token "Q_GETQU0TA" .*`},
   577  		// test_bad_seccomp_filter_args_socket
   578  		{"socket AF_UNI", `cannot parse line: cannot parse token "AF_UNI" .*`},
   579  		{"socket AF_UNIXX", `cannot parse line: cannot parse token "AF_UNIXX" .*`},
   580  		{"socket AF_UN!X", `cannot parse line: cannot parse token "AF_UN!X" .*`},
   581  		{"socket - SOCK_STREA", `cannot parse line: cannot parse token "SOCK_STREA" .*`},
   582  		{"socket - SOCK_STREAMM", `cannot parse line: cannot parse token "SOCK_STREAMM" .*`},
   583  		{"socket - NETLINK_ROUT", `cannot parse line: cannot parse token "NETLINK_ROUT" .*`},
   584  		{"socket - NETLINK_ROUTEE", `cannot parse line: cannot parse token "NETLINK_ROUTEE" .*`},
   585  		{"socket - NETLINK_R0UTE", `cannot parse line: cannot parse token "NETLINK_R0UTE" .*`},
   586  		// test_bad_seccomp_filter_args_termios
   587  		{"ioctl - TIOCST", `cannot parse line: cannot parse token "TIOCST" .*`},
   588  		{"ioctl - TIOCSTII", `cannot parse line: cannot parse token "TIOCSTII" .*`},
   589  		{"ioctl - TIOCST1", `cannot parse line: cannot parse token "TIOCST1" .*`},
   590  		// ensure missing numbers are caught
   591  		{"setpriority >", `cannot parse line: cannot parse token ">" .*`},
   592  		{"setpriority >=", `cannot parse line: cannot parse token ">=" .*`},
   593  		{"setpriority <", `cannot parse line: cannot parse token "<" .*`},
   594  		{"setpriority <=", `cannot parse line: cannot parse token "<=" .*`},
   595  		{"setpriority |", `cannot parse line: cannot parse token "|" .*`},
   596  		{"setpriority !", `cannot parse line: cannot parse token "!" .*`},
   597  
   598  		// u:<username>
   599  		{"setuid :root", `cannot parse line: cannot parse token ":root" .*`},
   600  		{"setuid u:", `cannot parse line: cannot parse token "u:" \(line "setuid u:"\): "" must be a valid username`},
   601  		{"setuid u:!", `cannot parse line: cannot parse token "u:!" \(line "setuid u:!"\): "!" must be a valid username`},
   602  		{"setuid u:b@d|npu+", `cannot parse line: cannot parse token "u:b@d|npu+" \(line "setuid u:b@d|npu+"\): "b@d|npu+" must be a valid username`},
   603  		{"setuid u:snap|bad", `cannot parse line: cannot parse token "u:snap|bad" \(line "setuid u:snap|bad"\): "snap|bad" must be a valid username`},
   604  		{"setuid U:root", `cannot parse line: cannot parse token "U:root" .*`},
   605  		{"setuid u:nonexistent", `cannot parse line: cannot parse token "u:nonexistent" \(line "setuid u:nonexistent"\): user: unknown user nonexistent`},
   606  		// g:<groupname>
   607  		{"setgid g:", `cannot parse line: cannot parse token "g:" \(line "setgid g:"\): "" must be a valid group name`},
   608  		{"setgid g:!", `cannot parse line: cannot parse token "g:!" \(line "setgid g:!"\): "!" must be a valid group name`},
   609  		{"setgid g:b@d|npu+", `cannot parse line: cannot parse token "g:b@d|npu+" \(line "setgid g:b@d|npu+"\): "b@d|npu+" must be a valid group name`},
   610  		{"setgid g:snap|bad", `cannot parse line: cannot parse token "g:snap|bad" \(line "setgid g:snap|bad"\): "snap|bad" must be a valid group name`},
   611  		{"setgid G:root", `cannot parse line: cannot parse token "G:root" .*`},
   612  		{"setgid g:nonexistent", `cannot parse line: cannot parse token "g:nonexistent" \(line "setgid g:nonexistent"\): group: unknown group nonexistent`},
   613  	} {
   614  		outPath := filepath.Join(c.MkDir(), "bpf")
   615  		err := main.Compile([]byte(t.inp), outPath)
   616  		c.Check(err, ErrorMatches, t.errMsg, Commentf("%q errors in unexpected ways, got: %q expected %q", t.inp, err, t.errMsg))
   617  	}
   618  }
   619  
   620  // ported from test_restrictions_working_args_socket
   621  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsSocket(c *C) {
   622  	if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" {
   623  		c.Skip("14.04/i386 uses socketcall which cannot be tested here")
   624  	}
   625  
   626  	for _, pre := range []string{"AF", "PF"} {
   627  		for _, i := range []string{"UNIX", "LOCAL", "INET", "INET6", "IPX", "NETLINK", "X25", "AX25", "ATMPVC", "APPLETALK", "PACKET", "ALG", "CAN", "BRIDGE", "NETROM", "ROSE", "NETBEUI", "SECURITY", "KEY", "ASH", "ECONET", "SNA", "IRDA", "PPPOX", "WANPIPE", "BLUETOOTH", "RDS", "LLC", "TIPC", "IUCV", "RXRPC", "ISDN", "PHONET", "IEEE802154", "CAIF", "NFC", "VSOCK", "MPLS", "IB"} {
   628  			seccompWhitelist := fmt.Sprintf("socket %s_%s", pre, i)
   629  			bpfInputGood := fmt.Sprintf("socket;native;%s_%s", pre, i)
   630  			bpfInputBad := "socket;native;99999"
   631  			s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   632  			s.runBpf(c, seccompWhitelist, bpfInputBad, Deny)
   633  
   634  			for _, j := range []string{"SOCK_STREAM", "SOCK_DGRAM", "SOCK_SEQPACKET", "SOCK_RAW", "SOCK_RDM", "SOCK_PACKET"} {
   635  				seccompWhitelist := fmt.Sprintf("socket %s_%s %s", pre, i, j)
   636  				bpfInputGood := fmt.Sprintf("socket;native;%s_%s,%s", pre, i, j)
   637  				bpfInputBad := fmt.Sprintf("socket;native;%s_%s,9999", pre, i)
   638  				s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   639  				s.runBpf(c, seccompWhitelist, bpfInputBad, Deny)
   640  			}
   641  		}
   642  	}
   643  
   644  	for _, i := range []string{"NETLINK_ROUTE", "NETLINK_USERSOCK", "NETLINK_FIREWALL", "NETLINK_SOCK_DIAG", "NETLINK_NFLOG", "NETLINK_XFRM", "NETLINK_SELINUX", "NETLINK_ISCSI", "NETLINK_AUDIT", "NETLINK_FIB_LOOKUP", "NETLINK_CONNECTOR", "NETLINK_NETFILTER", "NETLINK_IP6_FW", "NETLINK_DNRTMSG", "NETLINK_KOBJECT_UEVENT", "NETLINK_GENERIC", "NETLINK_SCSITRANSPORT", "NETLINK_ECRYPTFS", "NETLINK_RDMA", "NETLINK_CRYPTO", "NETLINK_INET_DIAG"} {
   645  		for _, j := range []string{"AF_NETLINK", "PF_NETLINK"} {
   646  			seccompWhitelist := fmt.Sprintf("socket %s - %s", j, i)
   647  			bpfInputGood := fmt.Sprintf("socket;native;%s,0,%s", j, i)
   648  			bpfInputBad := fmt.Sprintf("socket;native;%s,0,99", j)
   649  			s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   650  			s.runBpf(c, seccompWhitelist, bpfInputBad, Deny)
   651  		}
   652  	}
   653  }
   654  
   655  // ported from test_restrictions_working_args_quotactl
   656  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsQuotactl(c *C) {
   657  	for _, arg := range []string{"Q_QUOTAON", "Q_QUOTAOFF", "Q_GETQUOTA", "Q_SETQUOTA", "Q_GETINFO", "Q_SETINFO", "Q_GETFMT", "Q_SYNC", "Q_XQUOTAON", "Q_XQUOTAOFF", "Q_XGETQUOTA", "Q_XSETQLIM", "Q_XGETQSTAT", "Q_XQUOTARM"} {
   658  		// good input
   659  		seccompWhitelist := fmt.Sprintf("quotactl %s", arg)
   660  		bpfInputGood := fmt.Sprintf("quotactl;native;%s", arg)
   661  		s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   662  		// bad input
   663  		for _, bad := range []string{"quotactl;native;99999", "read;native;"} {
   664  			s.runBpf(c, seccompWhitelist, bad, Deny)
   665  		}
   666  	}
   667  }
   668  
   669  // ported from test_restrictions_working_args_prctl
   670  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsPrctl(c *C) {
   671  	for _, arg := range []string{"PR_CAP_AMBIENT", "PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL", "PR_CAPBSET_READ", "PR_CAPBSET_DROP", "PR_SET_CHILD_SUBREAPER", "PR_GET_CHILD_SUBREAPER", "PR_SET_DUMPABLE", "PR_GET_DUMPABLE", "PR_GET_ENDIAN", "PR_SET_FPEMU", "PR_GET_FPEMU", "PR_SET_FPEXC", "PR_GET_FPEXC", "PR_SET_KEEPCAPS", "PR_GET_KEEPCAPS", "PR_MCE_KILL", "PR_MCE_KILL_GET", "PR_SET_MM", "PR_SET_MM_START_CODE", "PR_SET_MM_END_CODE", "PR_SET_MM_START_DATA", "PR_SET_MM_END_DATA", "PR_SET_MM_START_STACK", "PR_SET_MM_START_BRK", "PR_SET_MM_BRK", "PR_SET_MM_ARG_START", "PR_SET_MM_ARG_END", "PR_SET_MM_ENV_START", "PR_SET_MM_ENV_END", "PR_SET_MM_AUXV", "PR_SET_MM_EXE_FILE", "PR_MPX_ENABLE_MANAGEMENT", "PR_MPX_DISABLE_MANAGEMENT", "PR_SET_NAME", "PR_GET_NAME", "PR_SET_NO_NEW_PRIVS", "PR_GET_NO_NEW_PRIVS", "PR_SET_PDEATHSIG", "PR_GET_PDEATHSIG", "PR_SET_PTRACER", "PR_SET_SECCOMP", "PR_GET_SECCOMP", "PR_SET_SECUREBITS", "PR_GET_SECUREBITS", "PR_SET_THP_DISABLE", "PR_TASK_PERF_EVENTS_DISABLE", "PR_TASK_PERF_EVENTS_ENABLE", "PR_GET_THP_DISABLE", "PR_GET_TID_ADDRESS", "PR_SET_TIMERSLACK", "PR_GET_TIMERSLACK", "PR_SET_TIMING", "PR_GET_TIMING", "PR_SET_TSC", "PR_GET_TSC", "PR_SET_UNALIGN", "PR_GET_UNALIGN"} {
   672  		// good input
   673  		seccompWhitelist := fmt.Sprintf("prctl %s", arg)
   674  		bpfInputGood := fmt.Sprintf("prctl;native;%s", arg)
   675  		s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   676  		// bad input
   677  		for _, bad := range []string{"prctl;native;99999", "setpriority;native;"} {
   678  			s.runBpf(c, seccompWhitelist, bad, Deny)
   679  		}
   680  
   681  		if arg == "PR_CAP_AMBIENT" {
   682  			for _, j := range []string{"PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL"} {
   683  				seccompWhitelist := fmt.Sprintf("prctl %s %s", arg, j)
   684  				bpfInputGood := fmt.Sprintf("prctl;native;%s,%s", arg, j)
   685  				s.runBpf(c, seccompWhitelist, bpfInputGood, Allow)
   686  				for _, bad := range []string{
   687  					fmt.Sprintf("prctl;native;%s,99999", arg),
   688  					"setpriority;native;",
   689  				} {
   690  					s.runBpf(c, seccompWhitelist, bad, Deny)
   691  				}
   692  			}
   693  		}
   694  	}
   695  }
   696  
   697  // ported from test_restrictions_working_args_clone
   698  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsClone(c *C) {
   699  	for _, t := range []struct {
   700  		seccompWhitelist string
   701  		bpfInput         string
   702  		expected         int
   703  	}{
   704  		// good input
   705  		{"setns - CLONE_NEWIPC", "setns;native;-,CLONE_NEWIPC", Allow},
   706  		{"setns - CLONE_NEWNET", "setns;native;-,CLONE_NEWNET", Allow},
   707  		{"setns - CLONE_NEWNS", "setns;native;-,CLONE_NEWNS", Allow},
   708  		{"setns - CLONE_NEWPID", "setns;native;-,CLONE_NEWPID", Allow},
   709  		{"setns - CLONE_NEWUSER", "setns;native;-,CLONE_NEWUSER", Allow},
   710  		{"setns - CLONE_NEWUTS", "setns;native;-,CLONE_NEWUTS", Allow},
   711  		// bad input
   712  		{"setns - CLONE_NEWIPC", "setns;native;-,99", Deny},
   713  		{"setns - CLONE_NEWNET", "setns;native;-,99", Deny},
   714  		{"setns - CLONE_NEWNS", "setns;native;-,99", Deny},
   715  		{"setns - CLONE_NEWPID", "setns;native;-,99", Deny},
   716  		{"setns - CLONE_NEWUSER", "setns;native;-,99", Deny},
   717  		{"setns - CLONE_NEWUTS", "setns;native;-,99", Deny},
   718  	} {
   719  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   720  	}
   721  }
   722  
   723  // ported from test_restrictions_working_args_mknod
   724  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsMknod(c *C) {
   725  	for _, t := range []struct {
   726  		seccompWhitelist string
   727  		bpfInput         string
   728  		expected         int
   729  	}{
   730  		// good input
   731  		{"mknod - S_IFREG", "mknod;native;-,S_IFREG", Allow},
   732  		{"mknod - S_IFCHR", "mknod;native;-,S_IFCHR", Allow},
   733  		{"mknod - S_IFBLK", "mknod;native;-,S_IFBLK", Allow},
   734  		{"mknod - S_IFIFO", "mknod;native;-,S_IFIFO", Allow},
   735  		{"mknod - S_IFSOCK", "mknod;native;-,S_IFSOCK", Allow},
   736  		// bad input
   737  		{"mknod - S_IFREG", "mknod;native;-,999", Deny},
   738  		{"mknod - S_IFCHR", "mknod;native;-,999", Deny},
   739  		{"mknod - S_IFBLK", "mknod;native;-,999", Deny},
   740  		{"mknod - S_IFIFO", "mknod;native;-,999", Deny},
   741  		{"mknod - S_IFSOCK", "mknod;native;-,999", Deny},
   742  	} {
   743  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   744  	}
   745  }
   746  
   747  // ported from test_restrictions_working_args_prio
   748  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsPrio(c *C) {
   749  	for _, t := range []struct {
   750  		seccompWhitelist string
   751  		bpfInput         string
   752  		expected         int
   753  	}{
   754  		// good input
   755  		{"setpriority PRIO_PROCESS", "setpriority;native;PRIO_PROCESS", Allow},
   756  		{"setpriority PRIO_PGRP", "setpriority;native;PRIO_PGRP", Allow},
   757  		{"setpriority PRIO_USER", "setpriority;native;PRIO_USER", Allow},
   758  		// bad input
   759  		{"setpriority PRIO_PROCESS", "setpriority;native;99", Deny},
   760  		{"setpriority PRIO_PGRP", "setpriority;native;99", Deny},
   761  		{"setpriority PRIO_USER", "setpriority;native;99", Deny},
   762  	} {
   763  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   764  	}
   765  }
   766  
   767  // ported from test_restrictions_working_args_termios
   768  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsTermios(c *C) {
   769  	for _, t := range []struct {
   770  		seccompWhitelist string
   771  		bpfInput         string
   772  		expected         int
   773  	}{
   774  		// good input
   775  		{"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow},
   776  		// bad input
   777  		{"ioctl - TIOCSTI", "quotactl;native;-,99", Deny},
   778  	} {
   779  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   780  	}
   781  }
   782  
   783  func (s *snapSeccompSuite) TestRestrictionsWorkingArgsUidGid(c *C) {
   784  	// while 'root' user usually has uid 0, 'daemon' user uid may vary
   785  	// across distributions, best lookup the uid directly
   786  	daemonUid, err := osutil.FindUid("daemon")
   787  
   788  	if err != nil {
   789  		c.Skip("daemon user not available, perhaps we are in a buildroot jail")
   790  	}
   791  
   792  	for _, t := range []struct {
   793  		seccompWhitelist string
   794  		bpfInput         string
   795  		expected         int
   796  	}{
   797  		// good input. 'root' is guaranteed to be '0' and 'daemon' uid
   798  		// was determined at runtime
   799  		{"setuid u:root", "setuid;native;0", Allow},
   800  		{"setuid u:daemon", fmt.Sprintf("setuid;native;%v", daemonUid), Allow},
   801  		{"setgid g:root", "setgid;native;0", Allow},
   802  		{"setgid g:daemon", fmt.Sprintf("setgid;native;%v", daemonUid), Allow},
   803  		// bad input
   804  		{"setuid u:root", "setuid;native;99", Deny},
   805  		{"setuid u:daemon", "setuid;native;99", Deny},
   806  		{"setgid g:root", "setgid;native;99", Deny},
   807  		{"setgid g:daemon", "setgid;native;99", Deny},
   808  	} {
   809  		s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   810  	}
   811  }
   812  
   813  func (s *snapSeccompSuite) TestCompatArchWorks(c *C) {
   814  	if !s.canCheckCompatArch {
   815  		c.Skip("multi-lib syscall runner not supported by this host")
   816  	}
   817  	for _, t := range []struct {
   818  		arch             string
   819  		seccompWhitelist string
   820  		bpfInput         string
   821  		expected         int
   822  	}{
   823  		// on amd64 we add compat i386
   824  		{"amd64", "read", "read;i386", Allow},
   825  		{"amd64", "read", "read;amd64", Allow},
   826  		{"amd64", "chown - 0 -1", "chown;i386;-,0,-1", Allow},
   827  		{"amd64", "chown - 0 -1", "chown;amd64;-,0,-1", Allow},
   828  		{"amd64", "chown - 0 -1", "chown;i386;-,99,-1", Deny},
   829  		{"amd64", "chown - 0 -1", "chown;amd64;-,99,-1", Deny},
   830  		{"amd64", "setresuid -1 -1 -1", "setresuid;i386;-1,-1,-1", Allow},
   831  		{"amd64", "setresuid -1 -1 -1", "setresuid;amd64;-1,-1,-1", Allow},
   832  		{"amd64", "setresuid -1 -1 -1", "setresuid;i386;-1,99,-1", Deny},
   833  		{"amd64", "setresuid -1 -1 -1", "setresuid;amd64;-1,99,-1", Deny},
   834  	} {
   835  		// It is tricky to mock the architecture here because
   836  		// seccomp is always adding the native arch to the seccomp
   837  		// filter and it will silently discard arches that have
   838  		// an endian mismatch:
   839  		// https://github.com/seccomp/libseccomp/issues/86
   840  		//
   841  		// This means we can not just
   842  		//    main.MockArchDpkgArchitecture(t.arch)
   843  		// here because on endian mismatch the arch will *not* be
   844  		// added
   845  		if arch.DpkgArchitecture() == t.arch {
   846  			s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
   847  		}
   848  	}
   849  }