1// Copyright 2021 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package selinux 16 17import ( 18 "github.com/google/blueprint/proptools" 19 20 "fmt" 21 "strconv" 22 23 "android/soong/android" 24) 25 26func init() { 27 ctx := android.InitRegistrationContext 28 ctx.RegisterModuleType("se_neverallow_test", neverallowTestFactory) 29} 30 31type neverallowTestProperties struct { 32 // Default modules for conf 33 Defaults []string 34 35 // Policy files to be tested. 36 Srcs []string `android:"path"` 37} 38 39type neverallowTestModule struct { 40 android.ModuleBase 41 properties neverallowTestProperties 42 testTimestamp android.OutputPath 43} 44 45type nameProperties struct { 46 Name *string 47} 48 49var checkpolicyTag = dependencyTag{name: "checkpolicy"} 50var sepolicyAnalyzeTag = dependencyTag{name: "sepolicy_analyze"} 51 52// se_neverallow_test builds given policy files and checks whether any neverallow violations exist. 53// This module creates two conf files, one with build test and one without build test. Policy with 54// build test will be compiled with checkpolicy, and policy without build test will be tested with 55// sepolicy-analyze's neverallow tool. This module's check can be skipped by setting 56// SELINUX_IGNORE_NEVERALLOWS := true. 57func neverallowTestFactory() android.Module { 58 n := &neverallowTestModule{} 59 n.AddProperties(&n.properties) 60 android.InitAndroidModule(n) 61 android.AddLoadHook(n, func(ctx android.LoadHookContext) { 62 n.loadHook(ctx) 63 }) 64 return n 65} 66 67// Child conf module name for checkpolicy test. 68func (n *neverallowTestModule) checkpolicyConfModuleName() string { 69 return n.Name() + ".checkpolicy.conf" 70} 71 72// Child conf module name for sepolicy-analyze test. 73func (n *neverallowTestModule) sepolicyAnalyzeConfModuleName() string { 74 return n.Name() + ".sepolicy_analyze.conf" 75} 76 77func (n *neverallowTestModule) loadHook(ctx android.LoadHookContext) { 78 checkpolicyConf := n.checkpolicyConfModuleName() 79 ctx.CreateModule(policyConfFactory, &nameProperties{ 80 Name: proptools.StringPtr(checkpolicyConf), 81 }, &policyConfProperties{ 82 Srcs: n.properties.Srcs, 83 Build_variant: proptools.StringPtr("user"), 84 Installable: proptools.BoolPtr(false), 85 }, &struct { 86 Defaults []string 87 }{ 88 Defaults: n.properties.Defaults, 89 }) 90 91 sepolicyAnalyzeConf := n.sepolicyAnalyzeConfModuleName() 92 ctx.CreateModule(policyConfFactory, &nameProperties{ 93 Name: proptools.StringPtr(sepolicyAnalyzeConf), 94 }, &policyConfProperties{ 95 Srcs: n.properties.Srcs, 96 Build_variant: proptools.StringPtr("user"), 97 Exclude_build_test: proptools.BoolPtr(true), 98 Installable: proptools.BoolPtr(false), 99 }, &struct { 100 Defaults []string 101 }{ 102 Defaults: n.properties.Defaults, 103 }) 104} 105 106func (n *neverallowTestModule) DepsMutator(ctx android.BottomUpMutatorContext) { 107 ctx.AddDependency(n, checkpolicyTag, n.checkpolicyConfModuleName()) 108 ctx.AddDependency(n, sepolicyAnalyzeTag, n.sepolicyAnalyzeConfModuleName()) 109} 110 111func (n *neverallowTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 112 n.testTimestamp = pathForModuleOut(ctx, "timestamp") 113 if ctx.Config().SelinuxIgnoreNeverallows() { 114 // just touch 115 android.WriteFileRule(ctx, n.testTimestamp, "") 116 return 117 } 118 119 var checkpolicyConfPaths android.Paths 120 var sepolicyAnalyzeConfPaths android.Paths 121 122 ctx.VisitDirectDeps(func(child android.Module) { 123 depTag := ctx.OtherModuleDependencyTag(child) 124 if depTag != checkpolicyTag && depTag != sepolicyAnalyzeTag { 125 return 126 } 127 128 outputs := android.OutputFilesForModule(ctx, child, "") 129 130 switch ctx.OtherModuleDependencyTag(child) { 131 case checkpolicyTag: 132 checkpolicyConfPaths = outputs 133 case sepolicyAnalyzeTag: 134 sepolicyAnalyzeConfPaths = outputs 135 } 136 }) 137 138 if len(checkpolicyConfPaths) != 1 { 139 panic(fmt.Errorf("Module %q should produce exactly one output", n.checkpolicyConfModuleName())) 140 } 141 142 if len(sepolicyAnalyzeConfPaths) != 1 { 143 panic(fmt.Errorf("Module %q should produce exactly one output", n.sepolicyAnalyzeConfModuleName())) 144 } 145 146 checkpolicyConfPath := checkpolicyConfPaths[0] 147 sepolicyAnalyzeConfPath := sepolicyAnalyzeConfPaths[0] 148 149 rule := android.NewRuleBuilder(pctx, ctx) 150 151 // Step 1. Build a binary policy from the conf file including build test 152 binaryPolicy := pathForModuleOut(ctx, "policy") 153 rule.Command().BuiltTool("checkpolicy"). 154 Flag("-M"). 155 FlagWithArg("-c ", strconv.Itoa(PolicyVers)). 156 FlagWithOutput("-o ", binaryPolicy). 157 Input(checkpolicyConfPath) 158 rule.Build("neverallow_checkpolicy", "Neverallow check: "+ctx.ModuleName()) 159 160 // Step 2. Run sepolicy-analyze with the conf file without the build test and binary policy 161 // file from Step 1 162 rule = android.NewRuleBuilder(pctx, ctx) 163 msg := `sepolicy-analyze failed. This is most likely due to the use\n` + 164 `of an expanded attribute in a neverallow assertion. Please fix\n` + 165 `the policy.` 166 167 rule.Command().BuiltTool("sepolicy-analyze"). 168 Input(binaryPolicy). 169 Text("neverallow"). 170 Flag("-w"). 171 FlagWithInput("-f ", sepolicyAnalyzeConfPath). 172 Text("|| (echo"). 173 Flag("-e"). 174 Text(`"` + msg + `"`). 175 Text("; exit 1)") 176 177 rule.Command().Text("touch").Output(n.testTimestamp) 178 rule.Build("neverallow_sepolicy-analyze", "Neverallow check: "+ctx.ModuleName()) 179} 180 181func (n *neverallowTestModule) AndroidMkEntries() []android.AndroidMkEntries { 182 return []android.AndroidMkEntries{android.AndroidMkEntries{ 183 OutputFile: android.OptionalPathForPath(n.testTimestamp), 184 Class: "FAKE", 185 Include: "$(BUILD_PHONY_PACKAGE)", 186 ExtraEntries: []android.AndroidMkExtraEntriesFunc{ 187 func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { 188 entries.SetPath("LOCAL_ADDITIONAL_DEPENDENCIES", n.testTimestamp) 189 }, 190 }, 191 }} 192} 193