github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/interfaces/apparmor/apparmor.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 contains primitives for working with apparmor.
    21  //
    22  // References:
    23  //  - http://wiki.apparmor.net/index.php/Kernel_interfaces
    24  //  - http://apparmor.wiki.kernel.org/
    25  //  - http://manpages.ubuntu.com/manpages/xenial/en/man7/apparmor.7.html
    26  package apparmor
    27  
    28  import (
    29  	"fmt"
    30  	"io"
    31  	"os"
    32  	"os/exec"
    33  	"path"
    34  	"path/filepath"
    35  	"runtime"
    36  	"strings"
    37  
    38  	"github.com/snapcore/snapd/osutil"
    39  )
    40  
    41  // ValidateNoAppArmorRegexp will check that the given string does not
    42  // contain AppArmor regular expressions (AARE), double quotes or \0.
    43  func ValidateNoAppArmorRegexp(s string) error {
    44  	const AARE = `?*[]{}^"` + "\x00"
    45  
    46  	if strings.ContainsAny(s, AARE) {
    47  		return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE)
    48  	}
    49  	return nil
    50  }
    51  
    52  type aaParserFlags int
    53  
    54  const (
    55  	// skipReadCache causes apparmor_parser to be invoked with --skip-read-cache.
    56  	// This allows us to essentially overwrite a cache that we know is stale regardless
    57  	// of the time and date settings (apparmor_parser caching is based on mtime).
    58  	// Note that writing of the cache relies on --write-cache but we pass that
    59  	// command-line option unconditionally.
    60  	skipReadCache aaParserFlags = 1 << iota
    61  
    62  	// conserveCPU tells apparmor_parser to spare up to two CPUs on multi-core systems to
    63  	// reduce load when processing many profiles at once.
    64  	conserveCPU aaParserFlags = 1 << iota
    65  
    66  	// skipKernelLoad tells apparmor_parser not to load profiles into the kernel. The use
    67  	// case of this is when in pre-seeding mode.
    68  	skipKernelLoad aaParserFlags = 1 << iota
    69  )
    70  
    71  var runtimeNumCPU = runtime.NumCPU
    72  
    73  func maybeSetNumberOfJobs() string {
    74  	cpus := runtimeNumCPU()
    75  	// Do not use all CPUs as this may have negative impact when booting.
    76  	if cpus > 2 {
    77  		// otherwise spare 2
    78  		cpus = cpus - 2
    79  	} else {
    80  		// Systems with only two CPUs, spare 1.
    81  		//
    82  		// When there is a a single CPU, pass -j1 to allow a single
    83  		// compilation job only. Note, we could pass -j0 in such case
    84  		// for further improvement, but that has incompatible meaning
    85  		// between apparmor 2.x (automatic job count, equivalent to
    86  		// -jauto) and 3.x (compile everything in the main process).
    87  		cpus = 1
    88  	}
    89  
    90  	return fmt.Sprintf("-j%d", cpus)
    91  }
    92  
    93  // loadProfiles loads apparmor profiles from the given files.
    94  //
    95  // If no such profiles were previously loaded then they are simply added to the kernel.
    96  // If there were some profiles with the same name before, those profiles are replaced.
    97  func loadProfiles(fnames []string, cacheDir string, flags aaParserFlags) error {
    98  	if len(fnames) == 0 {
    99  		return nil
   100  	}
   101  
   102  	// Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858)
   103  	args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)}
   104  	if flags&conserveCPU != 0 {
   105  		if jobArg := maybeSetNumberOfJobs(); jobArg != "" {
   106  			args = append(args, jobArg)
   107  		}
   108  	}
   109  
   110  	if flags&skipKernelLoad != 0 {
   111  		args = append(args, "--skip-kernel-load")
   112  	}
   113  
   114  	if flags&skipReadCache != 0 {
   115  		args = append(args, "--skip-read-cache")
   116  	}
   117  	if !osutil.GetenvBool("SNAPD_DEBUG") {
   118  		args = append(args, "--quiet")
   119  	}
   120  	args = append(args, fnames...)
   121  
   122  	output, err := exec.Command("apparmor_parser", args...).CombinedOutput()
   123  	if err != nil {
   124  		return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output))
   125  	}
   126  	return nil
   127  }
   128  
   129  // unloadProfiles is meant to remove the named profiles from the running
   130  // kernel and then remove any cache files. Importantly, we can only unload
   131  // profiles when we are sure there are no lingering processes from the snap
   132  // (ie, forcibly stop all running processes from the snap). Otherwise, any
   133  // running processes will become unconfined. Since we don't have this guarantee
   134  // yet, leave the profiles loaded in the kernel but remove the cache files from
   135  // the system so the policy is gone on the next reboot. LP: #1818241
   136  func unloadProfiles(names []string, cacheDir string) error {
   137  	if len(names) == 0 {
   138  		return nil
   139  	}
   140  
   141  	/* TODO: uncomment when no lingering snap processes is guaranteed
   142  	// By the time this function is called, all the profiles (names) have
   143  	// been removed from dirs.SnapAppArmorDir, so to unload the profiles
   144  	// from the running kernel we must instead use sysfs and write the
   145  	// profile names one at a time to
   146  	// /sys/kernel/security/apparmor/.remove (with no trailing \n).
   147  	apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove"
   148  	if !osutil.IsWritable(appArmorSysFsRemove) {
   149  	        return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove)
   150  	}
   151  	for _, n := range names {
   152  	        // ignore errors since it is ok if the profile isn't removed
   153  	        // from the kernel
   154  	        ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666)
   155  	}
   156  	*/
   157  
   158  	// AppArmor 2.13 and higher has a cache forest while 2.12 and lower has
   159  	// a flat directory (on 2.12 and earlier, .features and the snap
   160  	// profiles are in the top-level directory instead of a subdirectory).
   161  	// With 2.13+, snap profiles are not expected to be in every
   162  	// subdirectory, so don't error on ENOENT but otherwise if we get an
   163  	// error, something weird happened so stop processing.
   164  	if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+
   165  		for _, p := range li {
   166  			dir := path.Dir(p)
   167  			if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) {
   168  				return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err)
   169  			}
   170  		}
   171  	} else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12-
   172  		return fmt.Errorf("cannot remove apparmor profile cache: %s", err)
   173  	}
   174  	return nil
   175  }
   176  
   177  // profilesPath contains information about the currently loaded apparmor profiles.
   178  const realProfilesPath = "/sys/kernel/security/apparmor/profiles"
   179  
   180  var profilesPath = realProfilesPath
   181  
   182  // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles.
   183  //
   184  // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on
   185  // the system (via snappy dimension) and those are filtered-out.
   186  func LoadedProfiles() ([]string, error) {
   187  	file, err := os.Open(profilesPath)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	defer file.Close()
   192  	var profiles []string
   193  	for {
   194  		var name, mode string
   195  		n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode)
   196  		if n > 0 && n != 2 {
   197  			return nil, fmt.Errorf("syntax error, expected: name (mode)")
   198  		}
   199  		if err == io.EOF {
   200  			break
   201  		}
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		if strings.HasPrefix(name, "snap.") {
   206  			profiles = append(profiles, name)
   207  		}
   208  	}
   209  	return profiles, nil
   210  }