github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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. Note, -j0 has special meaning
    76  	// so we don't want to pass it to apparmor parser.
    77  	if cpus > 1 {
    78  		if cpus == 2 {
    79  			// systems with only two CPUs, spare 1
    80  			return fmt.Sprintf("-j%d", cpus-1)
    81  		} else {
    82  			// otherwise spare 2
    83  			return fmt.Sprintf("-j%d", cpus-2)
    84  		}
    85  	}
    86  	return ""
    87  }
    88  
    89  // loadProfiles loads apparmor profiles from the given files.
    90  //
    91  // If no such profiles were previously loaded then they are simply added to the kernel.
    92  // If there were some profiles with the same name before, those profiles are replaced.
    93  func loadProfiles(fnames []string, cacheDir string, flags aaParserFlags) error {
    94  	if len(fnames) == 0 {
    95  		return nil
    96  	}
    97  
    98  	// Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858)
    99  	args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)}
   100  	if flags&conserveCPU != 0 {
   101  		if jobArg := maybeSetNumberOfJobs(); jobArg != "" {
   102  			args = append(args, jobArg)
   103  		}
   104  	}
   105  
   106  	if flags&skipKernelLoad != 0 {
   107  		args = append(args, "--skip-kernel-load")
   108  	}
   109  
   110  	if flags&skipReadCache != 0 {
   111  		args = append(args, "--skip-read-cache")
   112  	}
   113  	if !osutil.GetenvBool("SNAPD_DEBUG") {
   114  		args = append(args, "--quiet")
   115  	}
   116  	args = append(args, fnames...)
   117  
   118  	output, err := exec.Command("apparmor_parser", args...).CombinedOutput()
   119  	if err != nil {
   120  		return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output))
   121  	}
   122  	return nil
   123  }
   124  
   125  // unloadProfiles is meant to remove the named profiles from the running
   126  // kernel and then remove any cache files. Importantly, we can only unload
   127  // profiles when we are sure there are no lingering processes from the snap
   128  // (ie, forcibly stop all running processes from the snap). Otherwise, any
   129  // running processes will become unconfined. Since we don't have this guarantee
   130  // yet, leave the profiles loaded in the kernel but remove the cache files from
   131  // the system so the policy is gone on the next reboot. LP: #1818241
   132  func unloadProfiles(names []string, cacheDir string) error {
   133  	if len(names) == 0 {
   134  		return nil
   135  	}
   136  
   137  	/* TODO: uncomment when no lingering snap processes is guaranteed
   138  	// By the time this function is called, all the profiles (names) have
   139  	// been removed from dirs.SnapAppArmorDir, so to unload the profiles
   140  	// from the running kernel we must instead use sysfs and write the
   141  	// profile names one at a time to
   142  	// /sys/kernel/security/apparmor/.remove (with no trailing \n).
   143  	apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove"
   144  	if !osutil.IsWritable(appArmorSysFsRemove) {
   145  	        return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove)
   146  	}
   147  	for _, n := range names {
   148  	        // ignore errors since it is ok if the profile isn't removed
   149  	        // from the kernel
   150  	        ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666)
   151  	}
   152  	*/
   153  
   154  	// AppArmor 2.13 and higher has a cache forest while 2.12 and lower has
   155  	// a flat directory (on 2.12 and earlier, .features and the snap
   156  	// profiles are in the top-level directory instead of a subdirectory).
   157  	// With 2.13+, snap profiles are not expected to be in every
   158  	// subdirectory, so don't error on ENOENT but otherwise if we get an
   159  	// error, something weird happened so stop processing.
   160  	if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+
   161  		for _, p := range li {
   162  			dir := path.Dir(p)
   163  			if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) {
   164  				return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err)
   165  			}
   166  		}
   167  	} else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12-
   168  		return fmt.Errorf("cannot remove apparmor profile cache: %s", err)
   169  	}
   170  	return nil
   171  }
   172  
   173  // profilesPath contains information about the currently loaded apparmor profiles.
   174  const realProfilesPath = "/sys/kernel/security/apparmor/profiles"
   175  
   176  var profilesPath = realProfilesPath
   177  
   178  // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles.
   179  //
   180  // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on
   181  // the system (via snappy dimension) and those are filtered-out.
   182  func LoadedProfiles() ([]string, error) {
   183  	file, err := os.Open(profilesPath)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	defer file.Close()
   188  	var profiles []string
   189  	for {
   190  		var name, mode string
   191  		n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode)
   192  		if n > 0 && n != 2 {
   193  			return nil, fmt.Errorf("syntax error, expected: name (mode)")
   194  		}
   195  		if err == io.EOF {
   196  			break
   197  		}
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		if strings.HasPrefix(name, "snap.") {
   202  			profiles = append(profiles, name)
   203  		}
   204  	}
   205  	return profiles, nil
   206  }