1// Copyright (C) 2019 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	"io"
20	"strings"
21
22	"github.com/google/blueprint/proptools"
23
24	"android/soong/android"
25)
26
27const (
28	coreMode     = "core"
29	recoveryMode = "recovery"
30)
31
32type selinuxContextsProperties struct {
33	// Filenames under sepolicy directories, which will be used to generate contexts file.
34	Srcs []string `android:"path"`
35
36	Product_variables struct {
37		Debuggable struct {
38			Srcs []string
39		}
40
41		Address_sanitize struct {
42			Srcs []string
43		}
44	}
45
46	// Whether reqd_mask directory is included to sepolicy directories or not.
47	Reqd_mask *bool
48
49	// Whether the comments in generated contexts file will be removed or not.
50	Remove_comment *bool
51
52	// Whether the result context file is sorted with fc_sort or not.
53	Fc_sort *bool
54
55	// Make this module available when building for recovery
56	Recovery_available *bool
57
58	InRecovery bool `blueprint:"mutated"`
59}
60
61type fileContextsProperties struct {
62	// flatten_apex can be used to specify additional sources of file_contexts.
63	// Apex paths, /system/apex/{apex_name}, will be amended to the paths of file_contexts
64	// entries.
65	Flatten_apex struct {
66		Srcs []string
67	}
68}
69
70type selinuxContextsModule struct {
71	android.ModuleBase
72
73	properties             selinuxContextsProperties
74	fileContextsProperties fileContextsProperties
75	build                  func(ctx android.ModuleContext, inputs android.Paths)
76	outputPath             android.ModuleGenPath
77	installPath            android.InstallPath
78}
79
80var (
81	reuseContextsDepTag = dependencyTag{name: "reuseContexts"}
82)
83
84func init() {
85	pctx.HostBinToolVariable("fc_sort", "fc_sort")
86
87	android.RegisterModuleType("file_contexts", fileFactory)
88	android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
89	android.RegisterModuleType("property_contexts", propertyFactory)
90	android.RegisterModuleType("service_contexts", serviceFactory)
91
92	android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
93		ctx.BottomUp("selinux_contexts", selinuxContextsMutator).Parallel()
94	})
95}
96
97func (m *selinuxContextsModule) inRecovery() bool {
98	return m.properties.InRecovery || m.ModuleBase.InstallInRecovery()
99}
100
101func (m *selinuxContextsModule) onlyInRecovery() bool {
102	return m.ModuleBase.InstallInRecovery()
103}
104
105func (m *selinuxContextsModule) InstallInRecovery() bool {
106	return m.inRecovery()
107}
108
109func (m *selinuxContextsModule) InstallInRoot() bool {
110	return m.inRecovery()
111}
112
113func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
114	if m.inRecovery() {
115		// Installing context files at the root of the recovery partition
116		m.installPath = android.PathForModuleInstall(ctx)
117	} else {
118		m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
119	}
120
121	if m.inRecovery() && !m.onlyInRecovery() {
122		dep := ctx.GetDirectDepWithTag(m.Name(), reuseContextsDepTag)
123
124		if reuseDeps, ok := dep.(*selinuxContextsModule); ok {
125			m.outputPath = reuseDeps.outputPath
126			ctx.InstallFile(m.installPath, m.Name(), m.outputPath)
127			return
128		}
129	}
130
131	var inputs android.Paths
132
133	ctx.VisitDirectDepsWithTag(android.SourceDepTag, func(dep android.Module) {
134		segroup, ok := dep.(*fileGroup)
135		if !ok {
136			ctx.ModuleErrorf("srcs dependency %q is not an selinux filegroup",
137				ctx.OtherModuleName(dep))
138			return
139		}
140
141		if ctx.ProductSpecific() {
142			inputs = append(inputs, segroup.ProductPrivateSrcs()...)
143		} else if ctx.SocSpecific() {
144			inputs = append(inputs, segroup.SystemVendorSrcs()...)
145			inputs = append(inputs, segroup.VendorSrcs()...)
146		} else if ctx.DeviceSpecific() {
147			inputs = append(inputs, segroup.OdmSrcs()...)
148		} else if ctx.SystemExtSpecific() {
149			inputs = append(inputs, segroup.SystemExtPrivateSrcs()...)
150		} else {
151			inputs = append(inputs, segroup.SystemPrivateSrcs()...)
152
153			if ctx.Config().ProductCompatibleProperty() {
154				inputs = append(inputs, segroup.SystemPublicSrcs()...)
155			}
156		}
157
158		if proptools.Bool(m.properties.Reqd_mask) {
159			inputs = append(inputs, segroup.SystemReqdMaskSrcs()...)
160		}
161	})
162
163	for _, src := range m.properties.Srcs {
164		// Module sources are handled above with VisitDirectDepsWithTag
165		if android.SrcIsModule(src) == "" {
166			inputs = append(inputs, android.PathForModuleSrc(ctx, src))
167		}
168	}
169
170	m.build(ctx, inputs)
171}
172
173func newModule() *selinuxContextsModule {
174	m := &selinuxContextsModule{}
175	m.AddProperties(
176		&m.properties,
177	)
178	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
179	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
180		m.selinuxContextsHook(ctx)
181	})
182	return m
183}
184
185func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
186	// TODO: clean this up to use build/soong/android/variable.go after b/79249983
187	var srcs []string
188
189	if ctx.Config().Debuggable() {
190		srcs = append(srcs, m.properties.Product_variables.Debuggable.Srcs...)
191	}
192
193	for _, sanitize := range ctx.Config().SanitizeDevice() {
194		if sanitize == "address" {
195			srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
196			break
197		}
198	}
199
200	m.properties.Srcs = append(m.properties.Srcs, srcs...)
201}
202
203func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
204	return android.AndroidMkData{
205		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
206			nameSuffix := ""
207			if m.inRecovery() && !m.onlyInRecovery() {
208				nameSuffix = ".recovery"
209			}
210			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
211			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
212			fmt.Fprintln(w, "LOCAL_MODULE :=", name+nameSuffix)
213			fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
214			if m.Owner() != "" {
215				fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", m.Owner())
216			}
217			fmt.Fprintln(w, "LOCAL_MODULE_TAGS := optional")
218			fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", m.outputPath.String())
219			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.ToMakePath().String())
220			fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", name)
221			fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
222		},
223	}
224}
225
226func selinuxContextsMutator(ctx android.BottomUpMutatorContext) {
227	m, ok := ctx.Module().(*selinuxContextsModule)
228	if !ok {
229		return
230	}
231
232	var coreVariantNeeded bool = true
233	var recoveryVariantNeeded bool = false
234	if proptools.Bool(m.properties.Recovery_available) {
235		recoveryVariantNeeded = true
236	}
237
238	if m.ModuleBase.InstallInRecovery() {
239		recoveryVariantNeeded = true
240		coreVariantNeeded = false
241	}
242
243	var variants []string
244	if coreVariantNeeded {
245		variants = append(variants, coreMode)
246	}
247	if recoveryVariantNeeded {
248		variants = append(variants, recoveryMode)
249	}
250	mod := ctx.CreateVariations(variants...)
251
252	for i, v := range variants {
253		if v == recoveryMode {
254			m := mod[i].(*selinuxContextsModule)
255			m.properties.InRecovery = true
256
257			if coreVariantNeeded {
258				ctx.AddInterVariantDependency(reuseContextsDepTag, m, mod[i-1])
259			}
260		}
261	}
262}
263
264func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) {
265	m.outputPath = android.PathForModuleGen(ctx, ctx.ModuleName()+"_m4out")
266
267	rule := android.NewRuleBuilder()
268
269	rule.Command().
270		Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
271		Text("--fatal-warnings -s").
272		FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
273		Inputs(inputs).
274		FlagWithOutput("> ", m.outputPath)
275
276	if proptools.Bool(m.properties.Remove_comment) {
277		rule.Temporary(m.outputPath)
278
279		remove_comment_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_remove_comment")
280
281		rule.Command().
282			Text("sed -e 's/#.*$//' -e '/^$/d'").
283			Input(m.outputPath).
284			FlagWithOutput("> ", remove_comment_output)
285
286		m.outputPath = remove_comment_output
287	}
288
289	if proptools.Bool(m.properties.Fc_sort) {
290		rule.Temporary(m.outputPath)
291
292		sorted_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_sorted")
293
294		rule.Command().
295			Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
296			FlagWithInput("-i ", m.outputPath).
297			FlagWithOutput("-o ", sorted_output)
298
299		m.outputPath = sorted_output
300	}
301
302	rule.Build(pctx, ctx, "selinux_contexts", m.Name())
303
304	rule.DeleteTemporaryFiles()
305
306	ctx.InstallFile(m.installPath, ctx.ModuleName(), m.outputPath)
307}
308
309func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) {
310	if m.properties.Fc_sort == nil {
311		m.properties.Fc_sort = proptools.BoolPtr(true)
312	}
313
314	rule := android.NewRuleBuilder()
315
316	if ctx.Config().FlattenApex() {
317		for _, src := range m.fileContextsProperties.Flatten_apex.Srcs {
318			if m := android.SrcIsModule(src); m != "" {
319				ctx.ModuleErrorf(
320					"Module srcs dependency %q is not supported for flatten_apex.srcs", m)
321				return
322			}
323			for _, path := range android.PathsForModuleSrcExcludes(ctx, []string{src}, nil) {
324				out := android.PathForModuleGen(ctx, "flattened_apex", path.Rel())
325				apex_path := "/system/apex/" + strings.Replace(
326					strings.TrimSuffix(path.Base(), "-file_contexts"),
327					".", "\\\\.", -1)
328
329				rule.Command().
330					Text("awk '/object_r/{printf(\""+apex_path+"%s\\n\",$0)}'").
331					Input(path).
332					FlagWithOutput("> ", out)
333
334				inputs = append(inputs, out)
335			}
336		}
337	}
338
339	rule.Build(pctx, ctx, m.Name(), "flattened_apex_file_contexts")
340	m.buildGeneralContexts(ctx, inputs)
341}
342
343func fileFactory() android.Module {
344	m := newModule()
345	m.AddProperties(&m.fileContextsProperties)
346	m.build = m.buildFileContexts
347	return m
348}
349
350func (m *selinuxContextsModule) buildHwServiceContexts(ctx android.ModuleContext, inputs android.Paths) {
351	if m.properties.Remove_comment == nil {
352		m.properties.Remove_comment = proptools.BoolPtr(true)
353	}
354
355	m.buildGeneralContexts(ctx, inputs)
356}
357
358func hwServiceFactory() android.Module {
359	m := newModule()
360	m.build = m.buildHwServiceContexts
361	return m
362}
363
364func propertyFactory() android.Module {
365	m := newModule()
366	m.build = m.buildGeneralContexts
367	return m
368}
369
370func serviceFactory() android.Module {
371	m := newModule()
372	m.build = m.buildGeneralContexts
373	return m
374}
375