github.com/containerd/nerdctl@v1.7.7/pkg/apparmorutil/apparmorutil_linux.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package apparmorutil
    18  
    19  import (
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/containerd/containerd/pkg/apparmor"
    27  	"github.com/containerd/log"
    28  	"github.com/moby/sys/userns"
    29  )
    30  
    31  // CanLoadNewProfile returns whether the current process can load a new AppArmor profile.
    32  //
    33  // CanLoadNewProfile needs root.
    34  //
    35  // CanLoadNewProfile checks both /sys/module/apparmor/parameters/enabled and /sys/kernel/security.
    36  //
    37  // Related: https://gitlab.com/apparmor/apparmor/-/blob/v3.0.3/libraries/libapparmor/src/kernel.c#L311
    38  func CanLoadNewProfile() bool {
    39  	return !userns.RunningInUserNS() && os.Geteuid() == 0 && apparmor.HostSupports()
    40  }
    41  
    42  var (
    43  	paramEnabled     bool
    44  	paramEnabledOnce sync.Once
    45  )
    46  
    47  // CanApplyExistingProfile returns whether the current process can apply an existing AppArmor profile
    48  // to processes.
    49  //
    50  // CanApplyExistingProfile does NOT need root.
    51  //
    52  // CanApplyExistingProfile checks /sys/module/apparmor/parameters/enabled ,but does NOT check /sys/kernel/security/apparmor ,
    53  // which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)
    54  //
    55  // Related: https://gitlab.com/apparmor/apparmor/-/blob/v3.0.3/libraries/libapparmor/src/kernel.c#L311
    56  func CanApplyExistingProfile() bool {
    57  	paramEnabledOnce.Do(func() {
    58  		buf, err := os.ReadFile("/sys/module/apparmor/parameters/enabled")
    59  		paramEnabled = err == nil && len(buf) > 1 && buf[0] == 'Y'
    60  	})
    61  	return paramEnabled
    62  }
    63  
    64  // CanApplySpecificExistingProfile attempts to run `aa-exec -p <NAME> -- true` to check whether
    65  // the profile can be applied.
    66  //
    67  // CanApplySpecificExistingProfile does NOT depend on /sys/kernel/security/apparmor/profiles ,
    68  // which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)
    69  func CanApplySpecificExistingProfile(profileName string) bool {
    70  	if !CanApplyExistingProfile() {
    71  		return false
    72  	}
    73  	cmd := exec.Command("aa-exec", "-p", profileName, "--", "true")
    74  	out, err := cmd.CombinedOutput()
    75  	if err != nil {
    76  		log.L.WithError(err).Debugf("failed to run %v: %q", cmd.Args, string(out))
    77  		return false
    78  	}
    79  	return true
    80  }
    81  
    82  type Profile struct {
    83  	Name string `json:"Name"`           // e.g., "nerdctl-default"
    84  	Mode string `json:"Mode,omitempty"` // e.g., "enforce"
    85  }
    86  
    87  // Profiles return profiles.
    88  //
    89  // Profiles does not need the root but needs access to /sys/kernel/security/apparmor/policy/profiles,
    90  // which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)
    91  //
    92  // So, Profiles cannot be called from rootless child.
    93  func Profiles() ([]Profile, error) {
    94  	const profilesPath = "/sys/kernel/security/apparmor/policy/profiles"
    95  	ents, err := os.ReadDir(profilesPath)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	res := make([]Profile, len(ents))
   100  	for i, ent := range ents {
   101  		namePath := filepath.Join(profilesPath, ent.Name(), "name")
   102  		b, err := os.ReadFile(namePath)
   103  		if err != nil {
   104  			log.L.WithError(err).Warnf("failed to read %q", namePath)
   105  			continue
   106  		}
   107  		profile := Profile{
   108  			Name: strings.TrimSpace(string(b)),
   109  		}
   110  		modePath := filepath.Join(profilesPath, ent.Name(), "mode")
   111  		b, err = os.ReadFile(modePath)
   112  		if err != nil {
   113  			log.L.WithError(err).Warnf("failed to read %q", namePath)
   114  		} else {
   115  			profile.Mode = strings.TrimSpace(string(b))
   116  		}
   117  		res[i] = profile
   118  	}
   119  	return res, nil
   120  }
   121  
   122  // Unload unloads a profile. Needs access to /sys/kernel/security/apparmor/.remove .
   123  func Unload(target string) error {
   124  	remover, err := os.OpenFile("/sys/kernel/security/apparmor/.remove", os.O_RDWR|os.O_TRUNC, 0644)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	if _, err := remover.Write([]byte(target)); err != nil {
   129  		remover.Close()
   130  		return err
   131  	}
   132  	return remover.Close()
   133  }