gitee.com/mysnapcore/mysnapd@v0.1.0/sandbox/apparmor/apparmor.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2018 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 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "sort" 30 "strings" 31 "sync" 32 33 "gitee.com/mysnapcore/mysnapd/dirs" 34 "gitee.com/mysnapcore/mysnapd/osutil" 35 "gitee.com/mysnapcore/mysnapd/strutil" 36 ) 37 38 // For mocking in tests 39 var ( 40 osMkdirAll = os.MkdirAll 41 osutilAtomicWrite = osutil.AtomicWrite 42 ) 43 44 // ValidateNoAppArmorRegexp will check that the given string does not 45 // contain AppArmor regular expressions (AARE), double quotes or \0. 46 // Note that to check the inverse of this, that is that a string has 47 // valid AARE, one should use interfaces/utils.NewPathPattern(). 48 func ValidateNoAppArmorRegexp(s string) error { 49 const AARE = `?*[]{}^"` + "\x00" 50 51 if strings.ContainsAny(s, AARE) { 52 return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE) 53 } 54 return nil 55 } 56 57 // LevelType encodes the kind of support for apparmor 58 // found on this system. 59 type LevelType int 60 61 const ( 62 // Unknown indicates that apparmor was not probed yet. 63 Unknown LevelType = iota 64 // Unsupported indicates that apparmor is not enabled. 65 Unsupported 66 // Unusable indicates that apparmor is enabled but cannot be used. 67 Unusable 68 // Partial indicates that apparmor is enabled but some 69 // features are missing. 70 Partial 71 // Full indicates that all features are supported. 72 Full 73 ) 74 75 func setupConfCacheDirs(newrootdir string) { 76 ConfDir = filepath.Join(newrootdir, "/etc/apparmor.d") 77 CacheDir = filepath.Join(newrootdir, "/var/cache/apparmor") 78 79 SystemCacheDir = filepath.Join(ConfDir, "cache") 80 exists, isDir, _ := osutil.DirExists(SystemCacheDir) 81 if !exists || !isDir { 82 // some systems use a single cache dir instead of splitting 83 // out the system cache 84 // TODO: it seems Solus has a different setup too, investigate this 85 SystemCacheDir = CacheDir 86 } 87 } 88 89 func init() { 90 dirs.AddRootDirCallback(setupConfCacheDirs) 91 setupConfCacheDirs(dirs.GlobalRootDir) 92 } 93 94 var ( 95 ConfDir string 96 CacheDir string 97 SystemCacheDir string 98 ) 99 100 func (level LevelType) String() string { 101 switch level { 102 case Unknown: 103 return "unknown" 104 case Unsupported: 105 return "none" 106 case Unusable: 107 return "unusable" 108 case Partial: 109 return "partial" 110 case Full: 111 return "full" 112 } 113 return fmt.Sprintf("AppArmorLevelType:%d", level) 114 } 115 116 // appArmorAssessment represents what is supported AppArmor-wise by the system. 117 var appArmorAssessment = &appArmorAssess{appArmorProber: &appArmorProbe{}} 118 119 // ProbedLevel quantifies how well apparmor is supported on the current 120 // kernel. The computation is costly to perform. The result is cached internally. 121 func ProbedLevel() LevelType { 122 appArmorAssessment.assess() 123 return appArmorAssessment.level 124 } 125 126 // Summary describes how well apparmor is supported on the current 127 // kernel. The computation is costly to perform. The result is cached 128 // internally. 129 func Summary() string { 130 appArmorAssessment.assess() 131 return appArmorAssessment.summary 132 } 133 134 // KernelFeatures returns a sorted list of apparmor features like 135 // []string{"dbus", "network"}. The result is cached internally. 136 func KernelFeatures() ([]string, error) { 137 return appArmorAssessment.KernelFeatures() 138 } 139 140 // ParserFeatures returns a sorted list of apparmor parser features 141 // like []string{"unsafe", ...}. The computation is costly to perform. The 142 // result is cached internally. 143 func ParserFeatures() ([]string, error) { 144 return appArmorAssessment.ParserFeatures() 145 } 146 147 // ParserMtime returns the mtime of the AppArmor parser, else 0. 148 func ParserMtime() int64 { 149 var mtime int64 150 mtime = 0 151 152 if path, err := findAppArmorParser(); err == nil { 153 if fi, err := os.Stat(path); err == nil { 154 mtime = fi.ModTime().Unix() 155 } 156 } 157 return mtime 158 } 159 160 // probe related code 161 162 var ( 163 // requiredParserFeatures denotes the features that must be present in the parser. 164 // Absence of any of those features results in the effective level be at most UnusableAppArmor. 165 requiredParserFeatures = []string{ 166 "unsafe", 167 } 168 // preferredParserFeatures denotes the features that should be present in the parser. 169 // Absence of any of those features results in the effective level be at most PartialAppArmor. 170 preferredParserFeatures = []string{ 171 "unsafe", 172 } 173 // requiredKernelFeatures denotes the features that must be present in the kernel. 174 // Absence of any of those features results in the effective level be at most UnusableAppArmor. 175 requiredKernelFeatures = []string{ 176 // For now, require at least file and simply prefer the rest. 177 "file", 178 } 179 // preferredKernelFeatures denotes the features that should be present in the kernel. 180 // Absence of any of those features results in the effective level be at most PartialAppArmor. 181 preferredKernelFeatures = []string{ 182 "caps", 183 "dbus", 184 "domain", 185 "file", 186 "mount", 187 "namespaces", 188 "network", 189 "ptrace", 190 "signal", 191 } 192 // Since AppArmorParserMtime() will be called by generateKey() in 193 // system-key and that could be called by different users on the 194 // system, use a predictable search path for finding the parser. 195 parserSearchPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 196 197 // Filesystem root defined locally to avoid dependency on the 198 // 'dirs' package 199 rootPath = "/" 200 ) 201 202 // Each apparmor feature is manifested as a directory entry. 203 const featuresSysPath = "sys/kernel/security/apparmor/features" 204 205 type appArmorProber interface { 206 KernelFeatures() ([]string, error) 207 ParserFeatures() ([]string, error) 208 } 209 210 type appArmorAssess struct { 211 appArmorProber 212 // level contains the assessment of the "level" of apparmor support. 213 level LevelType 214 // summary contains a human readable description of the assessment. 215 summary string 216 217 once sync.Once 218 } 219 220 func (aaa *appArmorAssess) assess() { 221 aaa.once.Do(func() { 222 aaa.level, aaa.summary = aaa.doAssess() 223 }) 224 } 225 226 func (aaa *appArmorAssess) doAssess() (level LevelType, summary string) { 227 // First, quickly check if apparmor is available in the kernel at all. 228 kernelFeatures, err := aaa.KernelFeatures() 229 if os.IsNotExist(err) { 230 return Unsupported, "apparmor not enabled" 231 } 232 // Then check that the parser supports the required parser features. 233 // If we have any missing required features then apparmor is unusable. 234 parserFeatures, err := aaa.ParserFeatures() 235 if os.IsNotExist(err) { 236 return Unsupported, "apparmor_parser not found" 237 } 238 var missingParserFeatures []string 239 for _, feature := range requiredParserFeatures { 240 if !strutil.SortedListContains(parserFeatures, feature) { 241 missingParserFeatures = append(missingParserFeatures, feature) 242 } 243 } 244 if len(missingParserFeatures) > 0 { 245 summary := fmt.Sprintf("apparmor_parser is available but required parser features are missing: %s", 246 strings.Join(missingParserFeatures, ", ")) 247 return Unusable, summary 248 } 249 250 // Next, check that the kernel supports the required kernel features. 251 var missingKernelFeatures []string 252 for _, feature := range requiredKernelFeatures { 253 if !strutil.SortedListContains(kernelFeatures, feature) { 254 missingKernelFeatures = append(missingKernelFeatures, feature) 255 } 256 } 257 if len(missingKernelFeatures) > 0 { 258 summary := fmt.Sprintf("apparmor is enabled but required kernel features are missing: %s", 259 strings.Join(missingKernelFeatures, ", ")) 260 return Unusable, summary 261 } 262 263 // Next check that the parser supports preferred parser features. 264 // If we have any missing preferred features then apparmor is partially enabled. 265 for _, feature := range preferredParserFeatures { 266 if !strutil.SortedListContains(parserFeatures, feature) { 267 missingParserFeatures = append(missingParserFeatures, feature) 268 } 269 } 270 if len(missingParserFeatures) > 0 { 271 summary := fmt.Sprintf("apparmor_parser is available but some features are missing: %s", 272 strings.Join(missingParserFeatures, ", ")) 273 return Partial, summary 274 } 275 276 // Lastly check that the kernel supports preferred kernel features. 277 for _, feature := range preferredKernelFeatures { 278 if !strutil.SortedListContains(kernelFeatures, feature) { 279 missingKernelFeatures = append(missingKernelFeatures, feature) 280 } 281 } 282 if len(missingKernelFeatures) > 0 { 283 summary := fmt.Sprintf("apparmor is enabled but some kernel features are missing: %s", 284 strings.Join(missingKernelFeatures, ", ")) 285 return Partial, summary 286 } 287 288 // If we got here then all features are available and supported. 289 return Full, "apparmor is enabled and all features are available" 290 } 291 292 type appArmorProbe struct { 293 // kernelFeatures contains a list of kernel features that are supported. 294 kernelFeatures []string 295 // kernelError contains an error, if any, encountered when 296 // discovering available kernel features. 297 kernelError error 298 // parserFeatures contains a list of parser features that are supported. 299 parserFeatures []string 300 // parserError contains an error, if any, encountered when 301 // discovering available parser features. 302 parserError error 303 304 probeKernelOnce sync.Once 305 probeParserOnce sync.Once 306 } 307 308 func (aap *appArmorProbe) KernelFeatures() ([]string, error) { 309 aap.probeKernelOnce.Do(func() { 310 aap.kernelFeatures, aap.kernelError = probeKernelFeatures() 311 }) 312 return aap.kernelFeatures, aap.kernelError 313 } 314 315 func (aap *appArmorProbe) ParserFeatures() ([]string, error) { 316 aap.probeParserOnce.Do(func() { 317 aap.parserFeatures, aap.parserError = probeParserFeatures() 318 }) 319 return aap.parserFeatures, aap.parserError 320 } 321 322 func probeKernelFeatures() ([]string, error) { 323 // note that ioutil.ReadDir() is already sorted 324 dentries, err := ioutil.ReadDir(filepath.Join(rootPath, featuresSysPath)) 325 if err != nil { 326 return []string{}, err 327 } 328 features := make([]string, 0, len(dentries)) 329 for _, fi := range dentries { 330 if fi.IsDir() { 331 features = append(features, fi.Name()) 332 } 333 } 334 return features, nil 335 } 336 337 func probeParserFeatures() ([]string, error) { 338 parser, err := findAppArmorParser() 339 if err != nil { 340 return []string{}, err 341 } 342 features := make([]string, 0, 4) 343 if tryAppArmorParserFeature(parser, "change_profile unsafe /**,") { 344 features = append(features, "unsafe") 345 } 346 if tryAppArmorParserFeature(parser, "network qipcrtr dgram,") { 347 features = append(features, "qipcrtr-socket") 348 } 349 if tryAppArmorParserFeature(parser, "capability bpf,") { 350 features = append(features, "cap-bpf") 351 } 352 if tryAppArmorParserFeature(parser, "capability audit_read,") { 353 features = append(features, "cap-audit-read") 354 } 355 if tryAppArmorParserFeature(parser, "mqueue,") { 356 features = append(features, "mqueue") 357 } 358 sort.Strings(features) 359 return features, nil 360 } 361 362 // findAppArmorParser returns the path of the apparmor_parser binary if one is found. 363 func findAppArmorParser() (string, error) { 364 for _, dir := range filepath.SplitList(parserSearchPath) { 365 path := filepath.Join(dir, "apparmor_parser") 366 if _, err := os.Stat(path); err == nil { 367 return path, nil 368 } 369 } 370 return "", os.ErrNotExist 371 } 372 373 // tryAppArmorParserFeature attempts to pre-process a bit of apparmor syntax with a given parser. 374 func tryAppArmorParserFeature(parser, rule string) bool { 375 cmd := exec.Command(parser, "--preprocess") 376 cmd.Stdin = bytes.NewBufferString(fmt.Sprintf("profile snap-test {\n %s\n}", rule)) 377 if err := cmd.Run(); err != nil { 378 return false 379 } 380 return true 381 } 382 383 // UpdateHomedirsTunable sets the AppArmor HOMEDIRS tunable to the list of the 384 // specified directories. This directly affects the value of the AppArmor 385 // @{HOME} variable. See the "/etc/apparmor.d/tunables/home" file for more 386 // information. 387 func UpdateHomedirsTunable(homedirs []string) error { 388 homeTunableDir := filepath.Join(ConfDir, "tunables/home.d") 389 tunableFilePath := filepath.Join(homeTunableDir, "snapd") 390 391 // If the file is not there and `homedirs` is empty, do nothing; this is 392 // not just an optimisation, but a necessity in Ubuntu Core: the 393 // /etc/apparmor.d/ tree is read-only, and attempting to create the file 394 // would generate an error. 395 if len(homedirs) == 0 && !osutil.FileExists(tunableFilePath) { 396 return nil 397 } 398 399 if err := osMkdirAll(homeTunableDir, 0755); err != nil { 400 return fmt.Errorf("cannot create AppArmor tunable directory: %v", err) 401 } 402 403 contents := &bytes.Buffer{} 404 fmt.Fprintln(contents, "# Generated by snapd -- DO NOT EDIT!") 405 if len(homedirs) > 0 { 406 contents.Write([]byte("@{HOMEDIRS}+=")) 407 separator := "" 408 for _, dir := range homedirs { 409 fmt.Fprintf(contents, `%s"%s"`, separator, dir) 410 separator = " " 411 } 412 } 413 return osutilAtomicWrite(tunableFilePath, contents, 0644, 0) 414 } 415 416 // mocking 417 418 type mockAppArmorProbe struct { 419 kernelFeatures []string 420 kernelError error 421 parserFeatures []string 422 parserError error 423 } 424 425 func (m *mockAppArmorProbe) KernelFeatures() ([]string, error) { 426 return m.kernelFeatures, m.kernelError 427 } 428 429 func (m *mockAppArmorProbe) ParserFeatures() ([]string, error) { 430 return m.parserFeatures, m.parserError 431 } 432 433 // MockAppArmorLevel makes the system believe it has certain level of apparmor 434 // support. 435 // 436 // AppArmor kernel and parser features are set to unrealistic values that do 437 // not match the requested level. Use this function to observe behavior that 438 // relies solely on the apparmor level value. 439 func MockLevel(level LevelType) (restore func()) { 440 oldAppArmorAssessment := appArmorAssessment 441 mockProbe := &mockAppArmorProbe{ 442 kernelFeatures: []string{"mocked-kernel-feature"}, 443 parserFeatures: []string{"mocked-parser-feature"}, 444 } 445 appArmorAssessment = &appArmorAssess{ 446 appArmorProber: mockProbe, 447 level: level, 448 summary: fmt.Sprintf("mocked apparmor level: %s", level), 449 } 450 appArmorAssessment.once.Do(func() {}) 451 return func() { 452 appArmorAssessment = oldAppArmorAssessment 453 } 454 } 455 456 // MockAppArmorFeatures makes the system believe it has certain kernel and 457 // parser features. 458 // 459 // AppArmor level and summary are automatically re-assessed as needed 460 // on both the change and the restore process. Use this function to 461 // observe real assessment of arbitrary features. 462 func MockFeatures(kernelFeatures []string, kernelError error, parserFeatures []string, parserError error) (restore func()) { 463 oldAppArmorAssessment := appArmorAssessment 464 mockProbe := &mockAppArmorProbe{ 465 kernelFeatures: kernelFeatures, 466 kernelError: kernelError, 467 parserFeatures: parserFeatures, 468 parserError: parserError, 469 } 470 appArmorAssessment = &appArmorAssess{ 471 appArmorProber: mockProbe, 472 } 473 appArmorAssessment.assess() 474 return func() { 475 appArmorAssessment = oldAppArmorAssessment 476 } 477 478 }