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	"fmt"
19
20	"github.com/google/blueprint/proptools"
21
22	"android/soong/android"
23)
24
25var (
26	compatTestDepTag = dependencyTag{name: "compat_test"}
27)
28
29func init() {
30	ctx := android.InitRegistrationContext
31	ctx.RegisterModuleType("se_compat_cil", compatCilFactory)
32	ctx.RegisterParallelSingletonModuleType("se_compat_test", compatTestFactory)
33}
34
35// se_compat_cil collects and installs backwards compatibility cil files.
36func compatCilFactory() android.Module {
37	c := &compatCil{}
38	c.AddProperties(&c.properties)
39	android.InitAndroidArchModule(c, android.DeviceSupported, android.MultilibCommon)
40	return c
41}
42
43type compatCil struct {
44	android.ModuleBase
45	properties    compatCilProperties
46	installSource android.OptionalPath
47	installPath   android.InstallPath
48}
49
50type compatCilProperties struct {
51	// List of source files. Can reference se_build_files type modules with the ":module" syntax.
52	Srcs []string `android:"path"`
53
54	// Output file name. Defaults to module name if unspecified.
55	Stem *string
56
57	// Target version that this module supports. This module will be ignored if platform sepolicy
58	// version is same as this module's version.
59	Version *string
60}
61
62func (c *compatCil) stem() string {
63	return proptools.StringDefault(c.properties.Stem, c.Name())
64}
65
66func (c *compatCil) expandSeSources(ctx android.ModuleContext) android.Paths {
67	return android.PathsForModuleSrc(ctx, c.properties.Srcs)
68}
69
70func (c *compatCil) shouldSkipBuild(ctx android.ModuleContext) bool {
71	return proptools.String(c.properties.Version) == ctx.DeviceConfig().PlatformSepolicyVersion()
72}
73
74func (c *compatCil) GenerateAndroidBuildActions(ctx android.ModuleContext) {
75	if c.ProductSpecific() || c.SocSpecific() || c.DeviceSpecific() {
76		ctx.ModuleErrorf("Compat cil files only support system and system_ext partitions")
77	}
78
79	if c.shouldSkipBuild(ctx) {
80		return
81	}
82
83	srcPaths := c.expandSeSources(ctx)
84	out := android.PathForModuleGen(ctx, c.Name())
85	ctx.Build(pctx, android.BuildParams{
86		Rule:        android.Cat,
87		Inputs:      srcPaths,
88		Output:      out,
89		Description: "Combining compat cil for " + c.Name(),
90	})
91
92	c.installPath = android.PathForModuleInstall(ctx, "etc", "selinux", "mapping")
93	c.installSource = android.OptionalPathForPath(out)
94	ctx.InstallFile(c.installPath, c.stem(), out)
95
96	if c.installSource.Valid() {
97		ctx.SetOutputFiles(android.Paths{c.installSource.Path()}, "")
98	}
99}
100
101func (c *compatCil) AndroidMkEntries() []android.AndroidMkEntries {
102	if !c.installSource.Valid() {
103		return nil
104	}
105	return []android.AndroidMkEntries{android.AndroidMkEntries{
106		Class:      "ETC",
107		OutputFile: c.installSource,
108		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
109			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
110				entries.SetPath("LOCAL_MODULE_PATH", c.installPath)
111				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.stem())
112			},
113		},
114	}}
115}
116
117// se_compat_test checks if compat files ({ver}.cil, {ver}.compat.cil) files are compatible with
118// current policy.
119func compatTestFactory() android.SingletonModule {
120	f := &compatTestModule{}
121	f.AddProperties(&f.properties)
122	android.InitAndroidModule(f)
123	android.AddLoadHook(f, func(ctx android.LoadHookContext) {
124		f.loadHook(ctx)
125	})
126	return f
127}
128
129type compatTestModule struct {
130	android.SingletonModuleBase
131	properties struct {
132		// Default modules for conf
133		Defaults []string
134	}
135
136	compatTestTimestamp android.ModuleOutPath
137}
138
139func (f *compatTestModule) createPlatPubVersionedModule(ctx android.LoadHookContext, ver string) {
140	confName := fmt.Sprintf("pub_policy_%s.conf", ver)
141	cilName := fmt.Sprintf("pub_policy_%s.cil", ver)
142	platPubVersionedName := fmt.Sprintf("plat_pub_versioned_%s.cil", ver)
143
144	ctx.CreateModule(policyConfFactory, &nameProperties{
145		Name: proptools.StringPtr(confName),
146	}, &policyConfProperties{
147		Srcs: []string{
148			fmt.Sprintf(":se_build_files{.plat_public_%s}", ver),
149			fmt.Sprintf(":se_build_files{.system_ext_public_%s}", ver),
150			fmt.Sprintf(":se_build_files{.product_public_%s}", ver),
151			":se_build_files{.reqd_mask}",
152		},
153		Installable: proptools.BoolPtr(false),
154	}, &struct {
155		Defaults []string
156	}{
157		Defaults: f.properties.Defaults,
158	})
159
160	ctx.CreateModule(policyCilFactory, &nameProperties{
161		Name: proptools.StringPtr(cilName),
162	}, &policyCilProperties{
163		Src:          proptools.StringPtr(":" + confName),
164		Filter_out:   []string{":reqd_policy_mask.cil"},
165		Secilc_check: proptools.BoolPtr(false),
166		Installable:  proptools.BoolPtr(false),
167	})
168
169	ctx.CreateModule(versionedPolicyFactory, &nameProperties{
170		Name: proptools.StringPtr(platPubVersionedName),
171	}, &versionedPolicyProperties{
172		Base:          proptools.StringPtr(":" + cilName),
173		Target_policy: proptools.StringPtr(":" + cilName),
174		Version:       proptools.StringPtr(ver),
175		Installable:   proptools.BoolPtr(false),
176	})
177}
178
179func (f *compatTestModule) createCompatTestModule(ctx android.LoadHookContext, ver string) {
180	srcs := []string{
181		":plat_sepolicy.cil",
182		":system_ext_sepolicy.cil",
183		":product_sepolicy.cil",
184		fmt.Sprintf(":plat_%s.cil", ver),
185		fmt.Sprintf(":%s.compat.cil", ver),
186		fmt.Sprintf(":system_ext_%s.cil", ver),
187		fmt.Sprintf(":system_ext_%s.compat.cil", ver),
188		fmt.Sprintf(":product_%s.cil", ver),
189	}
190
191	if ver == ctx.DeviceConfig().BoardSepolicyVers() {
192		srcs = append(srcs,
193			":plat_pub_versioned.cil",
194			":vendor_sepolicy.cil",
195			":odm_sepolicy.cil",
196		)
197	} else {
198		srcs = append(srcs, fmt.Sprintf(":plat_pub_versioned_%s.cil", ver))
199	}
200
201	compatTestName := fmt.Sprintf("%s_compat_test", ver)
202	ctx.CreateModule(policyBinaryFactory, &nameProperties{
203		Name: proptools.StringPtr(compatTestName),
204	}, &policyBinaryProperties{
205		Srcs:              srcs,
206		Ignore_neverallow: proptools.BoolPtr(true),
207		Installable:       proptools.BoolPtr(false),
208	})
209}
210
211func (f *compatTestModule) loadHook(ctx android.LoadHookContext) {
212	for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
213		f.createPlatPubVersionedModule(ctx, ver)
214		f.createCompatTestModule(ctx, ver)
215	}
216}
217
218func (f *compatTestModule) DepsMutator(ctx android.BottomUpMutatorContext) {
219	for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
220		ctx.AddDependency(f, compatTestDepTag, fmt.Sprintf("%s_compat_test", ver))
221	}
222}
223
224func (f *compatTestModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
225	// does nothing; se_compat_test is a singeton because two compat test modules don't make sense.
226}
227
228func (f *compatTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
229	var inputs android.Paths
230	ctx.VisitDirectDepsWithTag(compatTestDepTag, func(child android.Module) {
231		outputs := android.OutputFilesForModule(ctx, child, "")
232		if len(outputs) != 1 {
233			panic(fmt.Errorf("Module %q should produce exactly one output, but did %q", ctx.OtherModuleName(child), outputs.Strings()))
234		}
235
236		inputs = append(inputs, outputs[0])
237	})
238
239	f.compatTestTimestamp = android.PathForModuleOut(ctx, "timestamp")
240	rule := android.NewRuleBuilder(pctx, ctx)
241	rule.Command().Text("touch").Output(f.compatTestTimestamp).Implicits(inputs)
242	rule.Build("compat", "compat test timestamp for: "+f.Name())
243}
244
245func (f *compatTestModule) AndroidMkEntries() []android.AndroidMkEntries {
246	return []android.AndroidMkEntries{android.AndroidMkEntries{
247		Class: "FAKE",
248		// OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it.
249		// Without OutputFile this module won't be exported to Makefile.
250		OutputFile: android.OptionalPathForPath(f.compatTestTimestamp),
251		Include:    "$(BUILD_PHONY_PACKAGE)",
252		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
253			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
254				entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", f.compatTestTimestamp.String())
255			},
256		},
257	}}
258}
259