github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"strings"
    36  
    37  	"github.com/snapcore/snapd/osutil"
    38  )
    39  
    40  // ValidateNoAppArmorRegexp will check that the given string does not
    41  // contain AppArmor regular expressions (AARE), double quotes or \0.
    42  func ValidateNoAppArmorRegexp(s string) error {
    43  	const AARE = `?*[]{}^"` + "\x00"
    44  
    45  	if strings.ContainsAny(s, AARE) {
    46  		return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE)
    47  	}
    48  	return nil
    49  }
    50  
    51  type aaParserFlags int
    52  
    53  const (
    54  	// skipReadCache causes apparmor_parser to be invoked with --skip-read-cache.
    55  	// This allows us to essentially overwrite a cache that we know is stale regardless
    56  	// of the time and date settings (apparmor_parser caching is based on mtime).
    57  	// Note that writing of the cache relies on --write-cache but we pass that
    58  	// command-line option unconditionally.
    59  	skipReadCache aaParserFlags = 1 << iota
    60  )
    61  
    62  // loadProfiles loads apparmor profiles from the given files.
    63  //
    64  // If no such profiles were previously loaded then they are simply added to the kernel.
    65  // If there were some profiles with the same name before, those profiles are replaced.
    66  func loadProfiles(fnames []string, cacheDir string, flags aaParserFlags) error {
    67  	if len(fnames) == 0 {
    68  		return nil
    69  	}
    70  
    71  	// Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858)
    72  	args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)}
    73  	if flags&skipReadCache != 0 {
    74  		args = append(args, "--skip-read-cache")
    75  	}
    76  	if !osutil.GetenvBool("SNAPD_DEBUG") {
    77  		args = append(args, "--quiet")
    78  	}
    79  	args = append(args, fnames...)
    80  
    81  	output, err := exec.Command("apparmor_parser", args...).CombinedOutput()
    82  	if err != nil {
    83  		return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output))
    84  	}
    85  	return nil
    86  }
    87  
    88  // unloadProfiles is meant to remove the named profiles from the running
    89  // kernel and then remove any cache files. Importantly, we can only unload
    90  // profiles when we are sure there are no lingering processes from the snap
    91  // (ie, forcibly stop all running processes from the snap). Otherwise, any
    92  // running processes will become unconfined. Since we don't have this guarantee
    93  // yet, leave the profiles loaded in the kernel but remove the cache files from
    94  // the system so the policy is gone on the next reboot. LP: #1818241
    95  func unloadProfiles(names []string, cacheDir string) error {
    96  	if len(names) == 0 {
    97  		return nil
    98  	}
    99  
   100  	/* TODO: uncomment when no lingering snap processes is guaranteed
   101  	// By the time this function is called, all the profiles (names) have
   102  	// been removed from dirs.SnapAppArmorDir, so to unload the profiles
   103  	// from the running kernel we must instead use sysfs and write the
   104  	// profile names one at a time to
   105  	// /sys/kernel/security/apparmor/.remove (with no trailing \n).
   106  	apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove"
   107  	if !osutil.IsWritable(appArmorSysFsRemove) {
   108  	        return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove)
   109  	}
   110  	for _, n := range names {
   111  	        // ignore errors since it is ok if the profile isn't removed
   112  	        // from the kernel
   113  	        ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666)
   114  	}
   115  	*/
   116  
   117  	// AppArmor 2.13 and higher has a cache forest while 2.12 and lower has
   118  	// a flat directory (on 2.12 and earlier, .features and the snap
   119  	// profiles are in the top-level directory instead of a subdirectory).
   120  	// With 2.13+, snap profiles are not expected to be in every
   121  	// subdirectory, so don't error on ENOENT but otherwise if we get an
   122  	// error, something weird happened so stop processing.
   123  	if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+
   124  		for _, p := range li {
   125  			dir := path.Dir(p)
   126  			if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) {
   127  				return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err)
   128  			}
   129  		}
   130  	} else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12-
   131  		return fmt.Errorf("cannot remove apparmor profile cache: %s", err)
   132  	}
   133  	return nil
   134  }
   135  
   136  // profilesPath contains information about the currently loaded apparmor profiles.
   137  const realProfilesPath = "/sys/kernel/security/apparmor/profiles"
   138  
   139  var profilesPath = realProfilesPath
   140  
   141  // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles.
   142  //
   143  // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on
   144  // the system (via snappy dimension) and those are filtered-out.
   145  func LoadedProfiles() ([]string, error) {
   146  	file, err := os.Open(profilesPath)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	defer file.Close()
   151  	var profiles []string
   152  	for {
   153  		var name, mode string
   154  		n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode)
   155  		if n > 0 && n != 2 {
   156  			return nil, fmt.Errorf("syntax error, expected: name (mode)")
   157  		}
   158  		if err == io.EOF {
   159  			break
   160  		}
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		if strings.HasPrefix(name, "snap.") {
   165  			profiles = append(profiles, name)
   166  		}
   167  	}
   168  	return profiles, nil
   169  }