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