// 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) 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 }