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"
23	"github.com/google/blueprint/proptools"
24
25	"android/soong/android"
26	"android/soong/sysprop"
27)
28
29type selinuxContextsProperties struct {
30	// Filenames under sepolicy directories, which will be used to generate contexts file.
31	Srcs []string `android:"path"`
32
33	Product_variables struct {
34		Debuggable struct {
35			Srcs []string
36		}
37
38		Address_sanitize struct {
39			Srcs []string
40		}
41	}
42
43	// Whether reqd_mask directory is included to sepolicy directories or not.
44	Reqd_mask *bool
45
46	// Whether the comments in generated contexts file will be removed or not.
47	Remove_comment *bool
48
49	// Whether the result context file is sorted with fc_sort or not.
50	Fc_sort *bool
51
52	// Make this module available when building for recovery
53	Recovery_available *bool
54}
55
56type fileContextsProperties struct {
57	// flatten_apex can be used to specify additional sources of file_contexts.
58	// Apex paths, /system/apex/{apex_name}, will be amended to the paths of file_contexts
59	// entries.
60	Flatten_apex struct {
61		Srcs []string
62	}
63}
64
65type selinuxContextsModule struct {
66	android.ModuleBase
67
68	properties             selinuxContextsProperties
69	fileContextsProperties fileContextsProperties
70	build                  func(ctx android.ModuleContext, inputs android.Paths) android.Path
71	deps                   func(ctx android.BottomUpMutatorContext)
72	outputPath             android.Path
73	installPath            android.InstallPath
74}
75
76var (
77	reuseContextsDepTag  = dependencyTag{name: "reuseContexts"}
78	syspropLibraryDepTag = dependencyTag{name: "sysprop_library"}
79)
80
81func init() {
82	pctx.HostBinToolVariable("fc_sort", "fc_sort")
83
84	android.RegisterModuleType("file_contexts", fileFactory)
85	android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
86	android.RegisterModuleType("property_contexts", propertyFactory)
87	android.RegisterModuleType("service_contexts", serviceFactory)
88	android.RegisterModuleType("keystore2_key_contexts", keystoreKeyFactory)
89}
90
91func (m *selinuxContextsModule) InstallInRoot() bool {
92	return m.InRecovery()
93}
94
95func (m *selinuxContextsModule) InstallInRecovery() bool {
96	// ModuleBase.InRecovery() checks the image variant
97	return m.InRecovery()
98}
99
100func (m *selinuxContextsModule) onlyInRecovery() bool {
101	// ModuleBase.InstallInRecovery() checks commonProperties.Recovery property
102	return m.ModuleBase.InstallInRecovery()
103}
104
105func (m *selinuxContextsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
106	if m.deps != nil {
107		m.deps(ctx)
108	}
109
110	if m.InRecovery() && !m.onlyInRecovery() {
111		ctx.AddFarVariationDependencies([]blueprint.Variation{
112			{Mutator: "image", Variation: android.CoreVariation},
113		}, reuseContextsDepTag, ctx.ModuleName())
114	}
115}
116
117func (m *selinuxContextsModule) propertyContextsDeps(ctx android.BottomUpMutatorContext) {
118	for _, lib := range sysprop.SyspropLibraries(ctx.Config()) {
119		ctx.AddFarVariationDependencies([]blueprint.Variation{}, syspropLibraryDepTag, lib)
120	}
121}
122
123func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
124	if m.InRecovery() {
125		// Installing context files at the root of the recovery partition
126		m.installPath = android.PathForModuleInstall(ctx)
127	} else {
128		m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
129	}
130
131	if m.InRecovery() && !m.onlyInRecovery() {
132		dep := ctx.GetDirectDepWithTag(m.Name(), reuseContextsDepTag)
133
134		if reuseDeps, ok := dep.(*selinuxContextsModule); ok {
135			m.outputPath = reuseDeps.outputPath
136			ctx.InstallFile(m.installPath, m.Name(), m.outputPath)
137			return
138		}
139	}
140
141	var inputs android.Paths
142
143	ctx.VisitDirectDepsWithTag(android.SourceDepTag, func(dep android.Module) {
144		segroup, ok := dep.(*fileGroup)
145		if !ok {
146			ctx.ModuleErrorf("srcs dependency %q is not an selinux filegroup",
147				ctx.OtherModuleName(dep))
148			return
149		}
150
151		if ctx.ProductSpecific() {
152			inputs = append(inputs, segroup.ProductPrivateSrcs()...)
153		} else if ctx.SocSpecific() {
154			if ctx.DeviceConfig().BoardSepolicyVers() == ctx.DeviceConfig().PlatformSepolicyVersion() {
155				inputs = append(inputs, segroup.SystemVendorSrcs()...)
156			}
157			inputs = append(inputs, segroup.VendorSrcs()...)
158		} else if ctx.DeviceSpecific() {
159			inputs = append(inputs, segroup.OdmSrcs()...)
160		} else if ctx.SystemExtSpecific() {
161			inputs = append(inputs, segroup.SystemExtPrivateSrcs()...)
162		} else {
163			inputs = append(inputs, segroup.SystemPrivateSrcs()...)
164			inputs = append(inputs, segroup.SystemPublicSrcs()...)
165		}
166
167		if proptools.Bool(m.properties.Reqd_mask) {
168			if ctx.SocSpecific() || ctx.DeviceSpecific() {
169				inputs = append(inputs, segroup.VendorReqdMaskSrcs()...)
170			} else {
171				inputs = append(inputs, segroup.SystemReqdMaskSrcs()...)
172			}
173		}
174	})
175
176	for _, src := range m.properties.Srcs {
177		// Module sources are handled above with VisitDirectDepsWithTag
178		if android.SrcIsModule(src) == "" {
179			inputs = append(inputs, android.PathForModuleSrc(ctx, src))
180		}
181	}
182
183	m.outputPath = m.build(ctx, inputs)
184	ctx.InstallFile(m.installPath, ctx.ModuleName(), m.outputPath)
185}
186
187func newModule() *selinuxContextsModule {
188	m := &selinuxContextsModule{}
189	m.AddProperties(
190		&m.properties,
191	)
192	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
193	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
194		m.selinuxContextsHook(ctx)
195	})
196	return m
197}
198
199func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
200	// TODO: clean this up to use build/soong/android/variable.go after b/79249983
201	var srcs []string
202
203	if ctx.Config().Debuggable() {
204		srcs = append(srcs, m.properties.Product_variables.Debuggable.Srcs...)
205	}
206
207	for _, sanitize := range ctx.Config().SanitizeDevice() {
208		if sanitize == "address" {
209			srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
210			break
211		}
212	}
213
214	m.properties.Srcs = append(m.properties.Srcs, srcs...)
215}
216
217func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
218	return android.AndroidMkData{
219		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
220			nameSuffix := ""
221			if m.InRecovery() && !m.onlyInRecovery() {
222				nameSuffix = ".recovery"
223			}
224			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
225			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
226			fmt.Fprintln(w, "LOCAL_MODULE :=", name+nameSuffix)
227			data.Entries.WriteLicenseVariables(w)
228			fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
229			if m.Owner() != "" {
230				fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", m.Owner())
231			}
232			fmt.Fprintln(w, "LOCAL_MODULE_TAGS := optional")
233			fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", m.outputPath.String())
234			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.ToMakePath().String())
235			fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", name)
236			fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
237		},
238	}
239}
240
241func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
242	if proptools.Bool(m.properties.Recovery_available) && m.InstallInRecovery() {
243		ctx.PropertyErrorf("recovery_available",
244			"doesn't make sense at the same time as `recovery: true`")
245	}
246}
247
248func (m *selinuxContextsModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
249	return !m.InstallInRecovery()
250}
251
252func (m *selinuxContextsModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
253	return false
254}
255
256func (m *selinuxContextsModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
257	return false
258}
259
260func (m *selinuxContextsModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
261	return false
262}
263
264func (m *selinuxContextsModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
265	return m.InstallInRecovery() || proptools.Bool(m.properties.Recovery_available)
266}
267
268func (m *selinuxContextsModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
269	return nil
270}
271
272func (m *selinuxContextsModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
273}
274
275var _ android.ImageInterface = (*selinuxContextsModule)(nil)
276
277func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
278	ret := android.PathForModuleGen(ctx, ctx.ModuleName()+"_m4out")
279
280	rule := android.NewRuleBuilder(pctx, ctx)
281
282	rule.Command().
283		Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
284		Text("--fatal-warnings -s").
285		FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
286		Inputs(inputs).
287		FlagWithOutput("> ", ret)
288
289	if proptools.Bool(m.properties.Remove_comment) {
290		rule.Temporary(ret)
291
292		remove_comment_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_remove_comment")
293
294		rule.Command().
295			Text("sed -e 's/#.*$//' -e '/^$/d'").
296			Input(ret).
297			FlagWithOutput("> ", remove_comment_output)
298
299		ret = remove_comment_output
300	}
301
302	if proptools.Bool(m.properties.Fc_sort) {
303		rule.Temporary(ret)
304
305		sorted_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_sorted")
306
307		rule.Command().
308			Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
309			FlagWithInput("-i ", ret).
310			FlagWithOutput("-o ", sorted_output)
311
312		ret = sorted_output
313	}
314
315	rule.Build("selinux_contexts", "building contexts: "+m.Name())
316
317	rule.DeleteTemporaryFiles()
318
319	return ret
320}
321
322func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
323	if m.properties.Fc_sort == nil {
324		m.properties.Fc_sort = proptools.BoolPtr(true)
325	}
326
327	rule := android.NewRuleBuilder(pctx, ctx)
328
329	if ctx.Config().FlattenApex() {
330		for _, src := range m.fileContextsProperties.Flatten_apex.Srcs {
331			if m := android.SrcIsModule(src); m != "" {
332				ctx.ModuleErrorf(
333					"Module srcs dependency %q is not supported for flatten_apex.srcs", m)
334				return nil
335			}
336			for _, path := range android.PathsForModuleSrcExcludes(ctx, []string{src}, nil) {
337				out := android.PathForModuleGen(ctx, "flattened_apex", path.Rel())
338				apex_path := "/system/apex/" + strings.Replace(
339					strings.TrimSuffix(path.Base(), "-file_contexts"),
340					".", "\\\\.", -1)
341
342				rule.Command().
343					Text("awk '/object_r/{printf(\""+apex_path+"%s\\n\",$0)}'").
344					Input(path).
345					FlagWithOutput("> ", out)
346
347				inputs = append(inputs, out)
348			}
349		}
350	}
351
352	rule.Build(m.Name(), "flattened_apex_file_contexts")
353	return m.buildGeneralContexts(ctx, inputs)
354}
355
356func fileFactory() android.Module {
357	m := newModule()
358	m.AddProperties(&m.fileContextsProperties)
359	m.build = m.buildFileContexts
360	return m
361}
362
363func (m *selinuxContextsModule) buildHwServiceContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
364	if m.properties.Remove_comment == nil {
365		m.properties.Remove_comment = proptools.BoolPtr(true)
366	}
367
368	return m.buildGeneralContexts(ctx, inputs)
369}
370
371func (m *selinuxContextsModule) checkVendorPropertyNamespace(ctx android.ModuleContext, inputs android.Paths) android.Paths {
372	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
373	ApiLevelR := android.ApiLevelOrPanic(ctx, "R")
374
375	rule := android.NewRuleBuilder(pctx, ctx)
376
377	// This list is from vts_treble_sys_prop_test.
378	allowedPropertyPrefixes := []string{
379		"ctl.odm.",
380		"ctl.vendor.",
381		"ctl.start$odm.",
382		"ctl.start$vendor.",
383		"ctl.stop$odm.",
384		"ctl.stop$vendor.",
385		"init.svc.odm.",
386		"init.svc.vendor.",
387		"ro.boot.",
388		"ro.hardware.",
389		"ro.odm.",
390		"ro.vendor.",
391		"odm.",
392		"persist.odm.",
393		"persist.vendor.",
394		"vendor.",
395	}
396
397	// persist.camera is also allowed for devices launching with R or eariler
398	if shippingApiLevel.LessThanOrEqualTo(ApiLevelR) {
399		allowedPropertyPrefixes = append(allowedPropertyPrefixes, "persist.camera.")
400	}
401
402	var allowedContextPrefixes []string
403
404	if shippingApiLevel.GreaterThanOrEqualTo(ApiLevelR) {
405		// This list is from vts_treble_sys_prop_test.
406		allowedContextPrefixes = []string{
407			"vendor_",
408			"odm_",
409		}
410	}
411
412	var ret android.Paths
413	for _, input := range inputs {
414		cmd := rule.Command().
415			BuiltTool("check_prop_prefix").
416			FlagWithInput("--property-contexts ", input).
417			FlagForEachArg("--allowed-property-prefix ", proptools.ShellEscapeList(allowedPropertyPrefixes)). // contains shell special character '$'
418			FlagForEachArg("--allowed-context-prefix ", allowedContextPrefixes)
419
420		if !ctx.DeviceConfig().BuildBrokenVendorPropertyNamespace() {
421			cmd.Flag("--strict")
422		}
423
424		out := android.PathForModuleGen(ctx, "namespace_checked").Join(ctx, input.String())
425		rule.Command().Text("cp -f").Input(input).Output(out)
426		ret = append(ret, out)
427	}
428	rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName())
429	return ret
430}
431
432func (m *selinuxContextsModule) buildPropertyContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
433	// vendor/odm properties are enforced for devices launching with Android Q or later. So, if
434	// vendor/odm, make sure that only vendor/odm properties exist.
435	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
436	ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q")
437	if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) {
438		inputs = m.checkVendorPropertyNamespace(ctx, inputs)
439	}
440
441	builtCtxFile := m.buildGeneralContexts(ctx, inputs)
442
443	var apiFiles android.Paths
444	ctx.VisitDirectDepsWithTag(syspropLibraryDepTag, func(c android.Module) {
445		i, ok := c.(interface{ CurrentSyspropApiFile() android.OptionalPath })
446		if !ok {
447			panic(fmt.Errorf("unknown dependency %q for %q", ctx.OtherModuleName(c), ctx.ModuleName()))
448		}
449		if api := i.CurrentSyspropApiFile(); api.Valid() {
450			apiFiles = append(apiFiles, api.Path())
451		}
452	})
453
454	// check compatibility with sysprop_library
455	if len(apiFiles) > 0 {
456		out := android.PathForModuleGen(ctx, ctx.ModuleName()+"_api_checked")
457		rule := android.NewRuleBuilder(pctx, ctx)
458
459		msg := `\n******************************\n` +
460			`API of sysprop_library doesn't match with property_contexts\n` +
461			`Please fix the breakage and rebuild.\n` +
462			`******************************\n`
463
464		rule.Command().
465			Text("( ").
466			BuiltTool("sysprop_type_checker").
467			FlagForEachInput("--api ", apiFiles).
468			FlagWithInput("--context ", builtCtxFile).
469			Text(" || ( echo").Flag("-e").
470			Flag(`"` + msg + `"`).
471			Text("; exit 38) )")
472
473		rule.Command().Text("cp -f").Input(builtCtxFile).Output(out)
474		rule.Build("property_contexts_check_api", "checking API: "+m.Name())
475		builtCtxFile = out
476	}
477
478	return builtCtxFile
479}
480
481func hwServiceFactory() android.Module {
482	m := newModule()
483	m.build = m.buildHwServiceContexts
484	return m
485}
486
487func propertyFactory() android.Module {
488	m := newModule()
489	m.build = m.buildPropertyContexts
490	m.deps = m.propertyContextsDeps
491	return m
492}
493
494func serviceFactory() android.Module {
495	m := newModule()
496	m.build = m.buildGeneralContexts
497	return m
498}
499
500func keystoreKeyFactory() android.Module {
501	m := newModule()
502	m.build = m.buildGeneralContexts
503	return m
504}
505