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 }