blob: a9aed6024444292fe6c827e3d113275ab7298eda [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// 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 selinux
import (
"fmt"
"io"
"strings"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/sysprop"
)
type selinuxContextsProperties struct {
// Filenames under sepolicy directories, which will be used to generate contexts file.
Srcs []string `android:"path"`
Product_variables struct {
Debuggable struct {
Srcs []string
}
Address_sanitize struct {
Srcs []string
}
}
// Whether reqd_mask directory is included to sepolicy directories or not.
Reqd_mask *bool
// Whether the comments in generated contexts file will be removed or not.
Remove_comment *bool
// Whether the result context file is sorted with fc_sort or not.
Fc_sort *bool
// Make this module available when building for recovery
Recovery_available *bool
}
type fileContextsProperties struct {
// flatten_apex can be used to specify additional sources of file_contexts.
// Apex paths, /system/apex/{apex_name}, will be amended to the paths of file_contexts
// entries.
Flatten_apex struct {
Srcs []string
}
}
type selinuxContextsModule struct {
android.ModuleBase
properties selinuxContextsProperties
fileContextsProperties fileContextsProperties
build func(ctx android.ModuleContext, inputs android.Paths) android.Path
deps func(ctx android.BottomUpMutatorContext)
outputPath android.Path
installPath android.InstallPath
}
var (
reuseContextsDepTag = dependencyTag{name: "reuseContexts"}
syspropLibraryDepTag = dependencyTag{name: "sysprop_library"}
)
func init() {
pctx.HostBinToolVariable("fc_sort", "fc_sort")
android.RegisterModuleType("file_contexts", fileFactory)
android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
android.RegisterModuleType("property_contexts", propertyFactory)
android.RegisterModuleType("service_contexts", serviceFactory)
android.RegisterModuleType("keystore2_key_contexts", keystoreKeyFactory)
}
func (m *selinuxContextsModule) InstallInRoot() bool {
return m.InRecovery()
}
func (m *selinuxContextsModule) InstallInRecovery() bool {
// ModuleBase.InRecovery() checks the image variant
return m.InRecovery()
}
func (m *selinuxContextsModule) onlyInRecovery() bool {
// ModuleBase.InstallInRecovery() checks commonProperties.Recovery property
return m.ModuleBase.InstallInRecovery()
}
func (m *selinuxContextsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
if m.deps != nil {
m.deps(ctx)
}
if m.InRecovery() && !m.onlyInRecovery() {
ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "image", Variation: android.CoreVariation},
}, reuseContextsDepTag, ctx.ModuleName())
}
}
func (m *selinuxContextsModule) propertyContextsDeps(ctx android.BottomUpMutatorContext) {
for _, lib := range sysprop.SyspropLibraries(ctx.Config()) {
ctx.AddFarVariationDependencies([]blueprint.Variation{}, syspropLibraryDepTag, lib)
}
}
func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if m.InRecovery() {
// Installing context files at the root of the recovery partition
m.installPath = android.PathForModuleInstall(ctx)
} else {
m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
}
if m.InRecovery() && !m.onlyInRecovery() {
dep := ctx.GetDirectDepWithTag(m.Name(), reuseContextsDepTag)
if reuseDeps, ok := dep.(*selinuxContextsModule); ok {
m.outputPath = reuseDeps.outputPath
ctx.InstallFile(m.installPath, m.Name(), m.outputPath)
return
}
}
var inputs android.Paths
ctx.VisitDirectDepsWithTag(android.SourceDepTag, func(dep android.Module) {
segroup, ok := dep.(*fileGroup)
if !ok {
ctx.ModuleErrorf("srcs dependency %q is not an selinux filegroup",
ctx.OtherModuleName(dep))
return
}
if ctx.ProductSpecific() {
inputs = append(inputs, segroup.ProductPrivateSrcs()...)
} else if ctx.SocSpecific() {
if ctx.DeviceConfig().BoardSepolicyVers() == ctx.DeviceConfig().PlatformSepolicyVersion() {
inputs = append(inputs, segroup.SystemVendorSrcs()...)
}
inputs = append(inputs, segroup.VendorSrcs()...)
} else if ctx.DeviceSpecific() {
inputs = append(inputs, segroup.OdmSrcs()...)
} else if ctx.SystemExtSpecific() {
inputs = append(inputs, segroup.SystemExtPrivateSrcs()...)
} else {
inputs = append(inputs, segroup.SystemPrivateSrcs()...)
inputs = append(inputs, segroup.SystemPublicSrcs()...)
}
if proptools.Bool(m.properties.Reqd_mask) {
if ctx.SocSpecific() || ctx.DeviceSpecific() {
inputs = append(inputs, segroup.VendorReqdMaskSrcs()...)
} else {
inputs = append(inputs, segroup.SystemReqdMaskSrcs()...)
}
}
})
for _, src := range m.properties.Srcs {
// Module sources are handled above with VisitDirectDepsWithTag
if android.SrcIsModule(src) == "" {
inputs = append(inputs, android.PathForModuleSrc(ctx, src))
}
}
m.outputPath = m.build(ctx, inputs)
ctx.InstallFile(m.installPath, ctx.ModuleName(), m.outputPath)
}
func newModule() *selinuxContextsModule {
m := &selinuxContextsModule{}
m.AddProperties(
&m.properties,
)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
android.AddLoadHook(m, func(ctx android.LoadHookContext) {
m.selinuxContextsHook(ctx)
})
return m
}
func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
// TODO: clean this up to use build/soong/android/variable.go after b/79249983
var srcs []string
if ctx.Config().Debuggable() {
srcs = append(srcs, m.properties.Product_variables.Debuggable.Srcs...)
}
for _, sanitize := range ctx.Config().SanitizeDevice() {
if sanitize == "address" {
srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
break
}
}
m.properties.Srcs = append(m.properties.Srcs, srcs...)
}
func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
return android.AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
nameSuffix := ""
if m.InRecovery() && !m.onlyInRecovery() {
nameSuffix = ".recovery"
}
fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
fmt.Fprintln(w, "LOCAL_MODULE :=", name+nameSuffix)
data.Entries.WriteLicenseVariables(w)
fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
if m.Owner() != "" {
fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", m.Owner())
}
fmt.Fprintln(w, "LOCAL_MODULE_TAGS := optional")
fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", m.outputPath.String())
fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.ToMakePath().String())
fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", name)
fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
},
}
}
func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
if proptools.Bool(m.properties.Recovery_available) && m.InstallInRecovery() {
ctx.PropertyErrorf("recovery_available",
"doesn't make sense at the same time as `recovery: true`")
}
}
func (m *selinuxContextsModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
return !m.InstallInRecovery()
}
func (m *selinuxContextsModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
return m.InstallInRecovery() || proptools.Bool(m.properties.Recovery_available)
}
func (m *selinuxContextsModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
return nil
}
func (m *selinuxContextsModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
}
var _ android.ImageInterface = (*selinuxContextsModule)(nil)
func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
ret := android.PathForModuleGen(ctx, ctx.ModuleName()+"_m4out")
rule := android.NewRuleBuilder(pctx, ctx)
rule.Command().
Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Text("--fatal-warnings -s").
FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
Inputs(inputs).
FlagWithOutput("> ", ret)
if proptools.Bool(m.properties.Remove_comment) {
rule.Temporary(ret)
remove_comment_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_remove_comment")
rule.Command().
Text("sed -e 's/#.*$//' -e '/^$/d'").
Input(ret).
FlagWithOutput("> ", remove_comment_output)
ret = remove_comment_output
}
if proptools.Bool(m.properties.Fc_sort) {
rule.Temporary(ret)
sorted_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_sorted")
rule.Command().
Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
FlagWithInput("-i ", ret).
FlagWithOutput("-o ", sorted_output)
ret = sorted_output
}
rule.Build("selinux_contexts", "building contexts: "+m.Name())
rule.DeleteTemporaryFiles()
return ret
}
func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
if m.properties.Fc_sort == nil {
m.properties.Fc_sort = proptools.BoolPtr(true)
}
rule := android.NewRuleBuilder(pctx, ctx)
if ctx.Config().FlattenApex() {
for _, src := range m.fileContextsProperties.Flatten_apex.Srcs {
if m := android.SrcIsModule(src); m != "" {
ctx.ModuleErrorf(
"Module srcs dependency %q is not supported for flatten_apex.srcs", m)
return nil
}
for _, path := range android.PathsForModuleSrcExcludes(ctx, []string{src}, nil) {
out := android.PathForModuleGen(ctx, "flattened_apex", path.Rel())
apex_path := "/system/apex/" + strings.Replace(
strings.TrimSuffix(path.Base(), "-file_contexts"),
".", "\\\\.", -1)
rule.Command().
Text("awk '/object_r/{printf(\""+apex_path+"%s\\n\",$0)}'").
Input(path).
FlagWithOutput("> ", out)
inputs = append(inputs, out)
}
}
}
rule.Build(m.Name(), "flattened_apex_file_contexts")
return m.buildGeneralContexts(ctx, inputs)
}
func fileFactory() android.Module {
m := newModule()
m.AddProperties(&m.fileContextsProperties)
m.build = m.buildFileContexts
return m
}
func (m *selinuxContextsModule) buildHwServiceContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
if m.properties.Remove_comment == nil {
m.properties.Remove_comment = proptools.BoolPtr(true)
}
return m.buildGeneralContexts(ctx, inputs)
}
func (m *selinuxContextsModule) checkVendorPropertyNamespace(ctx android.ModuleContext, inputs android.Paths) android.Paths {
shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
ApiLevelR := android.ApiLevelOrPanic(ctx, "R")
rule := android.NewRuleBuilder(pctx, ctx)
// This list is from vts_treble_sys_prop_test.
allowedPropertyPrefixes := []string{
"ctl.odm.",
"ctl.vendor.",
"ctl.start$odm.",
"ctl.start$vendor.",
"ctl.stop$odm.",
"ctl.stop$vendor.",
"init.svc.odm.",
"init.svc.vendor.",
"ro.boot.",
"ro.hardware.",
"ro.odm.",
"ro.vendor.",
"odm.",
"persist.odm.",
"persist.vendor.",
"vendor.",
}
// persist.camera is also allowed for devices launching with R or eariler
if shippingApiLevel.LessThanOrEqualTo(ApiLevelR) {
allowedPropertyPrefixes = append(allowedPropertyPrefixes, "persist.camera.")
}
var allowedContextPrefixes []string
if shippingApiLevel.GreaterThanOrEqualTo(ApiLevelR) {
// This list is from vts_treble_sys_prop_test.
allowedContextPrefixes = []string{
"vendor_",
"odm_",
}
}
var ret android.Paths
for _, input := range inputs {
cmd := rule.Command().
BuiltTool("check_prop_prefix").
FlagWithInput("--property-contexts ", input).
FlagForEachArg("--allowed-property-prefix ", proptools.ShellEscapeList(allowedPropertyPrefixes)). // contains shell special character '$'
FlagForEachArg("--allowed-context-prefix ", allowedContextPrefixes)
if !ctx.DeviceConfig().BuildBrokenVendorPropertyNamespace() {
cmd.Flag("--strict")
}
out := android.PathForModuleGen(ctx, "namespace_checked").Join(ctx, input.String())
rule.Command().Text("cp -f").Input(input).Output(out)
ret = append(ret, out)
}
rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName())
return ret
}
func (m *selinuxContextsModule) buildPropertyContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
// vendor/odm properties are enforced for devices launching with Android Q or later. So, if
// vendor/odm, make sure that only vendor/odm properties exist.
shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q")
if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) {
inputs = m.checkVendorPropertyNamespace(ctx, inputs)
}
builtCtxFile := m.buildGeneralContexts(ctx, inputs)
var apiFiles android.Paths
ctx.VisitDirectDepsWithTag(syspropLibraryDepTag, func(c android.Module) {
i, ok := c.(interface{ CurrentSyspropApiFile() android.OptionalPath })
if !ok {
panic(fmt.Errorf("unknown dependency %q for %q", ctx.OtherModuleName(c), ctx.ModuleName()))
}
if api := i.CurrentSyspropApiFile(); api.Valid() {
apiFiles = append(apiFiles, api.Path())
}
})
// check compatibility with sysprop_library
if len(apiFiles) > 0 {
out := android.PathForModuleGen(ctx, ctx.ModuleName()+"_api_checked")
rule := android.NewRuleBuilder(pctx, ctx)
msg := `\n******************************\n` +
`API of sysprop_library doesn't match with property_contexts\n` +
`Please fix the breakage and rebuild.\n` +
`******************************\n`
rule.Command().
Text("( ").
BuiltTool("sysprop_type_checker").
FlagForEachInput("--api ", apiFiles).
FlagWithInput("--context ", builtCtxFile).
Text(" || ( echo").Flag("-e").
Flag(`"` + msg + `"`).
Text("; exit 38) )")
rule.Command().Text("cp -f").Input(builtCtxFile).Output(out)
rule.Build("property_contexts_check_api", "checking API: "+m.Name())
builtCtxFile = out
}
return builtCtxFile
}
func hwServiceFactory() android.Module {
m := newModule()
m.build = m.buildHwServiceContexts
return m
}
func propertyFactory() android.Module {
m := newModule()
m.build = m.buildPropertyContexts
m.deps = m.propertyContextsDeps
return m
}
func serviceFactory() android.Module {
m := newModule()
m.build = m.buildGeneralContexts
return m
}
func keystoreKeyFactory() android.Module {
m := newModule()
m.build = m.buildGeneralContexts
return m
}