Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c42f2f2e authored by Inseob Kim's avatar Inseob Kim Committed by Bill Peckham
Browse files

Add cfi static libraries to vendor snapshot

CFI modules can't link against non-CFI static libraries, and vice versa.
So without capturing both CFI and non-CFI static libraries, vendor
modules won't be able to use CFI, which will be a critical security
hole.

This captures both CFI and non-CFI variants of all static libraries for
vendor snapshot, except for those whose cfi are explicitly disabled.

For example, suppose that "libfoo" is defined as follows.

cc_library_static {
    name: "libfoo",
    vendor_available: true,
}

As it doesn't have cfi disabled, two libraries "libfoo.a" and
"libfoo.cfi.a" will be captured. When installed, vendor snapshot module
for "libfoo" will look like:

vendor_snapshot_static {
    name: "libfoo",
    src: "libfoo.a",
    cfi: {
        src: "libfoo.cfi.a",
    },
}

The build system will recognize the "cfi" property, and will create both
CFI and non-CFI variant, allowing any modules to link against "libfoo"
safely, no matter whether CFI is enabled or not.

Two clarification:

1) The reason why we don't create separate modules is that DepsMutator
runs before sanitize mutators. CFI and non-CFI variant of a library
should exist in a single module.

2) We can't capture CFI variant if the source module explicitly disables
cfi variant by specifying the following.

sanitize: {
    cfi: false,
}

In this case, only non-CFI variant will be created for the vendor
snapshot module.

Bug: 65377115
Test: m dist vendor-snapshot && install && build against snapshot
Change-Id: Idbf3e3205d581800d6093c8d6cf6152374129ba4
parent 2d34ad94
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -518,10 +518,14 @@ func (c *vendorSnapshotLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext,
		entries.Class = "HEADER_LIBRARIES"
	}

	if c.androidMkVendorSuffix {
		entries.SubName = vendorSuffix
	} else {
	entries.SubName = ""

	if c.sanitizerProperties.CfiEnabled {
		entries.SubName += ".cfi"
	}

	if c.androidMkVendorSuffix {
		entries.SubName += vendorSuffix
	}

	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+42 −1
Original line number Diff line number Diff line
@@ -1013,17 +1013,25 @@ func TestVendorSnapshot(t *testing.T) {
			filepath.Join(sharedDir, "libvendor_available.so.json"))

		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
		// Also cfi variants are captured, except for prebuilts like toolchain_library
		staticVariant := fmt.Sprintf("android_vendor.VER_%s_%s_static", archType, archVariant)
		staticCfiVariant := fmt.Sprintf("android_vendor.VER_%s_%s_static_cfi", archType, archVariant)
		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.cfi.a", staticDir, staticCfiVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.a", staticDir, staticVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.cfi.a", staticDir, staticCfiVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.a", staticDir, staticVariant)
		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.cfi.a", staticDir, staticCfiVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(staticDir, "libb.a.json"),
			filepath.Join(staticDir, "libvndk.a.json"),
			filepath.Join(staticDir, "libvndk.cfi.a.json"),
			filepath.Join(staticDir, "libvendor.a.json"),
			filepath.Join(staticDir, "libvendor_available.a.json"))
			filepath.Join(staticDir, "libvendor.cfi.a.json"),
			filepath.Join(staticDir, "libvendor_available.a.json"),
			filepath.Join(staticDir, "libvendor_available.cfi.a.json"))

		// For binary executables, all vendor:true and vendor_available modules are captured.
		if archType == "arm64" {
@@ -1055,6 +1063,39 @@ func TestVendorSnapshot(t *testing.T) {
	}
}

func TestVendorSnapshotSanitizer(t *testing.T) {
	bp := `
	vendor_snapshot_static {
		name: "libsnapshot",
		vendor: true,
		target_arch: "arm64",
		version: "BOARD",
		arch: {
			arm64: {
				src: "libsnapshot.a",
				cfi: {
					src: "libsnapshot.cfi.a",
				}
			},
		},
	}
`
	config := TestConfig(buildDir, android.Android, nil, bp, nil)
	config.TestProductVariables.DeviceVndkVersion = StringPtr("BOARD")
	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
	ctx := testCcWithConfig(t, config)

	// Check non-cfi and cfi variant.
	staticVariant := "android_vendor.BOARD_arm64_armv8-a_static"
	staticCfiVariant := "android_vendor.BOARD_arm64_armv8-a_static_cfi"

	staticModule := ctx.ModuleForTests("libsnapshot.vendor_static.BOARD.arm64", staticVariant).Module().(*Module)
	assertString(t, staticModule.outputFile.Path().Base(), "libsnapshot.a")

	staticCfiModule := ctx.ModuleForTests("libsnapshot.vendor_static.BOARD.arm64", staticCfiVariant).Module().(*Module)
	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
}

