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