github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/seccomp/backend.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2018 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 seccomp implements integration between snapd and snap-confine around
    21  // seccomp.
    22  //
    23  // Snappy creates so-called seccomp profiles for each application (for each
    24  // snap) present in the system.  Upon each execution of snap-confine, the
    25  // profile is read and "compiled" to an eBPF program and injected into the
    26  // kernel for the duration of the execution of the process.
    27  //
    28  // There is no binary cache for seccomp, each time the launcher starts an
    29  // application the profile is parsed and re-compiled.
    30  //
    31  // The actual profiles are stored in /var/lib/snappy/seccomp/bpf/*.{src,bin}.
    32  // This directory is hard-coded in snap-confine.
    33  package seccomp
    34  
    35  import (
    36  	"bytes"
    37  	"fmt"
    38  	"os"
    39  	"path/filepath"
    40  	"runtime"
    41  	"strings"
    42  
    43  	"github.com/snapcore/snapd/arch"
    44  	"github.com/snapcore/snapd/cmd"
    45  	"github.com/snapcore/snapd/dirs"
    46  	"github.com/snapcore/snapd/interfaces"
    47  	"github.com/snapcore/snapd/osutil"
    48  	"github.com/snapcore/snapd/release"
    49  	seccomp_compiler "github.com/snapcore/snapd/sandbox/seccomp"
    50  	"github.com/snapcore/snapd/snap"
    51  	"github.com/snapcore/snapd/strutil"
    52  	"github.com/snapcore/snapd/timings"
    53  )
    54  
    55  var (
    56  	kernelFeatures         = release.SecCompActions
    57  	dpkgKernelArchitecture = arch.DpkgKernelArchitecture
    58  	releaseInfoId          = release.ReleaseInfo.ID
    59  	releaseInfoVersionId   = release.ReleaseInfo.VersionID
    60  	requiresSocketcall     = requiresSocketcallImpl
    61  
    62  	snapSeccompVersionInfo = snapSeccompVersionInfoImpl
    63  	seccompCompilerLookup  = cmd.InternalToolPath
    64  )
    65  
    66  func snapSeccompVersionInfoImpl(c Compiler) (seccomp_compiler.VersionInfo, error) {
    67  	return c.VersionInfo()
    68  }
    69  
    70  type Compiler interface {
    71  	Compile(in, out string) error
    72  	VersionInfo() (seccomp_compiler.VersionInfo, error)
    73  }
    74  
    75  // Backend is responsible for maintaining seccomp profiles for snap-confine.
    76  type Backend struct {
    77  	snapSeccomp Compiler
    78  	versionInfo seccomp_compiler.VersionInfo
    79  }
    80  
    81  var globalProfileLE = []byte{
    82  	0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x04, 0x3e, 0x00, 0x00, 0xc0,
    83  	0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40,
    84  	0x15, 0x00, 0x00, 0x0d, 0xff, 0xff, 0xff, 0xff, 0x15, 0x00, 0x06, 0x0c, 0x10, 0x00, 0x00, 0x00,
    85  	0x15, 0x00, 0x00, 0x02, 0xb7, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    86  	0x15, 0x00, 0x03, 0x09, 0x1d, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x08, 0x15, 0x00, 0x00, 0xc0,
    87  	0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x06, 0x36, 0x00, 0x00, 0x00,
    88  	0x20, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    89  	0x15, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
    90  	0x15, 0x00, 0x00, 0x01, 0x12, 0x54, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00,
    91  	0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f,
    92  }
    93  
    94  var globalProfileBE = []byte{
    95  	0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x15, 0x00, 0x08, 0x80, 0x00, 0x00, 0x16,
    96  	0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36,
    97  	0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    98  	0x00, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c,
    99  	0x00, 0x15, 0x00, 0x01, 0x00, 0x00, 0x54, 0x12, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01,
   100  	0x00, 0x06, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00,
   101  }
   102  
   103  func isBigEndian() bool {
   104  	switch runtime.GOARCH {
   105  	case "s390x":
   106  		return true
   107  	case "ppc":
   108  		return true
   109  		// TODO: perhaps more here?
   110  	}
   111  	return false
   112  }
   113  
   114  // Initialize ensures that the global profile is on disk and interrogates
   115  // libseccomp wrapper to generate a version string that will be used to
   116  // determine if we need to recompile seccomp policy due to system
   117  // changes outside of snapd.
   118  func (b *Backend) Initialize() error {
   119  	dir := dirs.SnapSeccompDir
   120  	fname := "global.bin"
   121  	var globalProfile []byte
   122  	if isBigEndian() {
   123  		globalProfile = globalProfileBE
   124  	} else {
   125  		globalProfile = globalProfileLE
   126  	}
   127  	content := map[string]*osutil.FileState{
   128  		fname: {Content: globalProfile, Mode: 0644},
   129  	}
   130  	if err := os.MkdirAll(dir, 0755); err != nil {
   131  		return fmt.Errorf("cannot create directory for seccomp profiles %q: %s", dir, err)
   132  	}
   133  	_, _, err := osutil.EnsureDirState(dir, fname, content)
   134  	if err != nil {
   135  		return fmt.Errorf("cannot synchronize global seccomp profile: %s", err)
   136  	}
   137  
   138  	b.snapSeccomp, err = seccomp_compiler.New(seccompCompilerLookup)
   139  	if err != nil {
   140  		return fmt.Errorf("cannot initialize seccomp profile compiler: %v", err)
   141  	}
   142  
   143  	versionInfo, err := snapSeccompVersionInfo(b.snapSeccomp)
   144  	if err != nil {
   145  		return fmt.Errorf("cannot obtain snap-seccomp version information: %v", err)
   146  	}
   147  	b.versionInfo = versionInfo
   148  	return nil
   149  }
   150  
   151  // Name returns the name of the backend.
   152  func (b *Backend) Name() interfaces.SecuritySystem {
   153  	return interfaces.SecuritySecComp
   154  }
   155  
   156  func bpfSrcPath(srcName string) string {
   157  	return filepath.Join(dirs.SnapSeccompDir, srcName)
   158  }
   159  
   160  func bpfBinPath(srcName string) string {
   161  	return filepath.Join(dirs.SnapSeccompDir, strings.TrimSuffix(srcName, ".src")+".bin")
   162  }
   163  
   164  // Setup creates seccomp profiles specific to a given snap.
   165  // The snap can be in developer mode to make security violations non-fatal to
   166  // the offending application process.
   167  //
   168  // This method should be called after changing plug, slots, connections between
   169  // them or application present in the snap.
   170  func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
   171  	snapName := snapInfo.InstanceName()
   172  	// Get the snippets that apply to this snap
   173  	spec, err := repo.SnapSpecification(b.Name(), snapName)
   174  	if err != nil {
   175  		return fmt.Errorf("cannot obtain seccomp specification for snap %q: %s", snapName, err)
   176  	}
   177  
   178  	// Get the snippets that apply to this snap
   179  	content, err := b.deriveContent(spec.(*Specification), opts, snapInfo)
   180  	if err != nil {
   181  		return fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err)
   182  	}
   183  
   184  	glob := interfaces.SecurityTagGlob(snapName) + ".src"
   185  	dir := dirs.SnapSeccompDir
   186  	if err := os.MkdirAll(dir, 0755); err != nil {
   187  		return fmt.Errorf("cannot create directory for seccomp profiles %q: %s", dir, err)
   188  	}
   189  	// There is a delicate interaction between `snap run`, `snap-confine`
   190  	// and compilation of profiles:
   191  	// - whenever profiles need to be rebuilt due to system-key change,
   192  	//   `snap run` detects the system-key mismatch and waits for snapd
   193  	//   (system key is only updated once all security backends have
   194  	//   finished their job)
   195  	// - whenever the binary file does not exist, `snap-confine` will poll
   196  	//   and wait for SNAP_CONFINE_MAX_PROFILE_WAIT, if the profile does not
   197  	//   appear in that time, `snap-confine` will fail and die
   198  	changed, removed, err := osutil.EnsureDirState(dir, glob, content)
   199  	if err != nil {
   200  		return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, err)
   201  	}
   202  	for _, c := range removed {
   203  		err := os.Remove(bpfBinPath(c))
   204  		if err != nil && !os.IsNotExist(err) {
   205  			return err
   206  		}
   207  	}
   208  	for _, c := range changed {
   209  		in := bpfSrcPath(c)
   210  		out := bpfBinPath(c)
   211  
   212  		// remove binary profile first
   213  		err := os.Remove(out)
   214  		if err != nil && !os.IsNotExist(err) {
   215  			return err
   216  		}
   217  
   218  		// snap-seccomp uses AtomicWriteFile internally, on failure the
   219  		// output file is unlinked
   220  		if err := b.snapSeccomp.Compile(in, out); err != nil {
   221  			return fmt.Errorf("cannot compile %s: %v", in, err)
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Remove removes seccomp profiles of a given snap.
   229  func (b *Backend) Remove(snapName string) error {
   230  	glob := interfaces.SecurityTagGlob(snapName)
   231  	_, _, err := osutil.EnsureDirState(dirs.SnapSeccompDir, glob, nil)
   232  	if err != nil {
   233  		return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, err)
   234  	}
   235  	return nil
   236  }
   237  
   238  // Obtain the privilege dropping snippet
   239  func uidGidChownSnippet(name string) (string, error) {
   240  	tmp := strings.Replace(privDropAndChownSyscalls, "###USERNAME###", name, -1)
   241  	return strings.Replace(tmp, "###GROUP###", name, -1), nil
   242  }
   243  
   244  // deriveContent combines security snippets collected from all the interfaces
   245  // affecting a given snap into a content map applicable to EnsureDirState.
   246  func (b *Backend) deriveContent(spec *Specification, opts interfaces.ConfinementOptions, snapInfo *snap.Info) (content map[string]*osutil.FileState, err error) {
   247  	// Some base snaps and systems require the socketcall() in the default
   248  	// template
   249  	addSocketcall := requiresSocketcall(snapInfo.Base)
   250  
   251  	var uidGidChownSyscalls bytes.Buffer
   252  	if len(snapInfo.SystemUsernames) == 0 {
   253  		uidGidChownSyscalls.WriteString(barePrivDropSyscalls)
   254  	} else {
   255  		for _, id := range snapInfo.SystemUsernames {
   256  			syscalls, err := uidGidChownSnippet(id.Name)
   257  			if err != nil {
   258  				return nil, fmt.Errorf(`cannot calculate syscalls for "%s": %s`, id, err)
   259  			}
   260  			uidGidChownSyscalls.WriteString(syscalls)
   261  		}
   262  		uidGidChownSyscalls.WriteString(rootSetUidGidSyscalls)
   263  	}
   264  
   265  	for _, hookInfo := range snapInfo.Hooks {
   266  		if content == nil {
   267  			content = make(map[string]*osutil.FileState)
   268  		}
   269  		securityTag := hookInfo.SecurityTag()
   270  
   271  		path := securityTag + ".src"
   272  		content[path] = &osutil.FileState{
   273  			Content: generateContent(opts, spec.SnippetForTag(securityTag), addSocketcall, b.versionInfo, uidGidChownSyscalls.String()),
   274  			Mode:    0644,
   275  		}
   276  	}
   277  	for _, appInfo := range snapInfo.Apps {
   278  		if content == nil {
   279  			content = make(map[string]*osutil.FileState)
   280  		}
   281  		securityTag := appInfo.SecurityTag()
   282  		path := securityTag + ".src"
   283  		content[path] = &osutil.FileState{
   284  			Content: generateContent(opts, spec.SnippetForTag(securityTag), addSocketcall, b.versionInfo, uidGidChownSyscalls.String()),
   285  			Mode:    0644,
   286  		}
   287  	}
   288  
   289  	return content, nil
   290  }
   291  
   292  func generateContent(opts interfaces.ConfinementOptions, snippetForTag string, addSocketcall bool, versionInfo seccomp_compiler.VersionInfo, uidGidChownSyscalls string) []byte {
   293  	var buffer bytes.Buffer
   294  
   295  	if versionInfo != "" {
   296  		buffer.WriteString("# snap-seccomp version information:\n")
   297  		fmt.Fprintf(&buffer, "# %s\n", versionInfo)
   298  	}
   299  
   300  	if opts.Classic && !opts.JailMode {
   301  		// NOTE: This is understood by snap-confine
   302  		buffer.WriteString("@unrestricted\n")
   303  	}
   304  	if opts.DevMode && !opts.JailMode {
   305  		// NOTE: This is understood by snap-confine
   306  		buffer.WriteString("@complain\n")
   307  		if !release.SecCompSupportsAction("log") {
   308  			buffer.WriteString("# complain mode logging unavailable\n")
   309  		}
   310  	}
   311  
   312  	buffer.Write(defaultTemplate)
   313  	buffer.WriteString(snippetForTag)
   314  	buffer.WriteString(uidGidChownSyscalls)
   315  
   316  	// For systems with force-devmode we need to apply a workaround
   317  	// to avoid failing hooks. See description in template.go for
   318  	// more details.
   319  	if release.ReleaseInfo.ForceDevMode() {
   320  		buffer.WriteString(bindSyscallWorkaround)
   321  	}
   322  
   323  	if addSocketcall {
   324  		buffer.WriteString(socketcallSyscallDeprecated)
   325  	}
   326  
   327  	return buffer.Bytes()
   328  }
   329  
   330  // NewSpecification returns an empty seccomp specification.
   331  func (b *Backend) NewSpecification() interfaces.Specification {
   332  	return &Specification{}
   333  }
   334  
   335  // SandboxFeatures returns the list of seccomp features supported by the kernel
   336  // and userspace.
   337  func (b *Backend) SandboxFeatures() []string {
   338  	features := kernelFeatures()
   339  	tags := make([]string, 0, len(features)+1)
   340  	for _, feature := range features {
   341  		// Prepend "kernel:" to apparmor kernel features to namespace
   342  		// them.
   343  		tags = append(tags, "kernel:"+feature)
   344  	}
   345  	tags = append(tags, "bpf-argument-filtering")
   346  
   347  	if res, err := b.versionInfo.HasFeature("bpf-actlog"); err == nil && res {
   348  		tags = append(tags, "bpf-actlog")
   349  	}
   350  
   351  	return tags
   352  }
   353  
   354  // Determine if the system requires the use of socketcall(). Factors:
   355  // - if the kernel architecture is amd64, armhf or arm64, do not require
   356  //   socketcall (unused on these architectures)
   357  // - if the kernel architecture is i386 or s390x
   358  //   - if the kernel is < 4.3, force the use of socketcall()
   359  //   - for backwards compatibility, if the system is Ubuntu 14.04 or lower,
   360  //     force use of socketcall()
   361  //   - for backwards compatibility, if the base snap is unspecified, "core" or
   362  //     "core16", then force use of socketcall()
   363  //   - otherwise (ie, if new enough kernel, not 14.04, and a non-16 base
   364  //     snap), don't force use of socketcall()
   365  // - if the kernel architecture is not any of the above, force the use of
   366  //   socketcall()
   367  func requiresSocketcallImpl(baseSnap string) bool {
   368  	switch dpkgKernelArchitecture() {
   369  	case "i386", "s390x":
   370  		// glibc sysdeps/unix/sysv/linux/i386/kernel-features.h and
   371  		// sysdeps/unix/sysv/linux/s390/kernel-features.h added the
   372  		// individual socket syscalls in 4.3.
   373  		if cmp, _ := strutil.VersionCompare(osutil.KernelVersion(), "4.3"); cmp < 0 {
   374  			return true
   375  		}
   376  
   377  		// For now, on 14.04, always require socketcall()
   378  		if releaseInfoId == "ubuntu" {
   379  			if cmp, _ := strutil.VersionCompare(releaseInfoVersionId, "14.04"); cmp <= 0 {
   380  				return true
   381  			}
   382  		}
   383  
   384  		// Detect when the base snap requires the use of socketcall().
   385  		//
   386  		// TODO: eventually try to auto-detect this. For now, err on
   387  		// the side of security and only require it for base snaps
   388  		// where we know we want it added. Technically, core16's glibc
   389  		// is new enough, but it always had socketcall in the template,
   390  		// so ensure backwards compatibility.
   391  		if baseSnap == "" || baseSnap == "core" || baseSnap == "core16" {
   392  			return true
   393  		}
   394  
   395  		// If none of the above, we don't need the syscall
   396  		return false
   397  	case "powerpc":
   398  		// glibc's sysdeps/unix/sysv/linux/powerpc/kernel-features.h
   399  		// states that the individual syscalls are all available as of
   400  		// 2.6.37. snapd isn't expected to run on these kernels so just
   401  		// default to unneeded.
   402  		return false
   403  	case "sparc", "sparc64":
   404  		// glibc's sysdeps/unix/sysv/linux/sparc/kernel-features.h
   405  		// indicates that socketcall() is used and the individual
   406  		// syscalls are undefined.
   407  		return true
   408  	default:
   409  		// amd64, arm64, armhf, ppc64el, etc
   410  		// glibc's sysdeps/unix/sysv/linux/kernel-features.h says that
   411  		// __ASSUME_SOCKETCALL will be defined on archs that need it.
   412  		// Modern architectures do not implement the socketcall()
   413  		// syscall and all the older architectures except sparc (see
   414  		// above) have introduced the individual syscalls, so default
   415  		// to unneeded.
   416  		return false
   417  	}
   418  
   419  	// If we got here, something went wrong, but if the code above changes
   420  	// the compiler will complain about the lack of 'return'.
   421  }
   422  
   423  // MockSnapSeccompVersionInfo is for use in tests only.
   424  func MockSnapSeccompVersionInfo(versionInfo string) (restore func()) {
   425  	old := snapSeccompVersionInfo
   426  	snapSeccompVersionInfo = func(c Compiler) (seccomp_compiler.VersionInfo, error) {
   427  		return seccomp_compiler.VersionInfo(versionInfo), nil
   428  	}
   429  	return func() {
   430  		snapSeccompVersionInfo = old
   431  	}
   432  }