From 65b3fd900a0a71922eb8855cb6e20200acefd2ff Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Wed, 6 Dec 2017 14:18:35 -0800 Subject: [PATCH] neverallows in Soong Straightforward way of expressing policy inspired by a similar syntax in SELinux. Bug: 70165717 Test: no neverallows hit Test: manually checking neverallow rules by changing them/adding violations Change-Id: I7e15a0094d1861391bfe21a2ea30797d7593c142 --- Android.bp | 1 + android/mutator.go | 1 + android/neverallow.go | 273 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 android/neverallow.go diff --git a/Android.bp b/Android.bp index ebf103800..ba7178955 100644 --- a/Android.bp +++ b/Android.bp @@ -51,6 +51,7 @@ bootstrap_go_package { "android/module.go", "android/mutator.go", "android/namespace.go", + "android/neverallow.go", "android/onceper.go", "android/package_ctx.go", "android/paths.go", diff --git a/android/mutator.go b/android/mutator.go index 876d16176..8de57e1b6 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -92,6 +92,7 @@ var preDeps = []RegisterMutatorFunc{ var postDeps = []RegisterMutatorFunc{ RegisterPrebuiltsPostDepsMutators, + registerNeverallowMutator, } func PreArchMutators(f RegisterMutatorFunc) { diff --git a/android/neverallow.go b/android/neverallow.go new file mode 100644 index 000000000..261f2eeed --- /dev/null +++ b/android/neverallow.go @@ -0,0 +1,273 @@ +// Copyright 2017 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package android + +import ( + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/google/blueprint/proptools" +) + +// "neverallow" rules for the build system. +// +// This allows things which aren't related to the build system and are enforced +// for sanity, in progress code refactors, or policy to be expressed in a +// straightforward away disjoint from implementations and tests which should +// work regardless of these restrictions. +// +// A module is disallowed if all of the following are true: +// - it is in one of the "in" paths +// - it is not in one of the "notIn" paths +// - it has all "with" properties matched +// - - values are matched in their entirety +// - - nil is interpreted as an empty string +// - - nested properties are separated with a '.' +// - - if the property is a list, any of the values in the list being matches +// counts as a match +// - it has none of the "without" properties matched (same rules as above) + +func registerNeverallowMutator(ctx RegisterMutatorsContext) { + ctx.BottomUp("neverallow", neverallowMutator).Parallel() +} + +var neverallows = []*rule{ + neverallow().in("vendor", "device").with("vndk.enabled", "true"). + because("the VNDK can never contain a library that is device dependent."), + neverallow().with("vndk.enabled", "true").without("owner", ""). + because("a VNDK module can never have an owner."), + neverallow().notIn("libcore").with("no_standard_libs", "true"), + + // TODO(b/67974785): always enforce the manifest + neverallow(). + without("name", "libhidltransport"). + with("product_variables.enforce_vintf_manifest.cflags", "*"). + because("manifest enforcement should be independent of ."), + + // TODO(b/67975799): vendor code should always use /vendor/bin/sh + neverallow(). + without("name", "libc_bionic_ndk"). + with("product_variables.treble_linker_namespaces.cflags", "*"). + because("nothing should care if linker namespaces are enabled or not"), + + // Example: + // *neverallow().with("Srcs", "main.cpp"), +} + +func neverallowMutator(ctx BottomUpMutatorContext) { + m, ok := ctx.Module().(Module) + if !ok { + return + } + + dir := ctx.ModuleDir() + "/" + properties := m.GetProperties() + + for _, n := range neverallows { + if !n.appliesToPath(dir) { + continue + } + + if !n.appliesToProperties(properties) { + continue + } + + ctx.ModuleErrorf("violates " + n.String()) + } +} + +type ruleProperty struct { + fields []string // e.x.: Vndk.Enabled + value string // e.x.: true +} + +type rule struct { + // User string for why this is a thing. + reason string + + paths []string + unlessPaths []string + + props []ruleProperty + unlessProps []ruleProperty +} + +func neverallow() *rule { + return &rule{} +} +func (r *rule) in(path ...string) *rule { + r.paths = append(r.paths, cleanPaths(path)...) + return r +} +func (r *rule) notIn(path ...string) *rule { + r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...) + return r +} +func (r *rule) with(properties, value string) *rule { + r.props = append(r.props, ruleProperty{ + fields: fieldNamesForProperties(properties), + value: value, + }) + return r +} +func (r *rule) without(properties, value string) *rule { + r.unlessProps = append(r.unlessProps, ruleProperty{ + fields: fieldNamesForProperties(properties), + value: value, + }) + return r +} +func (r *rule) because(reason string) *rule { + r.reason = reason + return r +} + +func (r *rule) String() string { + s := "neverallow" + for _, v := range r.paths { + s += " dir:" + v + "*" + } + for _, v := range r.unlessPaths { + s += " -dir:" + v + "*" + } + for _, v := range r.props { + s += " " + strings.Join(v.fields, ".") + "=" + v.value + } + for _, v := range r.unlessProps { + s += " -" + strings.Join(v.fields, ".") + "=" + v.value + } + if len(r.reason) != 0 { + s += " which is restricted because " + r.reason + } + return s +} + +func (r *rule) appliesToPath(dir string) bool { + includePath := len(r.paths) == 0 || hasAnyPrefix(dir, r.paths) + excludePath := hasAnyPrefix(dir, r.unlessPaths) + return includePath && !excludePath +} + +func (r *rule) appliesToProperties(properties []interface{}) bool { + includeProps := hasAllProperties(properties, r.props) + excludeProps := hasAnyProperty(properties, r.unlessProps) + return includeProps && !excludeProps +} + +// assorted utils + +func cleanPaths(paths []string) []string { + res := make([]string, len(paths)) + for i, v := range paths { + res[i] = filepath.Clean(v) + "/" + } + return res +} + +func fieldNamesForProperties(propertyNames string) []string { + names := strings.Split(propertyNames, ".") + for i, v := range names { + names[i] = proptools.FieldNameForProperty(v) + } + return names +} + +func hasAnyPrefix(s string, prefixes []string) bool { + for _, prefix := range prefixes { + if strings.HasPrefix(s, prefix) { + return true + } + } + return false +} + +func hasAnyProperty(properties []interface{}, props []ruleProperty) bool { + for _, v := range props { + if hasProperty(properties, v) { + return true + } + } + return false +} + +func hasAllProperties(properties []interface{}, props []ruleProperty) bool { + for _, v := range props { + if !hasProperty(properties, v) { + return false + } + } + return true +} + +func hasProperty(properties []interface{}, prop ruleProperty) bool { + for _, propertyStruct := range properties { + propertiesValue := reflect.ValueOf(propertyStruct).Elem() + for _, v := range prop.fields { + if !propertiesValue.IsValid() { + break + } + propertiesValue = propertiesValue.FieldByName(v) + } + if !propertiesValue.IsValid() { + continue + } + + check := func(v string) bool { + return prop.value == "*" || prop.value == v + } + + if matchValue(propertiesValue, check) { + return true + } + } + return false +} + +func matchValue(value reflect.Value, check func(string) bool) bool { + if !value.IsValid() { + return false + } + + if value.Kind() == reflect.Ptr { + if value.IsNil() { + return check("") + } + value = value.Elem() + } + + switch value.Kind() { + case reflect.String: + return check(value.String()) + case reflect.Bool: + return check(strconv.FormatBool(value.Bool())) + case reflect.Int: + return check(strconv.FormatInt(value.Int(), 10)) + case reflect.Slice: + slice, ok := value.Interface().([]string) + if !ok { + panic("Can only handle slice of string") + } + for _, v := range slice { + if check(v) { + return true + } + } + return false + } + + panic("Can't handle type: " + value.Kind().String()) +}