func TestDoubleLoadableDepError(t *testing.T) {
	// Check whether an error is emitted when a LLNDK depends on a non-double_loadable VNDK lib.
	testCcError(t, "module \".*\" variant \".*\": link.* \".*\" which is not LL-NDK, VNDK-SP, .*double_loadable", `
+88 −32
Original line number Diff line number Diff line
@@ -309,14 +309,14 @@ func (sanitize *sanitize) begin(ctx BaseModuleContext) {

	// Is CFI actually enabled?
	if !ctx.Config().EnableCFI() {
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Cfi = boolPtr(false)
		s.Diag.Cfi = boolPtr(false)
	}

	// Also disable CFI for arm32 until b/35157333 is fixed.
	if ctx.Arch().ArchType == android.Arm {
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Cfi = boolPtr(false)
		s.Diag.Cfi = boolPtr(false)
	}

	// HWASan requires AArch64 hardware feature (top-byte-ignore).
@@ -331,14 +331,14 @@ func (sanitize *sanitize) begin(ctx BaseModuleContext) {

	// Also disable CFI if ASAN is enabled.
	if Bool(s.Address) || Bool(s.Hwaddress) {
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Cfi = boolPtr(false)
		s.Diag.Cfi = boolPtr(false)
	}

	// Disable sanitizers that depend on the UBSan runtime for windows/darwin builds.
	if !ctx.Os().Linux() {
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Cfi = boolPtr(false)
		s.Diag.Cfi = boolPtr(false)
		s.Misc_undefined = nil
		s.Undefined = nil
		s.All_undefined = nil
@@ -347,14 +347,15 @@ func (sanitize *sanitize) begin(ctx BaseModuleContext) {

	// Also disable CFI for VNDK variants of components
	if ctx.isVndk() && ctx.useVndk() {
		if ctx.static() {
			// Cfi variant for static vndk should be captured as vendor snapshot,
			// so don't strictly disable Cfi.
			s.Cfi = nil
			s.Diag.Cfi = nil
		} else {
			s.Cfi = boolPtr(false)
			s.Diag.Cfi = boolPtr(false)
		}

	// Also disable CFI if building against snapshot.
	vndkVersion := ctx.DeviceConfig().VndkVersion()
	if ctx.useVndk() && vndkVersion != "current" && vndkVersion != "" {
		s.Cfi = nil
	}

	// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
@@ -399,7 +400,7 @@ func (sanitize *sanitize) begin(ctx BaseModuleContext) {
	// TODO(b/131771163): CFI transiently depends on LTO, and thus Fuzzer is
	// mutually incompatible.
	if Bool(s.Fuzzer) {
		s.Cfi = nil
		s.Cfi = boolPtr(false)
	}
}

@@ -722,10 +723,46 @@ func isSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
	}
}

// Determines if the current module is a static library going to be captured
// as vendor snapshot. Such modules must create both cfi and non-cfi variants,
// except for ones which explicitly disable cfi.
func needsCfiForVendorSnapshot(mctx android.TopDownMutatorContext) bool {
	if isVendorProprietaryPath(mctx.ModuleDir()) {
		return false
	}

	c := mctx.Module().(*Module)

	if !c.inVendor() {
		return false
	}

	if !c.static() {
		return false
	}

	if c.Prebuilt() != nil {
		return false
	}

	return c.sanitize != nil &&
		!Bool(c.sanitize.Properties.Sanitize.Never) &&
		!c.sanitize.isSanitizerExplicitlyDisabled(cfi)
}

// Propagate sanitizer requirements down from binaries
func sanitizerDepsMutator(t sanitizerType) func(android.TopDownMutatorContext) {
	return func(mctx android.TopDownMutatorContext) {
		if c, ok := mctx.Module().(*Module); ok && c.sanitize.isSanitizerEnabled(t) {
		if c, ok := mctx.Module().(*Module); ok {
			enabled := c.sanitize.isSanitizerEnabled(t)
			if t == cfi && needsCfiForVendorSnapshot(mctx) {
				// We shouldn't change the result of isSanitizerEnabled(cfi) to correctly
				// determine defaultVariation in sanitizerMutator below.
				// Instead, just mark SanitizeDep to forcefully create cfi variant.
				enabled = true
				c.sanitize.Properties.SanitizeDep = true
			}
			if enabled {
				mctx.WalkDeps(func(child, parent android.Module) bool {
					if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
						return false
@@ -743,6 +780,7 @@ func sanitizerDepsMutator(t sanitizerType) func(android.TopDownMutatorContext) {
					}
					return true
				})
			}
		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
			// If an APEX module includes a lib which is enabled for a sanitizer T, then
			// the APEX module is also enabled for the same sanitizer type.
@@ -1076,6 +1114,24 @@ func sanitizerMutator(t sanitizerType) func(android.BottomUpMutatorContext) {
			// APEX modules fall here
			sanitizeable.AddSanitizerDependencies(mctx, t.name())
			mctx.CreateVariations(t.variationName())
		} else if c, ok := mctx.Module().(*Module); ok {
			// Check if it's a snapshot module supporting sanitizer
			if s, ok := c.linker.(snapshotSanitizer); ok && s.isSanitizerEnabled(t) {
				// Set default variation as above.
				defaultVariation := t.variationName()
				mctx.SetDefaultDependencyVariation(&defaultVariation)
				modules := mctx.CreateVariations("", t.variationName())
				modules[0].(*Module).linker.(snapshotSanitizer).setSanitizerVariation(t, false)
				modules[1].(*Module).linker.(snapshotSanitizer).setSanitizerVariation(t, true)

				// Export the static lib name to make
				if c.static() && c.ExportedToMake() {
					if t == cfi {
						// use BaseModuleName which is the name for Make.
						cfiStaticLibs(mctx.Config()).add(c, c.BaseModuleName())
					}
				}
			}
		}
	}
}
+1 −0
Original line number Diff line number Diff line
@@ -567,6 +567,7 @@ func CreateTestContext() *android.TestContext {
	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
	ctx.RegisterModuleType("vndk_libraries_txt", VndkLibrariesTxtFactory)
	ctx.RegisterModuleType("vendor_snapshot_static", VendorSnapshotStaticFactory)
	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
	android.RegisterPrebuiltMutators(ctx)
	RegisterRequiredBuildComponentsForTest(ctx)
+68 −9
Original line number Diff line number Diff line
@@ -140,13 +140,15 @@ type vendorSnapshotLibraryProperties struct {
	// Prebuilt file for each arch.
	Src *string `android:"arch_variant"`

	// list of directories that will be added to the include path (using -I).
	Export_include_dirs []string `android:"arch_variant"`

	// list of directories that will be added to the system path (using -isystem).
	Export_system_include_dirs []string `android:"arch_variant"`

	// list of flags that will be used for any module that links against this module.
	Export_flags []string `android:"arch_variant"`

	// Check the prebuilt ELF files (e.g. DT_SONAME, DT_NEEDED, resolution of undefined symbols,
	// etc).
	Check_elf_files *bool

	// Whether this prebuilt needs to depend on sanitize ubsan runtime or not.
	Sanitize_ubsan_dep *bool `android:"arch_variant"`

@@ -154,10 +156,21 @@ type vendorSnapshotLibraryProperties struct {
	Sanitize_minimal_dep *bool `android:"arch_variant"`
}

type snapshotSanitizer interface {
	isSanitizerEnabled(t sanitizerType) bool
	setSanitizerVariation(t sanitizerType, enabled bool)
}

type vendorSnapshotLibraryDecorator struct {
	vendorSnapshotModuleBase
	*libraryDecorator
	properties          vendorSnapshotLibraryProperties
	sanitizerProperties struct {
		CfiEnabled bool `blueprint:"mutated"`

		// Library flags for cfi variant.
		Cfi vendorSnapshotLibraryProperties `android:"arch_variant"`
	}
	androidMkVendorSuffix bool
}

@@ -186,11 +199,16 @@ func (p *vendorSnapshotLibraryDecorator) link(ctx ModuleContext,
		return p.libraryDecorator.link(ctx, flags, deps, objs)
	}

	if p.sanitizerProperties.CfiEnabled {
		p.properties = p.sanitizerProperties.Cfi
	}

	if !p.matchesWithDevice(ctx.DeviceConfig()) {
		return nil
	}

	p.libraryDecorator.exportIncludes(ctx)
	p.libraryDecorator.reexportDirs(android.PathsForModuleSrc(ctx, p.properties.Export_include_dirs)...)
	p.libraryDecorator.reexportSystemDirs(android.PathsForModuleSrc(ctx, p.properties.Export_system_include_dirs)...)
	p.libraryDecorator.reexportFlags(p.properties.Export_flags...)

	in := android.PathForModuleSrc(ctx, *p.properties.Src)
@@ -220,6 +238,27 @@ func (p *vendorSnapshotLibraryDecorator) nativeCoverage() bool {
	return false
}

func (p *vendorSnapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
	switch t {
	case cfi:
		return p.sanitizerProperties.Cfi.Src != nil
	default:
		return false
	}
}

func (p *vendorSnapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
	if !enabled {
		return
	}
	switch t {
	case cfi:
		p.sanitizerProperties.CfiEnabled = true
	default:
		return
	}
}

func vendorSnapshotLibrary(suffix string) (*Module, *vendorSnapshotLibraryDecorator) {
	module, library := NewLibrary(android.DeviceSupported)

@@ -244,7 +283,10 @@ func vendorSnapshotLibrary(suffix string) (*Module, *vendorSnapshotLibraryDecora
	module.installer = prebuilt

	prebuilt.init(module, suffix)
	module.AddProperties(&prebuilt.properties)
	module.AddProperties(
		&prebuilt.properties,
		&prebuilt.sanitizerProperties,
	)

	return module, prebuilt
}
@@ -267,6 +309,8 @@ func VendorSnapshotHeaderFactory() android.Module {
	return module.Init()
}

var _ snapshotSanitizer = (*vendorSnapshotLibraryDecorator)(nil)

type vendorSnapshotBinaryProperties struct {
	// Prebuilt file for each arch.
	Src *string `android:"arch_variant"`
@@ -504,13 +548,17 @@ func isVendorSnapshotModule(m *Module, moduleDir string) bool {
	if l, ok := m.linker.(snapshotLibraryInterface); ok {
		// TODO(b/65377115): add full support for sanitizer
		if m.sanitize != nil {
			// cfi, scs and hwasan export both sanitized and unsanitized variants for static and header
			// scs and hwasan export both sanitized and unsanitized variants for static and header
			// Always use unsanitized variants of them.
			for _, t := range []sanitizerType{cfi, scs, hwasan} {
			for _, t := range []sanitizerType{scs, hwasan} {
				if !l.shared() && m.sanitize.isSanitizerEnabled(t) {
					return false
				}
			}
			// cfi also exports both variants. But for static, we capture both.
			if !l.static() && !l.shared() && m.sanitize.isSanitizerEnabled(cfi) {
				return false
			}
		}
		if l.static() {
			return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
@@ -604,6 +652,7 @@ func (c *vendorSnapshotSingleton) GenerateBuildActions(ctx android.SingletonCont
			ExportedDirs       []string `json:",omitempty"`
			ExportedSystemDirs []string `json:",omitempty"`
			ExportedFlags      []string `json:",omitempty"`
			Sanitize           string   `json:",omitempty"`
			SanitizeMinimalDep bool     `json:",omitempty"`
			SanitizeUbsanDep   bool     `json:",omitempty"`

@@ -653,6 +702,7 @@ func (c *vendorSnapshotSingleton) GenerateBuildActions(ctx android.SingletonCont
		var propOut string

		if l, ok := m.linker.(snapshotLibraryInterface); ok {

			// library flags
			prop.ExportedFlags = l.exportedFlags()
			for _, dir := range l.exportedDirs() {
@@ -685,6 +735,15 @@ func (c *vendorSnapshotSingleton) GenerateBuildActions(ctx android.SingletonCont
			if libType != "header" {
				libPath := m.outputFile.Path()
				stem = libPath.Base()
				if l.static() && m.sanitize != nil && m.sanitize.isSanitizerEnabled(cfi) {
					// both cfi and non-cfi variant for static libraries can exist.
					// attach .cfi to distinguish between cfi and non-cfi.
					// e.g. libbase.a -> libbase.cfi.a
					ext := filepath.Ext(stem)
					stem = strings.TrimSuffix(stem, ext) + ".cfi" + ext
					prop.Sanitize = "cfi"
					prop.ModuleName += ".cfi"
				}
				snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, libType, stem)
				ret = append(ret, copyFile(ctx, libPath, snapshotLibOut))
			} else {