// 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" "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"` // Output file name. Defaults to module name Stem *string Product_variables struct { Address_sanitize struct { Srcs []string `android:"path"` } } // 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 seappProperties struct { // Files containing neverallow rules. Neverallow_files []string `android:"path"` // Precompiled sepolicy binary file which will be fed to checkseapp. Sepolicy *string `android:"path"` } type selinuxContextsModule struct { android.ModuleBase android.DefaultableModuleBase flaggableModuleBase properties selinuxContextsProperties seappProperties seappProperties build func(ctx android.ModuleContext, inputs android.Paths) android.Path deps func(ctx android.BottomUpMutatorContext) outputPath android.Path installPath android.InstallPath } var _ flaggableModule = (*selinuxContextsModule)(nil) var ( reuseContextsDepTag = dependencyTag{name: "reuseContexts"} syspropLibraryDepTag = dependencyTag{name: "sysprop_library"} ) func init() { pctx.HostBinToolVariable("fc_sort", "fc_sort") android.RegisterModuleType("contexts_defaults", contextsDefaultsFactory) 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) android.RegisterModuleType("seapp_contexts", seappFactory) android.RegisterModuleType("vndservice_contexts", vndServiceFactory) android.RegisterModuleType("file_contexts_test", fileContextsTestFactory) android.RegisterModuleType("property_contexts_test", propertyContextsTestFactory) android.RegisterModuleType("hwservice_contexts_test", hwserviceContextsTestFactory) android.RegisterModuleType("service_contexts_test", serviceContextsTestFactory) android.RegisterModuleType("vndservice_contexts_test", vndServiceContextsTestFactory) } 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) { m.flagDeps(ctx) 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) stem() string { return proptools.StringDefault(m.properties.Stem, m.Name()) } 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.stem(), m.outputPath) return } } m.outputPath = m.build(ctx, android.PathsForModuleSrc(ctx, m.properties.Srcs)) ctx.InstallFile(m.installPath, m.stem(), m.outputPath) } func newModule() *selinuxContextsModule { m := &selinuxContextsModule{} m.AddProperties( &m.properties, &m.seappProperties, ) initFlaggableModule(m) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(m) android.AddLoadHook(m, func(ctx android.LoadHookContext) { m.selinuxContextsHook(ctx) }) return m } type contextsDefaults struct { android.ModuleBase android.DefaultsModuleBase } // contexts_defaults provides a set of properties that can be inherited by other contexts modules. // (file_contexts, property_contexts, seapp_contexts, etc.) A module can use the properties from a // contexts_defaults using `defaults: ["<:default_module_name>"]`. Properties of both modules are // erged (when possible) by prepending the default module's values to the depending module's values. func contextsDefaultsFactory() android.Module { m := &contextsDefaults{} m.AddProperties( &selinuxContextsProperties{}, &seappProperties{}, &flaggableModuleProperties{}, ) android.InitDefaultsModule(m) 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 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 { nameSuffix := "" if m.InRecovery() && !m.onlyInRecovery() { nameSuffix = ".recovery" } return android.AndroidMkData{ Class: "ETC", OutputFile: android.OptionalPathForPath(m.outputPath), SubName: nameSuffix, Extra: []android.AndroidMkExtraFunc{ func(w io.Writer, outputFile android.Path) { fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.String()) fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", m.stem()) }, }, } } func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.BaseModuleContext) { if proptools.Bool(m.properties.Recovery_available) && m.ModuleBase.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.ModuleBase.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.ModuleBase.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 { builtContext := pathForModuleOut(ctx, ctx.ModuleName()+"_m4out") rule := android.NewRuleBuilder(pctx, ctx) newlineFile := pathForModuleOut(ctx, "newline") rule.Command().Text("echo").FlagWithOutput("> ", newlineFile) rule.Temporary(newlineFile) var inputsWithNewline android.Paths for _, input := range inputs { inputsWithNewline = append(inputsWithNewline, input, newlineFile) } flags := m.getBuildFlags(ctx) rule.Command(). Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")). Text("--fatal-warnings -s"). FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()). Flags(flagsToM4Macros(flags)). Inputs(inputsWithNewline). FlagWithOutput("> ", builtContext) if proptools.Bool(m.properties.Remove_comment) { rule.Temporary(builtContext) remove_comment_output := pathForModuleOut(ctx, ctx.ModuleName()+"_remove_comment") rule.Command(). Text("sed -e 's/#.*$//' -e '/^$/d'"). Input(builtContext). FlagWithOutput("> ", remove_comment_output) builtContext = remove_comment_output } if proptools.Bool(m.properties.Fc_sort) { rule.Temporary(builtContext) sorted_output := pathForModuleOut(ctx, ctx.ModuleName()+"_sorted") rule.Command(). Tool(ctx.Config().HostToolPath(ctx, "fc_sort")). FlagWithInput("-i ", builtContext). FlagWithOutput("-o ", sorted_output) builtContext = sorted_output } ret := pathForModuleOut(ctx, m.stem()) rule.Temporary(builtContext) rule.Command().Text("cp").Input(builtContext).Output(ret) rule.DeleteTemporaryFiles() rule.Build("selinux_contexts", "building contexts: "+m.Name()) return ret } func (m *selinuxContextsModule) buildFileContexts(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 fileFactory() android.Module { m := newModule() m.build = m.buildFileContexts return m } func (m *selinuxContextsModule) buildServiceContexts(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, input android.Path) android.Path { 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_", } } 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 := pathForModuleOut(ctx, ctx.ModuleName()+"_namespace_checked") rule.Command().Text("cp -f").Input(input).Output(out) rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName()) return out } 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. builtCtxFile := m.buildGeneralContexts(ctx, inputs) shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel() ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q") if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) { builtCtxFile = m.checkVendorPropertyNamespace(ctx, builtCtxFile) } 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 := pathForModuleOut(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 (m *selinuxContextsModule) shouldCheckCoredomain(ctx android.ModuleContext) bool { if !ctx.SocSpecific() && !ctx.DeviceSpecific() { return false } return ctx.DeviceConfig().CheckVendorSeappViolations() } func (m *selinuxContextsModule) buildSeappContexts(ctx android.ModuleContext, inputs android.Paths) android.Path { neverallowFile := pathForModuleOut(ctx, "neverallow") ret := pathForModuleOut(ctx, "checkseapp", m.stem()) // Step 1. Generate a M4 processed neverallow file flags := m.getBuildFlags(ctx) m4NeverallowFile := pathForModuleOut(ctx, "neverallow.m4out") rule := android.NewRuleBuilder(pctx, ctx) rule.Command(). Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")). Flag("--fatal-warnings"). FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()). Flags(flagsToM4Macros(flags)). Inputs(android.PathsForModuleSrc(ctx, m.seappProperties.Neverallow_files)). FlagWithOutput("> ", m4NeverallowFile) rule.Temporary(m4NeverallowFile) rule.Command(). Text("( grep"). Flag("-ihe"). Text("'^neverallow'"). Input(m4NeverallowFile). Text(">"). Output(neverallowFile). Text("|| true )") // to make ninja happy even when result is empty // Step 2. Generate a M4 processed contexts file builtCtx := m.buildGeneralContexts(ctx, inputs) // Step 3. checkseapp rule.Temporary(neverallowFile) checkCmd := rule.Command().BuiltTool("checkseapp"). FlagWithInput("-p ", android.PathForModuleSrc(ctx, proptools.String(m.seappProperties.Sepolicy))). FlagWithOutput("-o ", ret). Input(builtCtx). Input(neverallowFile) if m.shouldCheckCoredomain(ctx) { checkCmd.Flag("-c") // check coredomain for vendor contexts } rule.Build("seapp_contexts", "Building seapp_contexts: "+m.Name()) return ret } func hwServiceFactory() android.Module { m := newModule() m.build = m.buildServiceContexts 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.buildServiceContexts return m } func keystoreKeyFactory() android.Module { m := newModule() m.build = m.buildGeneralContexts return m } func seappFactory() android.Module { m := newModule() m.build = m.buildSeappContexts return m } func vndServiceFactory() android.Module { m := newModule() m.build = m.buildGeneralContexts android.AddLoadHook(m, func(ctx android.LoadHookContext) { if !ctx.SocSpecific() { ctx.ModuleErrorf(m.Name(), "must set vendor: true") return } }) return m } var _ android.OutputFileProducer = (*selinuxContextsModule)(nil) // Implements android.OutputFileProducer func (m *selinuxContextsModule) OutputFiles(tag string) (android.Paths, error) { if tag == "" { return []android.Path{m.outputPath}, nil } return nil, fmt.Errorf("unsupported module reference tag %q", tag) } type contextsTestProperties struct { // Contexts files to be tested. Srcs []string `android:"path"` // Precompiled sepolicy binary to be tesed together. Sepolicy *string `android:"path"` } type fileContextsTestProperties struct { // Test data. File passed to `checkfc -t` to validate how contexts are resolved. Test_data *string `android:"path"` } type contextsTestModule struct { android.ModuleBase // The type of context. context contextType properties contextsTestProperties fileProperties fileContextsTestProperties testTimestamp android.OutputPath } type contextType int const ( FileContext contextType = iota PropertyContext ServiceContext HwServiceContext VndServiceContext ) // checkfc parses a context file and checks for syntax errors. // If -s is specified, the service backend is used to verify binder services. // If -l is specified, the service backend is used to verify hwbinder services. // Otherwise, context_file is assumed to be a file_contexts file // If -e is specified, then the context_file is allowed to be empty. // file_contexts_test tests given file_contexts files with checkfc. func fileContextsTestFactory() android.Module { m := &contextsTestModule{context: FileContext} m.AddProperties(&m.properties) m.AddProperties(&m.fileProperties) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) return m } // property_contexts_test tests given property_contexts files with property_info_checker. func propertyContextsTestFactory() android.Module { m := &contextsTestModule{context: PropertyContext} m.AddProperties(&m.properties) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) return m } // hwservice_contexts_test tests given hwservice_contexts files with checkfc. func hwserviceContextsTestFactory() android.Module { m := &contextsTestModule{context: HwServiceContext} m.AddProperties(&m.properties) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) return m } // service_contexts_test tests given service_contexts files with checkfc. func serviceContextsTestFactory() android.Module { // checkfc -s: service_contexts test m := &contextsTestModule{context: ServiceContext} m.AddProperties(&m.properties) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) return m } // vndservice_contexts_test tests given vndservice_contexts files with checkfc. func vndServiceContextsTestFactory() android.Module { m := &contextsTestModule{context: VndServiceContext} m.AddProperties(&m.properties) android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) return m } func (m *contextsTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { tool := "checkfc" if m.context == PropertyContext { tool = "property_info_checker" } if len(m.properties.Srcs) == 0 { ctx.PropertyErrorf("srcs", "can't be empty") return } validateWithPolicy := true if proptools.String(m.properties.Sepolicy) == "" { if m.context == FileContext { if proptools.String(m.fileProperties.Test_data) == "" { ctx.PropertyErrorf("test_data", "Either test_data or sepolicy should be provided") return } validateWithPolicy = false } else { ctx.PropertyErrorf("sepolicy", "can't be empty") return } } flags := []string(nil) switch m.context { case FileContext: if !validateWithPolicy { flags = []string{"-t"} } case ServiceContext: flags = []string{"-s" /* binder services */} case HwServiceContext: flags = []string{"-e" /* allow empty */, "-l" /* hwbinder services */} case VndServiceContext: flags = []string{"-e" /* allow empty */, "-v" /* vnd service */} } srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs) rule := android.NewRuleBuilder(pctx, ctx) if validateWithPolicy { sepolicy := android.PathForModuleSrc(ctx, proptools.String(m.properties.Sepolicy)) rule.Command().BuiltTool(tool). Flags(flags). Input(sepolicy). Inputs(srcs) } else { test_data := android.PathForModuleSrc(ctx, proptools.String(m.fileProperties.Test_data)) rule.Command().BuiltTool(tool). Flags(flags). Inputs(srcs). Input(test_data) } m.testTimestamp = pathForModuleOut(ctx, "timestamp") rule.Command().Text("touch").Output(m.testTimestamp) rule.Build("contexts_test", "running contexts test: "+ctx.ModuleName()) } func (m *contextsTestModule) AndroidMkEntries() []android.AndroidMkEntries { return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "FAKE", // OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it. // Without OutputFile this module won't be exported to Makefile. OutputFile: android.OptionalPathForPath(m.testTimestamp), Include: "$(BUILD_PHONY_PACKAGE)", ExtraEntries: []android.AndroidMkExtraEntriesFunc{ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", m.testTimestamp.String()) }, }, }} } // contextsTestModule implements ImageInterface to be able to include recovery_available contexts // modules as its sources. func (m *contextsTestModule) ImageMutatorBegin(ctx android.BaseModuleContext) { } func (m *contextsTestModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool { return true } func (m *contextsTestModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool { return false } func (m *contextsTestModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { return false } func (m *contextsTestModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { return false } func (m *contextsTestModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool { return false } func (m *contextsTestModule) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil } func (m *contextsTestModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) { } var _ android.ImageInterface = (*contextsTestModule)(nil)