Relax freeze_test to check only compatibility

For now, freeze_test compares prebuilts against sources with diff, to
ensure that sources are identical to prebuilts. However, it could be the
case that the branch should be able to build both REL and ToT. In that
case, changes to the sources are inevitable and the freeze test will
fail.

To fix the issue, freeze_test will now only check compatibility. To be
specific, it will check if any public types or attributes are removed.
Contexts files and neverallow rules are not checked, but they may be
added later. Also to support the new freeze_test

- build_files module is changed to use glob (because REL version won't
  be in compat versions list)
- plat_pub_policy modules are added under prebuilts/api (because
  freeze_test needs that)

Bug: 296875906
Test: m selinux_policy
Change-Id: I39c40992965b98664facea3b760d9d6be1f6b87e
This commit is contained in:
Inseob Kim 2023-09-04 17:40:03 +09:00
parent 796ec5f0cb
commit 36d9d39e6e
11 changed files with 334 additions and 24 deletions

View file

@ -913,7 +913,7 @@ se_neverallow_test {
// SEPOLICY_FREEZE_TEST_EXTRA_DIRS and SEPOLICY_FREEZE_TEST_EXTRA_PREBUILT_DIRS.
//////////////////////////////////
se_freeze_test {
name: "sepolicy_freeze_test",
name: "se_freeze_test",
}
//////////////////////////////////

View file

@ -16,6 +16,7 @@ package selinux
import (
"fmt"
"path"
"path/filepath"
"strings"
@ -103,8 +104,15 @@ func (b *buildFiles) GenerateAndroidBuildActions(ctx android.ModuleContext) {
b.srcs[".vendor"] = b.findSrcsInDirs(ctx, ctx.DeviceConfig().VendorSepolicyDirs()...)
b.srcs[".odm"] = b.findSrcsInDirs(ctx, ctx.DeviceConfig().OdmSepolicyDirs()...)
prebuilt_directories, err := ctx.GlobWithDeps("system/sepolicy/prebuilts/api/*", nil)
if err != nil {
ctx.ModuleErrorf("error while globbing: %w", err)
return
}
// directories used for compat tests and Treble tests
for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
for _, dir := range prebuilt_directories {
ver := path.Base(dir)
b.srcs[".plat_public_"+ver] = b.findSrcsInDirs(ctx, filepath.Join("system", "sepolicy", "prebuilts", "api", ver, "public"))
b.srcs[".plat_private_"+ver] = b.findSrcsInDirs(ctx, filepath.Join("system", "sepolicy", "prebuilts", "api", ver, "private"))
b.srcs[".system_ext_public_"+ver] = b.findSrcsInDirs(ctx, filepath.Join(ctx.DeviceConfig().SystemExtSepolicyPrebuiltApiDir(), "prebuilts", "api", ver, "public"))

View file

@ -15,12 +15,14 @@
package selinux
import (
"path/filepath"
"sort"
"android/soong/android"
)
var currentCilTag = dependencyTag{name: "current_cil"}
var prebuiltCilTag = dependencyTag{name: "prebuilt_cil"}
func init() {
ctx := android.InitRegistrationContext
ctx.RegisterParallelSingletonModuleType("se_freeze_test", freezeTestFactory)
@ -32,6 +34,9 @@ func init() {
func freezeTestFactory() android.SingletonModule {
f := &freezeTestModule{}
android.InitAndroidModule(f)
android.AddLoadHook(f, func(ctx android.LoadHookContext) {
f.loadHook(ctx)
})
return f
}
@ -40,26 +45,23 @@ type freezeTestModule struct {
freezeTestTimestamp android.ModuleOutPath
}
func (f *freezeTestModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
// does nothing; se_freeze_test is a singeton because two freeze test modules don't make sense.
}
func (f *freezeTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
func (f *freezeTestModule) shouldSkip(ctx android.EarlyModuleContext) bool {
platformVersion := ctx.DeviceConfig().PlatformSepolicyVersion()
totVersion := ctx.DeviceConfig().TotSepolicyVersion()
return platformVersion == totVersion
}
func (f *freezeTestModule) loadHook(ctx android.LoadHookContext) {
extraDirs := ctx.DeviceConfig().SepolicyFreezeTestExtraDirs()
extraPrebuiltDirs := ctx.DeviceConfig().SepolicyFreezeTestExtraPrebuiltDirs()
f.freezeTestTimestamp = android.PathForModuleOut(ctx, "freeze_test")
if platformVersion == totVersion {
if f.shouldSkip(ctx) {
if len(extraDirs) > 0 || len(extraPrebuiltDirs) > 0 {
ctx.ModuleErrorf("SEPOLICY_FREEZE_TEST_EXTRA_DIRS or SEPOLICY_FREEZE_TEST_EXTRA_PREBUILT_DIRS cannot be set before system/sepolicy freezes.")
return
}
// we still build a rule to prevent possible regression
android.WriteFileRule(ctx, f.freezeTestTimestamp, ";; no freeze tests needed before system/sepolicy freezes")
return
}
@ -67,17 +69,80 @@ func (f *freezeTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext
ctx.ModuleErrorf("SEPOLICY_FREEZE_TEST_EXTRA_DIRS and SEPOLICY_FREEZE_TEST_EXTRA_PREBUILT_DIRS must have the same number of directories.")
return
}
}
platPublic := filepath.Join(ctx.ModuleDir(), "public")
platPrivate := filepath.Join(ctx.ModuleDir(), "private")
prebuiltPublic := filepath.Join(ctx.ModuleDir(), "prebuilts", "api", platformVersion, "public")
prebuiltPrivate := filepath.Join(ctx.ModuleDir(), "prebuilts", "api", platformVersion, "private")
func (f *freezeTestModule) prebuiltCilModuleName(ctx android.EarlyModuleContext) string {
return ctx.DeviceConfig().PlatformSepolicyVersion() + "_plat_pub_policy.cil"
}
sourceDirs := append(extraDirs, platPublic, platPrivate)
prebuiltDirs := append(extraPrebuiltDirs, prebuiltPublic, prebuiltPrivate)
func (f *freezeTestModule) DepsMutator(ctx android.BottomUpMutatorContext) {
if f.shouldSkip(ctx) {
return
}
ctx.AddDependency(f, currentCilTag, "base_plat_pub_policy.cil")
ctx.AddDependency(f, prebuiltCilTag, f.prebuiltCilModuleName(ctx))
}
func (f *freezeTestModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
// does nothing; se_freeze_test is a singeton because two freeze test modules don't make sense.
}
func (f *freezeTestModule) outputFileOfDep(ctx android.ModuleContext, depTag dependencyTag) android.Path {
deps := ctx.GetDirectDepsWithTag(depTag)
if len(deps) != 1 {
ctx.ModuleErrorf("%d deps having tag %q; expected only one dep", len(deps), depTag)
return nil
}
dep := deps[0]
outputFileProducer, ok := dep.(android.OutputFileProducer)
if !ok {
ctx.ModuleErrorf("module %q is not an output file producer", dep.String())
return nil
}
output, err := outputFileProducer.OutputFiles("")
if err != nil {
ctx.ModuleErrorf("module %q failed to produce output: %w", dep.String(), err)
return nil
}
if len(output) != 1 {
ctx.ModuleErrorf("module %q produced %d outputs; expected only one output", dep.String(), len(output))
return nil
}
return output[0]
}
func (f *freezeTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
f.freezeTestTimestamp = android.PathForModuleOut(ctx, "freeze_test")
if f.shouldSkip(ctx) {
// we still build a rule to prevent possible regression
android.WriteFileRule(ctx, f.freezeTestTimestamp, ";; no freeze tests needed before system/sepolicy freezes")
return
}
// Freeze test 1: compare ToT sepolicy and prebuilt sepolicy
currentCil := f.outputFileOfDep(ctx, currentCilTag)
prebuiltCil := f.outputFileOfDep(ctx, prebuiltCilTag)
if ctx.Failed() {
return
}
rule := android.NewRuleBuilder(pctx, ctx)
rule.Command().BuiltTool("sepolicy_freeze_test").
FlagWithInput("-c ", currentCil).
FlagWithInput("-p ", prebuiltCil)
// Freeze test 2: compare extra directories
// We don't know the exact structure of extra directories, so just directly compare them
extraDirs := ctx.DeviceConfig().SepolicyFreezeTestExtraDirs()
extraPrebuiltDirs := ctx.DeviceConfig().SepolicyFreezeTestExtraPrebuiltDirs()
var implicits []string
for _, dir := range append(sourceDirs, prebuiltDirs...) {
for _, dir := range append(extraDirs, extraPrebuiltDirs...) {
glob, err := ctx.GlobWithDeps(dir+"/**/*", []string{"bug_map"} /* exclude */)
if err != nil {
ctx.ModuleErrorf("failed to glob sepolicy dir %q: %s", dir, err.Error())
@ -87,15 +152,13 @@ func (f *freezeTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext
}
sort.Strings(implicits)
rule := android.NewRuleBuilder(pctx, ctx)
for idx, _ := range sourceDirs {
for idx, _ := range extraDirs {
rule.Command().Text("diff").
Flag("-r").
Flag("-q").
FlagWithArg("-x ", "bug_map"). // exclude
Text(sourceDirs[idx]).
Text(prebuiltDirs[idx])
Text(extraDirs[idx]).
Text(extraPrebuiltDirs[idx])
}
rule.Command().Text("touch").

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "29.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_29.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "29.0_plat_pub_policy.cil",
src: ":29.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "30.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_30.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "30.0_plat_pub_policy.cil",
src: ":30.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "31.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_31.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "31.0_plat_pub_policy.cil",
src: ":31.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "32.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_32.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "32.0_plat_pub_policy.cil",
src: ":32.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "33.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_33.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "33.0_plat_pub_policy.cil",
src: ":33.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
se_policy_conf {
name: "34.0_plat_pub_policy.conf",
srcs: [":se_build_files{.plat_public_34.0}", ":se_build_files{.reqd_mask}"],
installable: false,
build_variant: "user",
}
se_policy_cil {
name: "34.0_plat_pub_policy.cil",
src: ":34.0_plat_pub_policy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false,
installable: false,
}

View file

@ -146,3 +146,18 @@ python_binary_host {
name: "check_prop_prefix",
srcs: ["check_prop_prefix.py"],
}
python_binary_host {
name: "sepolicy_freeze_test",
srcs: [
"sepolicy_freeze_test.py",
],
version: {
py3: {
embedded_launcher: true,
},
},
libs: [
"mini_cil_parser",
],
}

View file

@ -0,0 +1,56 @@
# Copyright 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from optparse import OptionParser
import mini_parser
import os
import sys
def do_main():
usage = "sepolicy_freeze_test "
usage += "-c current_cil -p prebuilt_cil [--help]"
parser = OptionParser(usage=usage)
parser.add_option("-c", "--current", dest="current", metavar="FILE")
parser.add_option("-p", "--prebuilt", dest="prebuilt", metavar="FILE")
(options, args) = parser.parse_args()
if not options.current or not options.prebuilt:
sys.exit("Must specify both current and prebuilt\n" + parser.usage)
if not os.path.exists(options.current):
sys.exit("Current policy " + options.current + " does not exist\n"
+ parser.usage)
if not os.path.exists(options.prebuilt):
sys.exit("Prebuilt policy " + options.prebuilt + " does not exist\n"
+ parser.usage)
current_policy = mini_parser.MiniCilParser(options.current)
prebuilt_policy = mini_parser.MiniCilParser(options.prebuilt)
results = ""
removed_types = prebuilt_policy.types - current_policy.types
removed_attributes = prebuilt_policy.typeattributes - current_policy.typeattributes
removed_attributes = set(filter(lambda x: "base_typeattr_" not in x, removed_attributes))
if removed_types:
results += "The following public types were removed:\n" + ", ".join(removed_types) + "\n"
if removed_attributes:
results += "The following public attributes were removed:\n" + ", ".join(removed_attributes) + "\n"
if len(results) > 0:
sys.exit(results)
if __name__ == '__main__':
do_main()