gitee.com/mysnapcore/mysnapd@v0.1.0/sandbox/apparmor/apparmor.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 apparmor
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"sort"
    30  	"strings"
    31  	"sync"
    32  
    33  	"gitee.com/mysnapcore/mysnapd/dirs"
    34  	"gitee.com/mysnapcore/mysnapd/osutil"
    35  	"gitee.com/mysnapcore/mysnapd/strutil"
    36  )
    37  
    38  // For mocking in tests
    39  var (
    40  	osMkdirAll        = os.MkdirAll
    41  	osutilAtomicWrite = osutil.AtomicWrite
    42  )
    43  
    44  // ValidateNoAppArmorRegexp will check that the given string does not
    45  // contain AppArmor regular expressions (AARE), double quotes or \0.
    46  // Note that to check the inverse of this, that is that a string has
    47  // valid AARE, one should use interfaces/utils.NewPathPattern().
    48  func ValidateNoAppArmorRegexp(s string) error {
    49  	const AARE = `?*[]{}^"` + "\x00"
    50  
    51  	if strings.ContainsAny(s, AARE) {
    52  		return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE)
    53  	}
    54  	return nil
    55  }
    56  
    57  // LevelType encodes the kind of support for apparmor
    58  // found on this system.
    59  type LevelType int
    60  
    61  const (
    62  	// Unknown indicates that apparmor was not probed yet.
    63  	Unknown LevelType = iota
    64  	// Unsupported indicates that apparmor is not enabled.
    65  	Unsupported
    66  	// Unusable indicates that apparmor is enabled but cannot be used.
    67  	Unusable
    68  	// Partial indicates that apparmor is enabled but some
    69  	// features are missing.
    70  	Partial
    71  	// Full indicates that all features are supported.
    72  	Full
    73  )
    74  
    75  func setupConfCacheDirs(newrootdir string) {
    76  	ConfDir = filepath.Join(newrootdir, "/etc/apparmor.d")
    77  	CacheDir = filepath.Join(newrootdir, "/var/cache/apparmor")
    78  
    79  	SystemCacheDir = filepath.Join(ConfDir, "cache")
    80  	exists, isDir, _ := osutil.DirExists(SystemCacheDir)
    81  	if !exists || !isDir {
    82  		// some systems use a single cache dir instead of splitting
    83  		// out the system cache
    84  		// TODO: it seems Solus has a different setup too, investigate this
    85  		SystemCacheDir = CacheDir
    86  	}
    87  }
    88  
    89  func init() {
    90  	dirs.AddRootDirCallback(setupConfCacheDirs)
    91  	setupConfCacheDirs(dirs.GlobalRootDir)
    92  }
    93  
    94  var (
    95  	ConfDir        string
    96  	CacheDir       string
    97  	SystemCacheDir string
    98  )
    99  
   100  func (level LevelType) String() string {
   101  	switch level {
   102  	case Unknown:
   103  		return "unknown"
   104  	case Unsupported:
   105  		return "none"
   106  	case Unusable:
   107  		return "unusable"
   108  	case Partial:
   109  		return "partial"
   110  	case Full:
   111  		return "full"
   112  	}
   113  	return fmt.Sprintf("AppArmorLevelType:%d", level)
   114  }
   115  
   116  // appArmorAssessment represents what is supported AppArmor-wise by the system.
   117  var appArmorAssessment = &appArmorAssess{appArmorProber: &appArmorProbe{}}
   118  
   119  // ProbedLevel quantifies how well apparmor is supported on the current
   120  // kernel. The computation is costly to perform. The result is cached internally.
   121  func ProbedLevel() LevelType {
   122  	appArmorAssessment.assess()
   123  	return appArmorAssessment.level
   124  }
   125  
   126  // Summary describes how well apparmor is supported on the current
   127  // kernel. The computation is costly to perform. The result is cached
   128  // internally.
   129  func Summary() string {
   130  	appArmorAssessment.assess()
   131  	return appArmorAssessment.summary
   132  }
   133  
   134  // KernelFeatures returns a sorted list of apparmor features like
   135  // []string{"dbus", "network"}. The result is cached internally.
   136  func KernelFeatures() ([]string, error) {
   137  	return appArmorAssessment.KernelFeatures()
   138  }
   139  
   140  // ParserFeatures returns a sorted list of apparmor parser features
   141  // like []string{"unsafe", ...}. The computation is costly to perform. The
   142  // result is cached internally.
   143  func ParserFeatures() ([]string, error) {
   144  	return appArmorAssessment.ParserFeatures()
   145  }
   146  
   147  // ParserMtime returns the mtime of the AppArmor parser, else 0.
   148  func ParserMtime() int64 {
   149  	var mtime int64
   150  	mtime = 0
   151  
   152  	if path, err := findAppArmorParser(); err == nil {
   153  		if fi, err := os.Stat(path); err == nil {
   154  			mtime = fi.ModTime().Unix()
   155  		}
   156  	}
   157  	return mtime
   158  }
   159  
   160  // probe related code
   161  
   162  var (
   163  	// requiredParserFeatures denotes the features that must be present in the parser.
   164  	// Absence of any of those features results in the effective level be at most UnusableAppArmor.
   165  	requiredParserFeatures = []string{
   166  		"unsafe",
   167  	}
   168  	// preferredParserFeatures denotes the features that should be present in the parser.
   169  	// Absence of any of those features results in the effective level be at most PartialAppArmor.
   170  	preferredParserFeatures = []string{
   171  		"unsafe",
   172  	}
   173  	// requiredKernelFeatures denotes the features that must be present in the kernel.
   174  	// Absence of any of those features results in the effective level be at most UnusableAppArmor.
   175  	requiredKernelFeatures = []string{
   176  		// For now, require at least file and simply prefer the rest.
   177  		"file",
   178  	}
   179  	// preferredKernelFeatures denotes the features that should be present in the kernel.
   180  	// Absence of any of those features results in the effective level be at most PartialAppArmor.
   181  	preferredKernelFeatures = []string{
   182  		"caps",
   183  		"dbus",
   184  		"domain",
   185  		"file",
   186  		"mount",
   187  		"namespaces",
   188  		"network",
   189  		"ptrace",
   190  		"signal",
   191  	}
   192  	// Since AppArmorParserMtime() will be called by generateKey() in
   193  	// system-key and that could be called by different users on the
   194  	// system, use a predictable search path for finding the parser.
   195  	parserSearchPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
   196  
   197  	// Filesystem root defined locally to avoid dependency on the
   198  	// 'dirs' package
   199  	rootPath = "/"
   200  )
   201  
   202  // Each apparmor feature is manifested as a directory entry.
   203  const featuresSysPath = "sys/kernel/security/apparmor/features"
   204  
   205  type appArmorProber interface {
   206  	KernelFeatures() ([]string, error)
   207  	ParserFeatures() ([]string, error)
   208  }
   209  
   210  type appArmorAssess struct {
   211  	appArmorProber
   212  	// level contains the assessment of the "level" of apparmor support.
   213  	level LevelType
   214  	// summary contains a human readable description of the assessment.
   215  	summary string
   216  
   217  	once sync.Once
   218  }
   219  
   220  func (aaa *appArmorAssess) assess() {
   221  	aaa.once.Do(func() {
   222  		aaa.level, aaa.summary = aaa.doAssess()
   223  	})
   224  }
   225  
   226  func (aaa *appArmorAssess) doAssess() (level LevelType, summary string) {
   227  	// First, quickly check if apparmor is available in the kernel at all.
   228  	kernelFeatures, err := aaa.KernelFeatures()
   229  	if os.IsNotExist(err) {
   230  		return Unsupported, "apparmor not enabled"
   231  	}
   232  	// Then check that the parser supports the required parser features.
   233  	// If we have any missing required features then apparmor is unusable.
   234  	parserFeatures, err := aaa.ParserFeatures()
   235  	if os.IsNotExist(err) {
   236  		return Unsupported, "apparmor_parser not found"
   237  	}
   238  	var missingParserFeatures []string
   239  	for _, feature := range requiredParserFeatures {
   240  		if !strutil.SortedListContains(parserFeatures, feature) {
   241  			missingParserFeatures = append(missingParserFeatures, feature)
   242  		}
   243  	}
   244  	if len(missingParserFeatures) > 0 {
   245  		summary := fmt.Sprintf("apparmor_parser is available but required parser features are missing: %s",
   246  			strings.Join(missingParserFeatures, ", "))
   247  		return Unusable, summary
   248  	}
   249  
   250  	// Next, check that the kernel supports the required kernel features.
   251  	var missingKernelFeatures []string
   252  	for _, feature := range requiredKernelFeatures {
   253  		if !strutil.SortedListContains(kernelFeatures, feature) {
   254  			missingKernelFeatures = append(missingKernelFeatures, feature)
   255  		}
   256  	}
   257  	if len(missingKernelFeatures) > 0 {
   258  		summary := fmt.Sprintf("apparmor is enabled but required kernel features are missing: %s",
   259  			strings.Join(missingKernelFeatures, ", "))
   260  		return Unusable, summary
   261  	}
   262  
   263  	// Next check that the parser supports preferred parser features.
   264  	// If we have any missing preferred features then apparmor is partially enabled.
   265  	for _, feature := range preferredParserFeatures {
   266  		if !strutil.SortedListContains(parserFeatures, feature) {
   267  			missingParserFeatures = append(missingParserFeatures, feature)
   268  		}
   269  	}
   270  	if len(missingParserFeatures) > 0 {
   271  		summary := fmt.Sprintf("apparmor_parser is available but some features are missing: %s",
   272  			strings.Join(missingParserFeatures, ", "))
   273  		return Partial, summary
   274  	}
   275  
   276  	// Lastly check that the kernel supports preferred kernel features.
   277  	for _, feature := range preferredKernelFeatures {
   278  		if !strutil.SortedListContains(kernelFeatures, feature) {
   279  			missingKernelFeatures = append(missingKernelFeatures, feature)
   280  		}
   281  	}
   282  	if len(missingKernelFeatures) > 0 {
   283  		summary := fmt.Sprintf("apparmor is enabled but some kernel features are missing: %s",
   284  			strings.Join(missingKernelFeatures, ", "))
   285  		return Partial, summary
   286  	}
   287  
   288  	// If we got here then all features are available and supported.
   289  	return Full, "apparmor is enabled and all features are available"
   290  }
   291  
   292  type appArmorProbe struct {
   293  	// kernelFeatures contains a list of kernel features that are supported.
   294  	kernelFeatures []string
   295  	// kernelError contains an error, if any, encountered when
   296  	// discovering available kernel features.
   297  	kernelError error
   298  	// parserFeatures contains a list of parser features that are supported.
   299  	parserFeatures []string
   300  	// parserError contains an error, if any, encountered when
   301  	// discovering available parser features.
   302  	parserError error
   303  
   304  	probeKernelOnce sync.Once
   305  	probeParserOnce sync.Once
   306  }
   307  
   308  func (aap *appArmorProbe) KernelFeatures() ([]string, error) {
   309  	aap.probeKernelOnce.Do(func() {
   310  		aap.kernelFeatures, aap.kernelError = probeKernelFeatures()
   311  	})
   312  	return aap.kernelFeatures, aap.kernelError
   313  }
   314  
   315  func (aap *appArmorProbe) ParserFeatures() ([]string, error) {
   316  	aap.probeParserOnce.Do(func() {
   317  		aap.parserFeatures, aap.parserError = probeParserFeatures()
   318  	})
   319  	return aap.parserFeatures, aap.parserError
   320  }
   321  
   322  func probeKernelFeatures() ([]string, error) {
   323  	// note that ioutil.ReadDir() is already sorted
   324  	dentries, err := ioutil.ReadDir(filepath.Join(rootPath, featuresSysPath))
   325  	if err != nil {
   326  		return []string{}, err
   327  	}
   328  	features := make([]string, 0, len(dentries))
   329  	for _, fi := range dentries {
   330  		if fi.IsDir() {
   331  			features = append(features, fi.Name())
   332  		}
   333  	}
   334  	return features, nil
   335  }
   336  
   337  func probeParserFeatures() ([]string, error) {
   338  	parser, err := findAppArmorParser()
   339  	if err != nil {
   340  		return []string{}, err
   341  	}
   342  	features := make([]string, 0, 4)
   343  	if tryAppArmorParserFeature(parser, "change_profile unsafe /**,") {
   344  		features = append(features, "unsafe")
   345  	}
   346  	if tryAppArmorParserFeature(parser, "network qipcrtr dgram,") {
   347  		features = append(features, "qipcrtr-socket")
   348  	}
   349  	if tryAppArmorParserFeature(parser, "capability bpf,") {
   350  		features = append(features, "cap-bpf")
   351  	}
   352  	if tryAppArmorParserFeature(parser, "capability audit_read,") {
   353  		features = append(features, "cap-audit-read")
   354  	}
   355  	if tryAppArmorParserFeature(parser, "mqueue,") {
   356  		features = append(features, "mqueue")
   357  	}
   358  	sort.Strings(features)
   359  	return features, nil
   360  }
   361  
   362  // findAppArmorParser returns the path of the apparmor_parser binary if one is found.
   363  func findAppArmorParser() (string, error) {
   364  	for _, dir := range filepath.SplitList(parserSearchPath) {
   365  		path := filepath.Join(dir, "apparmor_parser")
   366  		if _, err := os.Stat(path); err == nil {
   367  			return path, nil
   368  		}
   369  	}
   370  	return "", os.ErrNotExist
   371  }
   372  
   373  // tryAppArmorParserFeature attempts to pre-process a bit of apparmor syntax with a given parser.
   374  func tryAppArmorParserFeature(parser, rule string) bool {
   375  	cmd := exec.Command(parser, "--preprocess")
   376  	cmd.Stdin = bytes.NewBufferString(fmt.Sprintf("profile snap-test {\n %s\n}", rule))
   377  	if err := cmd.Run(); err != nil {
   378  		return false
   379  	}
   380  	return true
   381  }
   382  
   383  // UpdateHomedirsTunable sets the AppArmor HOMEDIRS tunable to the list of the
   384  // specified directories. This directly affects the value of the AppArmor
   385  // @{HOME} variable. See the "/etc/apparmor.d/tunables/home" file for more
   386  // information.
   387  func UpdateHomedirsTunable(homedirs []string) error {
   388  	homeTunableDir := filepath.Join(ConfDir, "tunables/home.d")
   389  	tunableFilePath := filepath.Join(homeTunableDir, "snapd")
   390  
   391  	// If the file is not there and `homedirs` is empty, do nothing; this is
   392  	// not just an optimisation, but a necessity in Ubuntu Core: the
   393  	// /etc/apparmor.d/ tree is read-only, and attempting to create the file
   394  	// would generate an error.
   395  	if len(homedirs) == 0 && !osutil.FileExists(tunableFilePath) {
   396  		return nil
   397  	}
   398  
   399  	if err := osMkdirAll(homeTunableDir, 0755); err != nil {
   400  		return fmt.Errorf("cannot create AppArmor tunable directory: %v", err)
   401  	}
   402  
   403  	contents := &bytes.Buffer{}
   404  	fmt.Fprintln(contents, "# Generated by snapd -- DO NOT EDIT!")
   405  	if len(homedirs) > 0 {
   406  		contents.Write([]byte("@{HOMEDIRS}+="))
   407  		separator := ""
   408  		for _, dir := range homedirs {
   409  			fmt.Fprintf(contents, `%s"%s"`, separator, dir)
   410  			separator = " "
   411  		}
   412  	}
   413  	return osutilAtomicWrite(tunableFilePath, contents, 0644, 0)
   414  }
   415  
   416  // mocking
   417  
   418  type mockAppArmorProbe struct {
   419  	kernelFeatures []string
   420  	kernelError    error
   421  	parserFeatures []string
   422  	parserError    error
   423  }
   424  
   425  func (m *mockAppArmorProbe) KernelFeatures() ([]string, error) {
   426  	return m.kernelFeatures, m.kernelError
   427  }
   428  
   429  func (m *mockAppArmorProbe) ParserFeatures() ([]string, error) {
   430  	return m.parserFeatures, m.parserError
   431  }
   432  
   433  // MockAppArmorLevel makes the system believe it has certain level of apparmor
   434  // support.
   435  //
   436  // AppArmor kernel and parser features are set to unrealistic values that do
   437  // not match the requested level. Use this function to observe behavior that
   438  // relies solely on the apparmor level value.
   439  func MockLevel(level LevelType) (restore func()) {
   440  	oldAppArmorAssessment := appArmorAssessment
   441  	mockProbe := &mockAppArmorProbe{
   442  		kernelFeatures: []string{"mocked-kernel-feature"},
   443  		parserFeatures: []string{"mocked-parser-feature"},
   444  	}
   445  	appArmorAssessment = &appArmorAssess{
   446  		appArmorProber: mockProbe,
   447  		level:          level,
   448  		summary:        fmt.Sprintf("mocked apparmor level: %s", level),
   449  	}
   450  	appArmorAssessment.once.Do(func() {})
   451  	return func() {
   452  		appArmorAssessment = oldAppArmorAssessment
   453  	}
   454  }
   455  
   456  // MockAppArmorFeatures makes the system believe it has certain kernel and
   457  // parser features.
   458  //
   459  // AppArmor level and summary are automatically re-assessed as needed
   460  // on both the change and the restore process. Use this function to
   461  // observe real assessment of arbitrary features.
   462  func MockFeatures(kernelFeatures []string, kernelError error, parserFeatures []string, parserError error) (restore func()) {
   463  	oldAppArmorAssessment := appArmorAssessment
   464  	mockProbe := &mockAppArmorProbe{
   465  		kernelFeatures: kernelFeatures,
   466  		kernelError:    kernelError,
   467  		parserFeatures: parserFeatures,
   468  		parserError:    parserError,
   469  	}
   470  	appArmorAssessment = &appArmorAssess{
   471  		appArmorProber: mockProbe,
   472  	}
   473  	appArmorAssessment.assess()
   474  	return func() {
   475  		appArmorAssessment = oldAppArmorAssessment
   476  	}
   477  
   478  }