From 59f13afe9554b29169673a089061226337d9e56f Mon Sep 17 00:00:00 2001 From: cloudphone Date: Fri, 24 Jan 2025 09:12:10 +0800 Subject: [PATCH] add modified files --- aosp/build/make/core/clang/config.mk | 58 + aosp/build/make/core/main.mk | 2275 ++++++++++ aosp/build/make/core/product_config.mk | 666 +++ .../make/core/tasks/tools/compatibility.mk | 175 + aosp/build/make/target/product/base_vendor.mk | 107 + .../make/target/product/handheld_product.mk | 38 + .../make/target/product/updatable_apex.mk | 26 + aosp/build/soong/ui/build/paths/config.go | 148 + .../device/generic/goldfish-opengl/Android.mk | 156 + aosp/device/generic/goldfish/manifest.xml | 69 + .../generic/goldfish/product/generic.mk | 357 ++ aosp/external/crosvm/rutabaga_gfx/Android.bp | 109 + aosp/external/minigbm/Android.bp | 238 + aosp/external/minijail/libminijail.c | 3975 +++++++++++++++++ .../libselinux/include/selinux/selinux.h | 754 ++++ .../selinux/libselinux/src/checkAccess.c | 112 + .../selinux/libselinux/src/compute_create.c | 162 + .../selinux/libselinux/src/getenforce.c | 40 + .../selinux/libselinux/src/getfilecon.c | 72 + .../selinux/libselinux/src/getpeercon.c | 69 + aosp/external/selinux/libselinux/src/init.c | 165 + aosp/external/selinux/libselinux/src/label.c | 391 ++ .../selinux/libselinux/src/lgetfilecon.c | 71 + .../selinux/libselinux/src/lsetfilecon.c | 43 + .../selinux/libselinux/src/procattr.c | 323 ++ .../selinux/libselinux/src/sestatus.c | 393 ++ .../selinux/libselinux/src/setfilecon.c | 43 + .../selinux/libselinux/src/setrans_client.c | 446 ++ aosp/external/virglrenderer/Android.bp | 176 + aosp/system/bpf/bpfloader/BpfLoader.cpp | 223 + aosp/system/core/fs_mgr/Android.bp | 242 + aosp/system/core/init/Android.bp | 643 +++ aosp/system/core/init/devices.cpp | 628 +++ aosp/system/core/init/devices.h | 157 + aosp/system/core/init/first_stage_init.cpp | 564 +++ aosp/system/core/init/init.cpp | 1158 +++++ aosp/system/core/init/mount_namespace.cpp | 243 + aosp/system/core/init/property_service.cpp | 1551 +++++++ aosp/system/core/init/selinux.cpp | 820 ++++ aosp/system/core/init/service.cpp | 1041 +++++ aosp/system/core/init/subcontext.cpp | 390 ++ aosp/system/core/init/util.cpp | 762 ++++ .../core/libprocessgroup/processgroup.cpp | 762 ++++ .../setup/cgroup_map_write.cpp | 482 ++ aosp/system/core/libsuspend/autosuspend.c | 120 + aosp/system/core/rootdir/init.rc | 1360 ++++++ .../libbase/include/android-base/macros.h | 155 + aosp/system/libhwbinder/Binder.cpp | 268 ++ aosp/system/libhwbinder/ProcessState.cpp | 442 ++ .../logging/liblog/include/android/log.h | 385 ++ aosp/system/logging/logd/main.cpp | 311 ++ .../netd/server/BandwidthController.cpp | 788 ++++ aosp/system/netd/server/Controllers.cpp | 349 ++ aosp/system/netd/server/FwmarkServer.cpp | 322 ++ .../netd/server/InterfaceController.cpp | 536 +++ .../netd/server/IptablesRestoreController.cpp | 360 ++ aosp/system/netd/server/NetlinkManager.cpp | 218 + aosp/system/netd/server/RouteController.cpp | 1555 +++++++ aosp/system/vold/Utils.cpp | 1812 ++++++++ aosp/system/vold/model/EmulatedVolume.cpp | 530 +++ .../common/android/scripts/hook_cpuinfo.sh | 10 - aosp/vendor/common/products/product_common.mk | 5 +- 62 files changed, 30836 insertions(+), 13 deletions(-) create mode 100644 aosp/build/make/core/clang/config.mk create mode 100644 aosp/build/make/core/main.mk create mode 100644 aosp/build/make/core/product_config.mk create mode 100644 aosp/build/make/core/tasks/tools/compatibility.mk create mode 100644 aosp/build/make/target/product/base_vendor.mk create mode 100644 aosp/build/make/target/product/handheld_product.mk create mode 100644 aosp/build/make/target/product/updatable_apex.mk create mode 100644 aosp/build/soong/ui/build/paths/config.go create mode 100644 aosp/device/generic/goldfish-opengl/Android.mk create mode 100644 aosp/device/generic/goldfish/manifest.xml create mode 100644 aosp/device/generic/goldfish/product/generic.mk create mode 100644 aosp/external/crosvm/rutabaga_gfx/Android.bp create mode 100644 aosp/external/minigbm/Android.bp create mode 100644 aosp/external/minijail/libminijail.c create mode 100644 aosp/external/selinux/libselinux/include/selinux/selinux.h create mode 100644 aosp/external/selinux/libselinux/src/checkAccess.c create mode 100644 aosp/external/selinux/libselinux/src/compute_create.c create mode 100644 aosp/external/selinux/libselinux/src/getenforce.c create mode 100644 aosp/external/selinux/libselinux/src/getfilecon.c create mode 100644 aosp/external/selinux/libselinux/src/getpeercon.c create mode 100644 aosp/external/selinux/libselinux/src/init.c create mode 100644 aosp/external/selinux/libselinux/src/label.c create mode 100644 aosp/external/selinux/libselinux/src/lgetfilecon.c create mode 100644 aosp/external/selinux/libselinux/src/lsetfilecon.c create mode 100644 aosp/external/selinux/libselinux/src/procattr.c create mode 100644 aosp/external/selinux/libselinux/src/sestatus.c create mode 100644 aosp/external/selinux/libselinux/src/setfilecon.c create mode 100644 aosp/external/selinux/libselinux/src/setrans_client.c create mode 100644 aosp/external/virglrenderer/Android.bp create mode 100644 aosp/system/bpf/bpfloader/BpfLoader.cpp create mode 100644 aosp/system/core/fs_mgr/Android.bp create mode 100644 aosp/system/core/init/Android.bp create mode 100644 aosp/system/core/init/devices.cpp create mode 100644 aosp/system/core/init/devices.h create mode 100644 aosp/system/core/init/first_stage_init.cpp create mode 100644 aosp/system/core/init/init.cpp create mode 100644 aosp/system/core/init/mount_namespace.cpp create mode 100644 aosp/system/core/init/property_service.cpp create mode 100644 aosp/system/core/init/selinux.cpp create mode 100644 aosp/system/core/init/service.cpp create mode 100644 aosp/system/core/init/subcontext.cpp create mode 100644 aosp/system/core/init/util.cpp create mode 100644 aosp/system/core/libprocessgroup/processgroup.cpp create mode 100644 aosp/system/core/libprocessgroup/setup/cgroup_map_write.cpp create mode 100644 aosp/system/core/libsuspend/autosuspend.c create mode 100644 aosp/system/core/rootdir/init.rc create mode 100644 aosp/system/libbase/include/android-base/macros.h create mode 100644 aosp/system/libhwbinder/Binder.cpp create mode 100644 aosp/system/libhwbinder/ProcessState.cpp create mode 100644 aosp/system/logging/liblog/include/android/log.h create mode 100644 aosp/system/logging/logd/main.cpp create mode 100644 aosp/system/netd/server/BandwidthController.cpp create mode 100644 aosp/system/netd/server/Controllers.cpp create mode 100644 aosp/system/netd/server/FwmarkServer.cpp create mode 100644 aosp/system/netd/server/InterfaceController.cpp create mode 100644 aosp/system/netd/server/IptablesRestoreController.cpp create mode 100644 aosp/system/netd/server/NetlinkManager.cpp create mode 100644 aosp/system/netd/server/RouteController.cpp create mode 100644 aosp/system/vold/Utils.cpp create mode 100644 aosp/system/vold/model/EmulatedVolume.cpp delete mode 100644 aosp/vendor/common/android/scripts/hook_cpuinfo.sh diff --git a/aosp/build/make/core/clang/config.mk b/aosp/build/make/core/clang/config.mk new file mode 100644 index 000000000..6aac34dc5 --- /dev/null +++ b/aosp/build/make/core/clang/config.mk @@ -0,0 +1,58 @@ +## Clang configurations. + +LLVM_READOBJ := $(LLVM_PREBUILTS_BASE)/$(BUILD_OS)-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-readobj + +LLVM_RTLIB_PATH := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib/clang/$(LLVM_RELEASE_VERSION)/lib/linux/ +LLVM70_TBLGEN := $(BUILD_OUT_EXECUTABLES)/llvm70-tblgen$(BUILD_EXECUTABLE_SUFFIX) + +define convert-to-clang-flags +$(strip $(filter-out $(CLANG_CONFIG_UNKNOWN_CFLAGS),$(1))) +endef + +CLANG_DEFAULT_UB_CHECKS := \ + bool \ + integer-divide-by-zero \ + return \ + returns-nonnull-attribute \ + shift-exponent \ + unreachable \ + vla-bound \ + +# TODO(danalbert): The following checks currently have compiler performance +# issues. +# CLANG_DEFAULT_UB_CHECKS += alignment +# CLANG_DEFAULT_UB_CHECKS += bounds +# CLANG_DEFAULT_UB_CHECKS += enum +# CLANG_DEFAULT_UB_CHECKS += float-cast-overflow +# CLANG_DEFAULT_UB_CHECKS += float-divide-by-zero +# CLANG_DEFAULT_UB_CHECKS += nonnull-attribute +# CLANG_DEFAULT_UB_CHECKS += null +# CLANG_DEFAULT_UB_CHECKS += shift-base +# CLANG_DEFAULT_UB_CHECKS += signed-integer-overflow + +# TODO(danalbert): Fix UB in libc++'s __tree so we can turn this on. +# https://llvm.org/PR19302 +# http://reviews.llvm.org/D6974 +# CLANG_DEFAULT_UB_CHECKS += object-size + +# HOST config +clang_2nd_arch_prefix := +include $(BUILD_SYSTEM)/clang/HOST_$(HOST_ARCH).mk + +# HOST_2ND_ARCH config +ifdef HOST_2ND_ARCH +clang_2nd_arch_prefix := $(HOST_2ND_ARCH_VAR_PREFIX) +include $(BUILD_SYSTEM)/clang/HOST_$(HOST_2ND_ARCH).mk +endif + +# TARGET config +clang_2nd_arch_prefix := +include $(BUILD_SYSTEM)/clang/TARGET_$(TARGET_ARCH).mk + +# TARGET_2ND_ARCH config +ifdef TARGET_2ND_ARCH +clang_2nd_arch_prefix := $(TARGET_2ND_ARCH_VAR_PREFIX) +include $(BUILD_SYSTEM)/clang/TARGET_$(TARGET_2ND_ARCH).mk +endif + +include $(BUILD_SYSTEM)/clang/tidy.mk diff --git a/aosp/build/make/core/main.mk b/aosp/build/make/core/main.mk new file mode 100644 index 000000000..7f255eec5 --- /dev/null +++ b/aosp/build/make/core/main.mk @@ -0,0 +1,2275 @@ +ifndef KATI +$(warning Calling make directly is no longer supported.) +$(warning Either use 'envsetup.sh; m' or 'build/soong/soong_ui.bash --make-mode') +$(error done) +endif + +$(info [1/1] initializing legacy Make module parser ...) + +# Absolute path of the present working direcotry. +# This overrides the shell variable $PWD, which does not necessarily points to +# the top of the source tree, for example when "make -C" is used in m/mm/mmm. +PWD := $(shell pwd) + +# This is the default target. It must be the first declared target. +.PHONY: droid +DEFAULT_GOAL := droid +$(DEFAULT_GOAL): droid_targets + +.PHONY: droid_targets +droid_targets: + +# Set up various standard variables based on configuration +# and host information. +include build/make/core/config.mk + +ifneq ($(filter $(dont_bother_goals), $(MAKECMDGOALS)),) +dont_bother := true +endif + +.KATI_READONLY := SOONG_CONFIG_NAMESPACES +.KATI_READONLY := $(foreach n,$(SOONG_CONFIG_NAMESPACES),SOONG_CONFIG_$(n)) +.KATI_READONLY := $(foreach n,$(SOONG_CONFIG_NAMESPACES),$(foreach k,$(SOONG_CONFIG_$(n)),SOONG_CONFIG_$(n)_$(k))) + +include $(SOONG_MAKEVARS_MK) + +YACC :=$= $(BISON) -d + +include $(BUILD_SYSTEM)/clang/config.mk + +# Write the build number to a file so it can be read back in +# without changing the command line every time. Avoids rebuilds +# when using ninja. +BUILD_NUMBER_FILE := $(SOONG_OUT_DIR)/build_number.txt +$(KATI_obsolete_var BUILD_NUMBER,See https://android.googlesource.com/platform/build/+/master/Changes.md#BUILD_NUMBER) +BUILD_HOSTNAME_FILE := $(SOONG_OUT_DIR)/build_hostname.txt +$(KATI_obsolete_var BUILD_HOSTNAME,Use BUILD_HOSTNAME_FROM_FILE instead) +$(KATI_obsolete_var FILE_NAME_TAG,https://android.googlesource.com/platform/build/+/master/Changes.md#FILE_NAME_TAG) + +$(BUILD_NUMBER_FILE): + # empty rule to prevent dangling rule error for a file that is written by soong_ui +$(BUILD_HOSTNAME_FILE): + # empty rule to prevent dangling rule error for a file that is written by soong_ui + +.KATI_RESTAT: $(BUILD_NUMBER_FILE) +.KATI_RESTAT: $(BUILD_HOSTNAME_FILE) + +DATE_FROM_FILE := date -d @$(BUILD_DATETIME_FROM_FILE) +.KATI_READONLY := DATE_FROM_FILE + + +# Make an empty directory, which can be used to make empty jars +EMPTY_DIRECTORY := $(OUT_DIR)/empty +$(shell mkdir -p $(EMPTY_DIRECTORY) && rm -rf $(EMPTY_DIRECTORY)/*) + +# CTS-specific config. +-include cts/build/config.mk +# device-tests-specific-config. +-include tools/tradefederation/build/suites/device-tests/config.mk +# general-tests-specific-config. +-include tools/tradefederation/build/suites/general-tests/config.mk +# STS-specific config. +-include test/sts/tools/sts-tradefed/build/config.mk +# CTS-Instant-specific config +-include test/suite_harness/tools/cts-instant-tradefed/build/config.mk +# MTS-specific config. +-include test/mts/tools/build/config.mk +# VTS-Core-specific config. +-include test/vts/tools/vts-core-tradefed/build/config.mk +# CSUITE-specific config. +-include test/app_compat/csuite/tools/build/config.mk +# CATBox-specific config. +-include test/catbox/tools/build/config.mk +# CTS-Root-specific config. +-include test/cts-root/tools/build/config.mk +# WVTS-specific config. +-include test/wvts/tools/build/config.mk + + +# Clean rules +.PHONY: clean-dex-files +clean-dex-files: + $(hide) find $(OUT_DIR) -name "*.dex" | xargs rm -f + $(hide) for i in `find $(OUT_DIR) -name "*.jar" -o -name "*.apk"` ; do ((unzip -l $$i 2> /dev/null | \ + grep -q "\.dex$$" && rm -f $$i) || continue ) ; done + @echo "All dex files and archives containing dex files have been removed." + +# Include the google-specific config +-include vendor/google/build/config.mk + +# These are the modifier targets that don't do anything themselves, but +# change the behavior of the build. +# (must be defined before including definitions.make) +INTERNAL_MODIFIER_TARGETS := all + +# EMMA_INSTRUMENT_STATIC merges the static jacoco library to each +# jacoco-enabled module. +ifeq (true,$(EMMA_INSTRUMENT_STATIC)) +EMMA_INSTRUMENT := true +endif + +ifdef TARGET_ARCH_SUITE + # TODO(b/175577370): Enable this error. + # $(error TARGET_ARCH_SUITE is not supported in kati/make builds) +endif + +# ADDITIONAL__PROPERTIES are properties that are determined by the +# build system itself. Don't let it be defined from outside of the core build +# system like Android.mk or .mk files. +_additional_prop_var_names := \ + ADDITIONAL_SYSTEM_PROPERTIES \ + ADDITIONAL_VENDOR_PROPERTIES \ + ADDITIONAL_ODM_PROPERTIES \ + ADDITIONAL_PRODUCT_PROPERTIES + +$(foreach name, $(_additional_prop_var_names),\ + $(if $($(name)),\ + $(error $(name) must not set before here. $($(name)))\ + ,)\ + $(eval $(name) :=)\ +) +_additional_prop_var_names := + +$(KATI_obsolete_var ADDITIONAL_BUILD_PROPERTIES, Please use ADDITIONAL_SYSTEM_PROPERTIES) + +# +# ----------------------------------------------------------------- +# Add the product-defined properties to the build properties. +ifneq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true) + ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) +else + ifndef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE + ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) + endif +endif + + +# Bring in standard build system definitions. +include $(BUILD_SYSTEM)/definitions.mk + +ifneq ($(filter user userdebug eng,$(MAKECMDGOALS)),) +$(info ***************************************************************) +$(info ***************************************************************) +$(info Do not pass '$(filter user userdebug eng,$(MAKECMDGOALS))' on \ + the make command line.) +$(info Set TARGET_BUILD_VARIANT in buildspec.mk, or use lunch or) +$(info choosecombo.) +$(info ***************************************************************) +$(info ***************************************************************) +$(error stopping) +endif + +# These are the valid values of TARGET_BUILD_VARIANT. +INTERNAL_VALID_VARIANTS := user userdebug eng +ifneq ($(filter-out $(INTERNAL_VALID_VARIANTS),$(TARGET_BUILD_VARIANT)),) +$(info ***************************************************************) +$(info ***************************************************************) +$(info Invalid variant: $(TARGET_BUILD_VARIANT)) +$(info Valid values are: $(INTERNAL_VALID_VARIANTS)) +$(info ***************************************************************) +$(info ***************************************************************) +$(error stopping) +endif + +# ----------------------------------------------------------------- +# PDK builds are no longer supported, this is always platform +TARGET_BUILD_JAVA_SUPPORT_LEVEL :=$= platform + +# ----------------------------------------------------------------- + +ADDITIONAL_SYSTEM_PROPERTIES += ro.treble.enabled=${PRODUCT_FULL_TREBLE} + +$(KATI_obsolete_var PRODUCT_FULL_TREBLE,\ + Code should be written to work regardless of a device being Treble) + +# Set ro.llndk.api_level to show the maximum vendor API level that the LLNDK in +# the system partition supports. +ifdef RELEASE_BOARD_API_LEVEL +ADDITIONAL_SYSTEM_PROPERTIES += ro.llndk.api_level=$(RELEASE_BOARD_API_LEVEL) +endif + +# Sets ro.actionable_compatible_property.enabled to know on runtime whether the +# allowed list of actionable compatible properties is enabled or not. +ADDITIONAL_SYSTEM_PROPERTIES += ro.actionable_compatible_property.enabled=true + +# Add the system server compiler filter if they are specified for the product. +ifneq (,$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER)) +ADDITIONAL_PRODUCT_PROPERTIES += dalvik.vm.systemservercompilerfilter=$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER) +endif + +# Add the 16K developer option if it is defined for the product. +ifeq ($(PRODUCT_16K_DEVELOPER_OPTION),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=true +else +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=false +endif + +# Enable core platform API violation warnings on userdebug and eng builds. +ifneq ($(TARGET_BUILD_VARIANT),user) +ADDITIONAL_SYSTEM_PROPERTIES += persist.debug.dalvik.vm.core_platform_api_policy=just-warn +endif + +# Define ro.sanitize. properties for all global sanitizers. +ADDITIONAL_SYSTEM_PROPERTIES += $(foreach s,$(SANITIZE_TARGET),ro.sanitize.$(s)=true) + +# Sets the default value of ro.postinstall.fstab.prefix to /system. +# Device board config should override the value to /product when needed by: +# +# PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product +# +# It then uses ${ro.postinstall.fstab.prefix}/etc/fstab.postinstall to +# mount system_other partition. +ADDITIONAL_SYSTEM_PROPERTIES += ro.postinstall.fstab.prefix=/system + +# ----------------------------------------------------------------- +# ADDITIONAL_VENDOR_PROPERTIES will be installed in vendor/build.prop if +# property_overrides_split_enabled is true. Otherwise it will be installed in +# /system/build.prop +ifeq ($(KEEP_VNDK),true) +ifdef BOARD_VNDK_VERSION + ifeq ($(BOARD_VNDK_VERSION),current) + ADDITIONAL_VENDOR_PROPERTIES := ro.vndk.version=$(PLATFORM_VNDK_VERSION) + else + ADDITIONAL_VENDOR_PROPERTIES := ro.vndk.version=$(BOARD_VNDK_VERSION) + endif +endif +endif + +# Add cpu properties for bionic and ART. +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.arch=$(TARGET_ARCH) +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.cpu_variant=$(TARGET_CPU_VARIANT_RUNTIME) +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_arch=$(TARGET_2ND_ARCH) +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_cpu_variant=$(TARGET_2ND_CPU_VARIANT_RUNTIME) + +ADDITIONAL_VENDOR_PROPERTIES += persist.sys.dalvik.vm.lib.2=libart.so +ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).variant=$(DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) +ifneq ($(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).features=$(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) +endif + +ifdef TARGET_2ND_ARCH + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).variant=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) + ifneq ($($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).features=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) + endif +endif + +# Although these variables are prefixed with TARGET_RECOVERY_, they are also needed under charger +# mode (via libminui). +ifdef TARGET_RECOVERY_DEFAULT_ROTATION +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.default_rotation=$(TARGET_RECOVERY_DEFAULT_ROTATION) +endif +ifdef TARGET_RECOVERY_OVERSCAN_PERCENT +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.overscan_percent=$(TARGET_RECOVERY_OVERSCAN_PERCENT) +endif +ifdef TARGET_RECOVERY_PIXEL_FORMAT +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.pixel_format=$(TARGET_RECOVERY_PIXEL_FORMAT) +endif + +ifdef PRODUCT_USE_DYNAMIC_PARTITIONS +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.boot.dynamic_partitions=$(PRODUCT_USE_DYNAMIC_PARTITIONS) +endif + +ifdef PRODUCT_RETROFIT_DYNAMIC_PARTITIONS +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.boot.dynamic_partitions_retrofit=$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS) +endif + +ifdef PRODUCT_SHIPPING_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.product.first_api_level=$(PRODUCT_SHIPPING_API_LEVEL) +endif + +ifdef PRODUCT_SHIPPING_VENDOR_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.api_level=$(PRODUCT_SHIPPING_VENDOR_API_LEVEL) +endif + +ifneq ($(TARGET_BUILD_VARIANT),user) + ifdef PRODUCT_SET_DEBUGFS_RESTRICTIONS + ADDITIONAL_VENDOR_PROPERTIES += \ + ro.product.debugfs_restrictions.enabled=$(PRODUCT_SET_DEBUGFS_RESTRICTIONS) + endif +endif + +# Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level. +# This must not be defined for the non-GRF devices. +# The values of the GRF properties will be verified by post_process_props.py +ifdef BOARD_SHIPPING_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.first_api_level=$(BOARD_SHIPPING_API_LEVEL) +endif + +# Build system set BOARD_API_LEVEL to show the api level of the vendor API surface. +# This must not be altered outside of build system. +ifdef BOARD_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.api_level=$(BOARD_API_LEVEL) +endif +# RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen. +ifdef RELEASE_BOARD_API_LEVEL_FROZEN +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.api_frozen=$(RELEASE_BOARD_API_LEVEL_FROZEN) +endif + +# Set build prop. This prop is read by ota_from_target_files when generating OTA, +# to decide if VABC should be disabled. +ifeq ($(BOARD_DONT_USE_VABC_OTA),true) +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.dont_use_vabc=true +endif + +# Set the flag in vendor. So VTS would know if the new fingerprint format is in use when +# the system images are replaced by GSI. +ifeq ($(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT),true) +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.fingerprint_has_digest=1 +endif + +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.security_patch=$(VENDOR_SECURITY_PATCH) \ + ro.product.board=$(TARGET_BOOTLOADER_BOARD_NAME) \ + ro.board.platform=$(TARGET_BOARD_PLATFORM) \ + ro.hwui.use_vulkan=$(TARGET_USES_VULKAN) + +ifdef TARGET_SCREEN_DENSITY +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.sf.lcd_density=$(TARGET_SCREEN_DENSITY) +endif + +ifdef AB_OTA_UPDATER +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.build.ab_update=$(AB_OTA_UPDATER) +endif + +# Set ro.product.vndk.version to PLATFORM_VNDK_VERSION only if +# KEEP_VNDK is true, PRODUCT_PRODUCT_VNDK_VERSION is current and +# PLATFORM_VNDK_VERSION is less than or equal to 35. +# ro.product.vndk.version must be removed for the other future builds. +ifeq ($(KEEP_VNDK)|$(PRODUCT_PRODUCT_VNDK_VERSION),true|current) +ifeq ($(call math_is_number,$(PLATFORM_VNDK_VERSION)),true) +ifeq ($(call math_lt_or_eq,$(PLATFORM_VNDK_VERSION),35),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.vndk.version=$(PLATFORM_VNDK_VERSION) +endif +endif +endif + +ADDITIONAL_PRODUCT_PROPERTIES += ro.build.characteristics=$(TARGET_AAPT_CHARACTERISTICS) + +ifeq ($(AB_OTA_UPDATER),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) +ADDITIONAL_VENDOR_PROPERTIES += ro.vendor.build.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) +endif + +# Set this property for VTS to skip large page size tests on unsupported devices. +ADDITIONAL_PRODUCT_PROPERTIES += \ + ro.product.cpu.pagesize.max=$(TARGET_MAX_PAGE_SIZE_SUPPORTED) + +# ----------------------------------------------------------------- +### +### In this section we set up the things that are different +### between the build variants +### + +is_sdk_build := + +ifneq ($(filter sdk sdk_addon,$(MAKECMDGOALS)),) +is_sdk_build := true +endif + +## user/userdebug ## + +user_variant := $(filter user userdebug,$(TARGET_BUILD_VARIANT)) +enable_target_debugging := true +tags_to_install := +ifneq (,$(user_variant)) + # Target is secure in user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=1 + ADDITIONAL_SYSTEM_PROPERTIES += security.perf_harden=1 + + ifeq ($(user_variant),user) + ADDITIONAL_SYSTEM_PROPERTIES += ro.adb.secure=0 + endif + + ifeq ($(user_variant),userdebug) + # Pick up some extra useful tools + tags_to_install += debug + else + # Disable debugging in plain user builds. + # enable_target_debugging := + endif + + # Disallow mock locations by default for user builds + ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=0 + +else # !user_variant + # Turn on checkjni for non-user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.kernel.android.checkjni=1 + # Set device insecure for non-user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=0 + # Allow mock locations by default for non user builds + ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=1 +endif # !user_variant + +ifeq (true,$(strip $(enable_target_debugging))) + # Target is more debuggable and adbd is on by default + ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=1 + # Enable Dalvik lock contention logging. + ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.lockprof.threshold=500 +else # !enable_target_debugging + # Target is less debuggable and adbd is off by default + ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=0 +endif # !enable_target_debugging + +## eng ## + +ifeq ($(TARGET_BUILD_VARIANT),eng) +tags_to_install := debug eng +ifneq ($(filter ro.setupwizard.mode=ENABLED, $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))),) + # Don't require the setup wizard on eng builds + ADDITIONAL_SYSTEM_PROPERTIES := $(filter-out ro.setupwizard.mode=%,\ + $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))) \ + ro.setupwizard.mode=OPTIONAL +endif +ifndef is_sdk_build + # To speedup startup of non-preopted builds, don't verify or compile the boot image. + ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.image-dex2oat-filter=extract +endif +# b/323566535 +ADDITIONAL_SYSTEM_PROPERTIES += init.svc_debug.no_fatal.zygote=true +endif + +## asan ## + +# Install some additional tools on ASAN builds IFF we are also installing debug tools +ifneq ($(filter address,$(SANITIZE_TARGET)),) +ifneq (,$(filter debug,$(tags_to_install))) + tags_to_install += asan +endif +endif + +## java coverage ## +# Install additional tools on java coverage builds +ifeq (true,$(EMMA_INSTRUMENT)) +ifneq (,$(filter debug,$(tags_to_install))) + tags_to_install += java_coverage +endif +endif + + +## sdk ## + +ifdef is_sdk_build + +# Detect if we want to build a repository for the SDK +sdk_repo_goal := $(strip $(filter sdk_repo,$(MAKECMDGOALS))) +MAKECMDGOALS := $(strip $(filter-out sdk_repo,$(MAKECMDGOALS))) + +ifneq ($(words $(sort $(filter-out $(INTERNAL_MODIFIER_TARGETS) checkbuild emulator_tests,$(MAKECMDGOALS)))),1) +$(error The 'sdk' target may not be specified with any other targets) +endif + +# TODO: this should be eng I think. Since the sdk is built from the eng +# variant. +tags_to_install := debug eng +ADDITIONAL_SYSTEM_PROPERTIES += xmpp.auto-presence=true +ADDITIONAL_SYSTEM_PROPERTIES += ro.config.nocheckin=yes +else # !sdk +endif + +BUILD_WITHOUT_PV := true + +ADDITIONAL_SYSTEM_PROPERTIES += net.bt.name=Android + +# This property is set by flashing debug boot image, so default to false. +ADDITIONAL_SYSTEM_PROPERTIES += ro.force.debuggable=0 + +# ------------------------------------------------------------ +# Define a function that, given a list of module tags, returns +# non-empty if that module should be installed in /system. + +# For most goals, anything not tagged with the "tests" tag should +# be installed in /system. +define should-install-to-system +$(if $(filter tests,$(1)),,true) +endef + +ifdef is_sdk_build +# For the sdk goal, anything with the "samples" tag should be +# installed in /data even if that module also has "eng"/"debug"/"user". +define should-install-to-system +$(if $(filter samples tests,$(1)),,true) +endef +endif + + +# If they only used the modifier goals (all, etc), we'll actually +# build the default target. +ifeq ($(filter-out $(INTERNAL_MODIFIER_TARGETS),$(MAKECMDGOALS)),) +.PHONY: $(INTERNAL_MODIFIER_TARGETS) +$(INTERNAL_MODIFIER_TARGETS): $(DEFAULT_GOAL) +endif + +# +# Typical build; include any Android.mk files we can find. +# + +include $(BUILD_SYSTEM)/art_config.mk + +# Bring in dex_preopt.mk +# This creates some modules so it needs to be included after +# should-install-to-system is defined (in order for base_rules.mk to function +# properly), but before readonly-final-product-vars is called. +include $(BUILD_SYSTEM)/dex_preopt.mk + +# Strip and readonly a few more variables so they won't be modified. +$(readonly-final-product-vars) +ADDITIONAL_SYSTEM_PROPERTIES := $(strip $(ADDITIONAL_SYSTEM_PROPERTIES)) +.KATI_READONLY := ADDITIONAL_SYSTEM_PROPERTIES +ADDITIONAL_PRODUCT_PROPERTIES := $(strip $(ADDITIONAL_PRODUCT_PROPERTIES)) +.KATI_READONLY := ADDITIONAL_PRODUCT_PROPERTIES + +ifneq ($(PRODUCT_ENFORCE_RRO_TARGETS),) +ENFORCE_RRO_SOURCES := +endif + +# Color-coded warnings including current module info +# $(1): message to print +define pretty-warning +$(shell $(call echo-warning,$(LOCAL_MODULE_MAKEFILE),$(LOCAL_MODULE): $(1))) +endef + +# Color-coded errors including current module info +# $(1): message to print +define pretty-error +$(shell $(call echo-error,$(LOCAL_MODULE_MAKEFILE),$(LOCAL_MODULE): $(1))) +$(error done) +endef + +subdir_makefiles_inc := . +FULL_BUILD := + +ifneq ($(dont_bother),true) +FULL_BUILD := true +# +# Include all of the makefiles in the system +# + +subdir_makefiles := $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk $(SOONG_ANDROID_MK) +# Android.mk files are only used on Linux builds, Mac only supports Android.bp +ifeq ($(HOST_OS),linux) + subdir_makefiles += $(file <$(OUT_DIR)/.module_paths/Android.mk.list) +endif +subdir_makefiles += $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk +subdir_makefiles_total := $(words int $(subdir_makefiles) post finish) +.KATI_READONLY := subdir_makefiles_total + +$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk))) + +# For an unbundled image, we can skip blueprint_tools because unbundled image +# aims to remove a large number framework projects from the manifest, the +# sources or dependencies for these tools may be missing from the tree. +ifeq (,$(TARGET_BUILD_UNBUNDLED_IMAGE)) +droid_targets : blueprint_tools +checkbuild: blueprint_tests +endif + +endif # dont_bother + +ifndef subdir_makefiles_total +subdir_makefiles_total := $(words init post finish) +endif + +$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing legacy Make module parsing ...) + +# ------------------------------------------------------------------- +# All module makefiles have been included at this point. +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# Use basic warning/error messages now that LOCAL_MODULE_MAKEFILE +# and LOCAL_MODULE aren't useful anymore. +# ------------------------------------------------------------------- +define pretty-warning +$(warning $(1)) +endef + +define pretty-error +$(error $(1)) +endef + +# ------------------------------------------------------------------- +# Enforce to generate all RRO packages for modules having resource +# overlays. +# ------------------------------------------------------------------- +ifneq ($(PRODUCT_ENFORCE_RRO_TARGETS),) +$(call generate_all_enforce_rro_packages) +endif + +# ------------------------------------------------------------------- +# Sort ALL_MODULES to remove duplicate entries. +# ------------------------------------------------------------------- +ALL_MODULES := $(sort $(ALL_MODULES)) +# Cannot set to readonly because Makefile extends ALL_MODULES +# .KATI_READONLY := ALL_MODULES + +# ------------------------------------------------------------------- +# Fix up CUSTOM_MODULES to refer to installed files rather than +# just bare module names. Leave unknown modules alone in case +# they're actually full paths to a particular file. +known_custom_modules := $(filter $(ALL_MODULES),$(CUSTOM_MODULES)) +unknown_custom_modules := $(filter-out $(ALL_MODULES),$(CUSTOM_MODULES)) +CUSTOM_MODULES := \ + $(call module-installed-files,$(known_custom_modules)) \ + $(unknown_custom_modules) + +# ------------------------------------------------------------------- +# Define dependencies for modules that require other modules. +# This can only happen now, after we've read in all module makefiles. +# +# TODO: deal with the fact that a bare module name isn't +# unambiguous enough. Maybe declare short targets like +# APPS:Quake or HOST:SHARED_LIBRARIES:libutils. +# BUG: the system image won't know to depend on modules that are +# brought in as requirements of other modules. +# +# Resolve the required module name to 32-bit or 64-bit variant. + +# Get a list of corresponding module names for the second arch, if they exist. +# $(1): TARGET, HOST or HOST_CROSS +# $(2): A list of module names +define get-modules-for-2nd-arch +$(strip \ + $(foreach m,$(2), \ + $(if $(filter true,$(ALL_MODULES.$(m)$($(1)_2ND_ARCH_MODULE_SUFFIX).FOR_2ND_ARCH)), \ + $(m)$($(1)_2ND_ARCH_MODULE_SUFFIX) \ + ) \ + ) \ +) +endef + +# Resolves module bitness for PRODUCT_PACKAGES and PRODUCT_HOST_PACKAGES. +# The returned list of module names can be used to access +# ALL_MODULES..<*> variables. +# Name resolution for PRODUCT_PACKAGES / PRODUCT_HOST_PACKAGES: +# foo:32 resolves to foo_32; +# foo:64 resolves to foo; +# foo resolves to both foo and foo_32 (if foo_32 is defined). +# +# Name resolution for HOST_CROSS modules: +# foo:32 resolves to foo; +# foo:64 resolves to foo_64; +# foo resolves to both foo and foo_64 (if foo_64 is defined). +# +# $(1): TARGET, HOST or HOST_CROSS +# $(2): A list of simple module names with :32 and :64 suffix +define resolve-bitness-for-modules +$(strip \ + $(eval modules_32 := $(patsubst %:32,%,$(filter %:32,$(2)))) \ + $(eval modules_64 := $(patsubst %:64,%,$(filter %:64,$(2)))) \ + $(eval modules_both := $(filter-out %:32 %:64,$(2))) \ + $(eval ### if 2ND_HOST_CROSS_IS_64_BIT, then primary/secondary are reversed for HOST_CROSS modules) \ + $(if $(filter HOST_CROSS_true,$(1)_$(2ND_HOST_CROSS_IS_64_BIT)), \ + $(eval modules_1st_arch := $(modules_32)) \ + $(eval modules_2nd_arch := $(modules_64)), \ + $(eval modules_1st_arch := $(modules_64)) \ + $(eval modules_2nd_arch := $(modules_32))) \ + $(eval ### Note for 32-bit product, 32 and 64 will be added as their original module names.) \ + $(eval modules := $(modules_1st_arch)) \ + $(if $($(1)_2ND_ARCH), \ + $(eval modules += $(call get-modules-for-2nd-arch,$(1),$(modules_2nd_arch))), \ + $(eval modules += $(modules_2nd_arch))) \ + $(eval ### For the rest we add both) \ + $(eval modules += $(modules_both)) \ + $(if $($(1)_2ND_ARCH), \ + $(eval modules += $(call get-modules-for-2nd-arch,$(1),$(modules_both)))) \ + $(modules) \ +) +endef + +# Resolve the required module names to 32-bit or 64-bit variant for: +# ALL_MODULES.<*>.REQUIRED_FROM_TARGET +# ALL_MODULES.<*>.REQUIRED_FROM_HOST +# ALL_MODULES.<*>.REQUIRED_FROM_HOST_CROSS +# +# If a module is for cross host OS, the required modules are also for that OS. +# Required modules explicitly suffixed with :32 or :64 resolve to that bitness. +# Otherwise if the requiring module is native and the required module is shared +# library or native test, then the required module resolves to the same bitness. +# Otherwise the required module resolves to both variants, if they exist. +# $(1): TARGET, HOST or HOST_CROSS +define select-bitness-of-required-modules +$(foreach m,$(ALL_MODULES), \ + $(eval r := $(ALL_MODULES.$(m).REQUIRED_FROM_$(1))) \ + $(if $(r), \ + $(if $(filter HOST_CROSS,$(1)), \ + $(if $(ALL_MODULES.$(m).FOR_HOST_CROSS),,$(error Only expected REQUIRED_FROM_HOST_CROSS on FOR_HOST_CROSS modules - $(m))) \ + $(eval r := $(addprefix host_cross_,$(r)))) \ + $(eval module_is_native := \ + $(filter EXECUTABLES SHARED_LIBRARIES NATIVE_TESTS,$(ALL_MODULES.$(m).CLASS))) \ + $(eval r_r := \ + $(foreach r_i,$(r), \ + $(if $(filter %:32 %:64,$(r_i)), \ + $(eval r_m := $(call resolve-bitness-for-modules,$(1),$(r_i))), \ + $(eval r_m := \ + $(eval r_i_2nd := $(call get-modules-for-2nd-arch,$(1),$(r_i))) \ + $(eval required_is_shared_library_or_native_test := \ + $(filter SHARED_LIBRARIES NATIVE_TESTS, \ + $(ALL_MODULES.$(r_i).CLASS) $(ALL_MODULES.$(r_i_2nd).CLASS))) \ + $(if $(and $(module_is_native),$(required_is_shared_library_or_native_test)), \ + $(if $(ALL_MODULES.$(m).FOR_2ND_ARCH),$(r_i_2nd),$(r_i)), \ + $(r_i) $(r_i_2nd)))) \ + $(eval r_m := $(foreach r_j,$(r_m),$(if $(ALL_MODULES.$(r_j).PATH),$(r_j)))) \ + $(if $(r_m),,$(eval _nonexistent_required += $(1)$(comma)$(m)$(comma)$(1)$(comma)$(r_i))) \ + $(r_m))) \ + $(eval ALL_MODULES.$(m).REQUIRED_FROM_$(1) := $(sort $(r_r))) \ + ) \ +) +endef + +# Resolve the required module names to 32-bit or 64-bit variant for: +# ALL_MODULES.<*>.TARGET_REQUIRED_FROM_HOST +# ALL_MODULES.<*>.HOST_REQUIRED_FROM_TARGET +# +# This is like select-bitness-of-required-modules, but it doesn't have +# complicated logic for various module types. +# Calls resolve-bitness-for-modules to resolve module names. +# $(1): TARGET or HOST +# $(2): HOST or TARGET +define select-bitness-of-target-host-required-modules +$(foreach m,$(ALL_MODULES), \ + $(eval r := $(ALL_MODULES.$(m).$(1)_REQUIRED_FROM_$(2))) \ + $(if $(r), \ + $(eval r_r := \ + $(foreach r_i,$(r), \ + $(eval r_m := $(call resolve-bitness-for-modules,$(1),$(r_i))) \ + $(eval r_m := $(foreach r_j,$(r_m),$(if $(ALL_MODULES.$(r_j).PATH),$(r_j)))) \ + $(if $(r_m),,$(eval _nonexistent_required += $(2)$(comma)$(m)$(comma)$(1)$(comma)$(r_i))) \ + $(r_m))) \ + $(eval ALL_MODULES.$(m).$(1)_REQUIRED_FROM_$(2) := $(sort $(r_r))) \ + ) \ +) +endef + +_nonexistent_required := +$(call select-bitness-of-required-modules,TARGET) +$(call select-bitness-of-required-modules,HOST) +$(call select-bitness-of-required-modules,HOST_CROSS) +$(call select-bitness-of-target-host-required-modules,TARGET,HOST) +$(call select-bitness-of-target-host-required-modules,HOST,TARGET) +_nonexistent_required := $(sort $(_nonexistent_required)) + +check_missing_required_modules := true +ifneq (,$(filter true,$(ALLOW_MISSING_DEPENDENCIES) $(BUILD_BROKEN_MISSING_REQUIRED_MODULES))) + check_missing_required_modules := +endif # ALLOW_MISSING_DEPENDENCIES == true || BUILD_BROKEN_MISSING_REQUIRED_MODULES == true + +# Some executables are skipped in ASAN SANITIZE_TARGET build, thus breaking their dependencies. +ifneq (,$(filter address,$(SANITIZE_TARGET))) + check_missing_required_modules := +endif # SANITIZE_TARGET has ASAN + +# HOST OS darwin build is broken, disable this check for darwin for now. +# TODO(b/162102724): Remove this when darwin host has no broken dependency. +ifneq (,$(filter $(HOST_OS),darwin)) + check_missing_required_modules := +endif # HOST_OS == darwin + +ifeq (true,$(check_missing_required_modules)) +ifneq (,$(_nonexistent_required)) + $(warning Missing required dependencies:) + $(foreach r_i,$(_nonexistent_required), \ + $(eval r := $(subst $(comma),$(space),$(r_i))) \ + $(info $(word 1,$(r)) module $(word 2,$(r)) requires non-existent $(word 3,$(r)) module: $(word 4,$(r))) \ + ) + $(warning Set BUILD_BROKEN_MISSING_REQUIRED_MODULES := true to bypass this check if this is intentional) + ifneq (,$(PRODUCT_SOURCE_ROOT_DIRS)) + $(warning PRODUCT_SOURCE_ROOT_DIRS is non-empty. Some necessary modules may have been skipped by Soong) + endif + $(error Build failed) +endif # _nonexistent_required != empty +endif # check_missing_required_modules == true + +define add-required-deps +$(1): | $(2) +endef + +# Use a normal dependency instead of an order-only dependency when installing +# host dynamic binaries so that the timestamp of the final binary always +# changes, even if the toc optimization has skipped relinking the binary +# and its dependant shared libraries. +define add-required-host-so-deps +$(1): $(2) +endef + +# Sets up dependencies such that whenever a host module is installed, +# any other host modules listed in $(ALL_MODULES.$(m).REQUIRED_FROM_HOST) will also be installed +define add-all-host-to-host-required-modules-deps +$(foreach m,$(ALL_MODULES), \ + $(eval r := $(ALL_MODULES.$(m).REQUIRED_FROM_HOST)) \ + $(if $(r), \ + $(eval r := $(call module-installed-files,$(r))) \ + $(eval h_m := $(filter $(HOST_OUT)/%, $(ALL_MODULES.$(m).INSTALLED))) \ + $(eval h_r := $(filter $(HOST_OUT)/%, $(r))) \ + $(eval h_r := $(filter-out $(h_m), $(h_r))) \ + $(if $(h_m), $(eval $(call add-required-deps, $(h_m),$(h_r)))) \ + ) \ +) +endef +$(call add-all-host-to-host-required-modules-deps) + +# Sets up dependencies such that whenever a host cross module is installed, +# any other host cross modules listed in $(ALL_MODULES.$(m).REQUIRED_FROM_HOST_CROSS) will also be installed +define add-all-host-cross-to-host-cross-required-modules-deps +$(foreach m,$(ALL_MODULES), \ + $(eval r := $(ALL_MODULES.$(m).REQUIRED_FROM_HOST_CROSS)) \ + $(if $(r), \ + $(eval r := $(call module-installed-files,$(r))) \ + $(eval hc_m := $(filter $(HOST_CROSS_OUT)/%, $(ALL_MODULES.$(m).INSTALLED))) \ + $(eval hc_r := $(filter $(HOST_CROSS_OUT)/%, $(r))) \ + $(eval hc_r := $(filter-out $(hc_m), $(hc_r))) \ + $(if $(hc_m), $(eval $(call add-required-deps, $(hc_m),$(hc_r)))) \ + ) \ +) +endef +$(call add-all-host-cross-to-host-cross-required-modules-deps) + +# Sets up dependencies such that whenever a target module is installed, +# any other target modules listed in $(ALL_MODULES.$(m).REQUIRED_FROM_TARGET) will also be installed +# This doesn't apply to ORDERONLY_INSTALLED items. +define add-all-target-to-target-required-modules-deps +$(foreach m,$(ALL_MODULES), \ + $(eval r := $(ALL_MODULES.$(m).REQUIRED_FROM_TARGET)) \ + $(if $(r), \ + $(eval r := $(call module-installed-files,$(r))) \ + $(eval t_m := $(filter $(TARGET_OUT_ROOT)/%, $(ALL_MODULES.$(m).INSTALLED))) \ + $(eval t_m := $(filter-out $(ALL_MODULES.$(m).ORDERONLY_INSTALLED), $(ALL_MODULES.$(m).INSTALLED))) \ + $(eval t_r := $(filter $(TARGET_OUT_ROOT)/%, $(r))) \ + $(eval t_r := $(filter-out $(t_m), $(t_r))) \ + $(if $(t_m), $(eval $(call add-required-deps, $(t_m),$(t_r)))) \ + ) \ +) +endef +$(call add-all-target-to-target-required-modules-deps) + +# Sets up dependencies such that whenever a host module is installed, +# any target modules listed in $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST) will also be installed +define add-all-host-to-target-required-modules-deps +$(foreach m,$(ALL_MODULES), \ + $(eval req_mods := $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST))\ + $(if $(req_mods), \ + $(eval req_files := )\ + $(foreach req_mod,$(req_mods), \ + $(eval req_file := $(filter $(TARGET_OUT_ROOT)/%, $(call module-installed-files,$(req_mod)))) \ + $(if $(filter true,$(ALLOW_MISSING_DEPENDENCIES)), \ + , \ + $(if $(strip $(req_file)), \ + , \ + $(error $(m).LOCAL_TARGET_REQUIRED_MODULES : illegal value $(req_mod) : not a device module. If you want to specify host modules to be required to be installed along with your host module, add those module names to LOCAL_REQUIRED_MODULES instead) \ + ) \ + ) \ + $(eval req_files := $(req_files)$(space)$(req_file))\ + )\ + $(eval req_files := $(strip $(req_files)))\ + $(eval mod_files := $(filter $(HOST_OUT)/%, $(call module-installed-files,$(m)))) \ + $(if $(mod_files),\ + $(eval $(call add-required-deps, $(mod_files),$(req_files))) \ + )\ + )\ +) +endef +$(call add-all-host-to-target-required-modules-deps) + +# Sets up dependencies such that whenever a target module is installed, +# any host modules listed in $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET) will also be installed +define add-all-target-to-host-required-modules-deps +$(foreach m,$(ALL_MODULES), \ + $(eval req_mods := $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET))\ + $(if $(req_mods), \ + $(eval req_files := )\ + $(foreach req_mod,$(req_mods), \ + $(eval req_file := $(filter $(HOST_OUT)/%, $(call module-installed-files,$(req_mod)))) \ + $(if $(filter true,$(ALLOW_MISSING_DEPENDENCIES)), \ + , \ + $(if $(strip $(req_file)), \ + , \ + $(error $(m).LOCAL_HOST_REQUIRED_MODULES : illegal value $(req_mod) : not a host module. If you want to specify target modules to be required to be installed along with your target module, add those module names to LOCAL_REQUIRED_MODULES instead) \ + ) \ + ) \ + $(eval req_files := $(req_files)$(space)$(req_file))\ + )\ + $(eval req_files := $(strip $(req_files)))\ + $(eval mod_files := $(filter $(TARGET_OUT_ROOT)/%, $(call module-installed-files,$(m))))\ + $(if $(mod_files),\ + $(eval $(call add-required-deps, $(mod_files),$(req_files))) \ + )\ + )\ +) +endef +$(call add-all-target-to-host-required-modules-deps) + +t_m := +h_m := +hc_m := +t_r := +h_r := +hc_r := + +# Establish the dependencies on the shared libraries. +# It also adds the shared library module names to ALL_MODULES.$(m).REQUIRED_FROM_(TARGET|HOST|HOST_CROSS), +# so they can be expanded to product_MODULES later. +# $(1): TARGET_ or HOST_ or HOST_CROSS_. +# $(2): non-empty for 2nd arch. +# $(3): non-empty for host cross compile. +define resolve-shared-libs-depes +$(foreach m,$($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))$(1)DEPENDENCIES_ON_SHARED_LIBRARIES),\ + $(eval p := $(subst :,$(space),$(m)))\ + $(eval mod := $(firstword $(p)))\ + $(eval deps := $(subst $(comma),$(space),$(lastword $(p))))\ + $(eval root := $(1)OUT$(if $(call streq,$(1),TARGET_),_ROOT))\ + $(if $(2),$(eval deps := $(addsuffix $($(1)2ND_ARCH_MODULE_SUFFIX),$(deps))))\ + $(if $(3),$(eval deps := $(addprefix host_cross_,$(deps))))\ + $(eval r := $(filter $($(root))/%,$(call module-installed-files,\ + $(deps))))\ + $(if $(filter $(1),HOST_),\ + $(eval ALL_MODULES.$(mod).HOST_SHARED_LIBRARY_FILES := $$(ALL_MODULES.$(mod).HOST_SHARED_LIBRARY_FILES) $(word 2,$(p)) $(r))\ + $(eval ALL_MODULES.$(mod).HOST_SHARED_LIBRARIES := $$(ALL_MODULES.$(mod).HOST_SHARED_LIBRARIES) $(deps))\ + $(eval $(call add-required-host-so-deps,$(word 2,$(p)),$(r))),\ + $(eval $(call add-required-deps,$(word 2,$(p)),$(r))))\ + $(eval ALL_MODULES.$(mod).REQUIRED_FROM_$(patsubst %_,%,$(1)) += $(deps))) +endef + +# Recursively resolve host shared library dependency for a given module. +# $(1): module name +# Returns all dependencies of shared library. +define get-all-shared-libs-deps +$(if $(_all_deps_for_$(1)_set_),$(_all_deps_for_$(1)_),\ + $(eval _all_deps_for_$(1)_ :=) \ + $(foreach dep,$(ALL_MODULES.$(1).HOST_SHARED_LIBRARIES),\ + $(foreach m,$(call get-all-shared-libs-deps,$(dep)),\ + $(eval _all_deps_for_$(1)_ := $$(_all_deps_for_$(1)_) $(m))\ + $(eval _all_deps_for_$(1)_ := $(sort $(_all_deps_for_$(1)_))))\ + $(eval _all_deps_for_$(1)_ := $$(_all_deps_for_$(1)_) $(dep))\ + $(eval _all_deps_for_$(1)_ := $(sort $(_all_deps_for_$(1)_) $(dep)))\ + $(eval _all_deps_for_$(1)_set_ := true))\ +$(_all_deps_for_$(1)_)) +endef + +# Scan all modules in general-tests, device-tests and other selected suites and +# flatten the shared library dependencies. +define update-host-shared-libs-deps-for-suites +$(foreach suite,general-tests device-tests vts tvts art-host-tests host-unit-tests,\ + $(foreach m,$(COMPATIBILITY.$(suite).MODULES),\ + $(eval my_deps := $(call get-all-shared-libs-deps,$(m)))\ + $(foreach dep,$(my_deps),\ + $(foreach f,$(ALL_MODULES.$(dep).HOST_SHARED_LIBRARY_FILES),\ + $(if $(filter $(suite),device-tests general-tests art-host-tests host-unit-tests),\ + $(eval my_testcases := $(HOST_OUT_TESTCASES)),\ + $(eval my_testcases := $$(COMPATIBILITY_TESTCASES_OUT_$(suite))))\ + $(eval target := $(my_testcases)/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\ + $(if $(strip $(ALL_TARGETS.$(target).META_LIC)),,$(call declare-copy-target-license-metadata,$(target),$(f)))\ + $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \ + $$(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES) $(f):$(target))\ + $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \ + $(sort $(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES))))))) +endef + +$(call resolve-shared-libs-depes,TARGET_) +ifdef TARGET_2ND_ARCH +$(call resolve-shared-libs-depes,TARGET_,true) +endif +$(call resolve-shared-libs-depes,HOST_) +ifdef HOST_2ND_ARCH +$(call resolve-shared-libs-depes,HOST_,true) +endif +# Update host side shared library dependencies for tests in suite device-tests and general-tests. +# This should be called after calling resolve-shared-libs-depes for HOST_2ND_ARCH. +$(call update-host-shared-libs-deps-for-suites) +ifdef HOST_CROSS_OS +$(call resolve-shared-libs-depes,HOST_CROSS_,,true) +ifdef HOST_CROSS_2ND_ARCH +$(call resolve-shared-libs-depes,HOST_CROSS_,true,true) +endif +endif + +# Pass the shared libraries dependencies to prebuilt ELF file check. +define add-elf-file-check-shared-lib +$(1): PRIVATE_SHARED_LIBRARY_FILES += $(2) +$(1): $(2) +endef + +define resolve-shared-libs-for-elf-file-check +$(foreach m,$($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))$(1)DEPENDENCIES_ON_SHARED_LIBRARIES),\ + $(eval p := $(subst :,$(space),$(m)))\ + $(eval mod := $(firstword $(p)))\ + \ + $(eval deps := $(subst $(comma),$(space),$(lastword $(p))))\ + $(if $(2),$(eval deps := $(addsuffix $($(1)2ND_ARCH_MODULE_SUFFIX),$(deps))))\ + $(eval root := $(1)OUT$(if $(call streq,$(1),TARGET_),_ROOT))\ + $(eval deps := $(filter $($(root))/%$($(1)SHLIB_SUFFIX),$(call module-built-files,$(deps))))\ + \ + $(eval r := $(firstword $(filter \ + $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/EXECUTABLES/%\ + $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/NATIVE_TESTS/%\ + $($(if $(2),$($(1)2ND_ARCH_VAR_PREFIX))TARGET_OUT_INTERMEDIATES)/SHARED_LIBRARIES/%,\ + $(call module-built-files,$(mod)))))\ + \ + $(if $(and $(r),$(deps)),\ + $(eval stamp := $(dir $(r))check_elf_files.timestamp)\ + $(if $(CHECK_ELF_FILES.$(stamp)),\ + $(eval $(call add-elf-file-check-shared-lib,$(stamp),$(deps))))\ + )) +endef + +$(call resolve-shared-libs-for-elf-file-check,TARGET_) +ifdef TARGET_2ND_ARCH +$(call resolve-shared-libs-for-elf-file-check,TARGET_,true) +endif + +m := +r := +p := +stamp := +deps := +add-required-deps := + +################################################################################ +# Link type checking +# +# ALL_LINK_TYPES contains a list of all link type prefixes (generally one per +# module, but APKs can "link" to both java and native code). The link type +# prefix consists of all the information needed by intermediates-dir-for: +# +# LINK_TYPE:TARGET:_:2ND:STATIC_LIBRARIES:libfoo +# +# 1: LINK_TYPE literal +# 2: prefix +# - TARGET +# - HOST +# - HOST_CROSS +# 3: Whether to use the common intermediates directory or not +# - _ +# - COMMON +# 4: Whether it's the second arch or not +# - _ +# - 2ND_ +# 5: Module Class +# - STATIC_LIBRARIES +# - SHARED_LIBRARIES +# - ... +# 6: Module Name +# +# Then fields under that are separated by a period and the field name: +# - TYPE: the link types for this module +# - MAKEFILE: Where this module was defined +# - BUILT: The built module location +# - DEPS: the link type prefixes for the module's dependencies +# - ALLOWED: the link types to allow in this module's dependencies +# - WARN: the link types to warn about in this module's dependencies +# +# All of the dependency link types not listed in ALLOWED or WARN will become +# errors. +################################################################################ + +link_type_error := + +define link-type-prefix +$(word 2,$(subst :,$(space),$(1))) +endef +define link-type-common +$(patsubst _,,$(word 3,$(subst :,$(space),$(1)))) +endef +define link-type-2ndarchprefix +$(patsubst _,,$(word 4,$(subst :,$(space),$(1)))) +endef +define link-type-class +$(word 5,$(subst :,$(space),$(1))) +endef +define link-type-name +$(word 6,$(subst :,$(space),$(1))) +endef +define link-type-os +$(strip $(eval _p := $(link-type-prefix))\ + $(if $(filter HOST HOST_CROSS,$(_p)),\ + $($(_p)_OS),\ + android)) +endef +define link-type-arch +$($(link-type-prefix)_$(link-type-2ndarchprefix)ARCH) +endef +define link-type-name-variant +$(link-type-name) ($(link-type-class) $(link-type-os)-$(link-type-arch)) +endef + +# $(1): the prefix of the module doing the linking +# $(2): the prefix of the linked module +define link-type-warning +$(shell $(call echo-warning,$($(1).MAKEFILE),"$(call link-type-name,$(1)) ($($(1).TYPE)) should not link against $(call link-type-name,$(2)) ($(3))")) +endef + +# $(1): the prefix of the module doing the linking +# $(2): the prefix of the linked module +define link-type-error +$(shell $(call echo-error,$($(1).MAKEFILE),"$(call link-type-name,$(1)) ($($(1).TYPE)) can not link against $(call link-type-name,$(2)) ($(3))"))\ +$(eval link_type_error := true) +endef + +link-type-missing := +ifneq ($(ALLOW_MISSING_DEPENDENCIES),true) + # Print an error message if the linked-to module is missing + # $(1): the prefix of the module doing the linking + # $(2): the prefix of the missing module + define link-type-missing + $(shell $(call echo-error,$($(1).MAKEFILE),"$(call link-type-name-variant,$(1)) missing $(call link-type-name-variant,$(2))"))\ + $(eval available_variants := $(filter %:$(call link-type-name,$(2)),$(ALL_LINK_TYPES)))\ + $(if $(available_variants),\ + $(info Available variants:)\ + $(foreach v,$(available_variants),$(info $(space)$(space)$(call link-type-name-variant,$(v)))))\ + $(info You can set ALLOW_MISSING_DEPENDENCIES=true in your environment if this is intentional, but that may defer real problems until later in the build.)\ + $(eval link_type_error := true) + endef +else + define link-type-missing + $(eval $$(1).MISSING := true) + endef +endif + +# Verify that $(1) can link against $(2) +# Both $(1) and $(2) are the link type prefix defined above +define verify-link-type +$(foreach t,$($(2).TYPE),\ + $(if $(filter-out $($(1).ALLOWED),$(t)),\ + $(if $(filter $(t),$($(1).WARN)),\ + $(call link-type-warning,$(1),$(2),$(t)),\ + $(call link-type-error,$(1),$(2),$(t))))) +endef + +$(foreach lt,$(ALL_LINK_TYPES),\ + $(foreach d,$($(lt).DEPS),\ + $(if $($(d).TYPE),\ + $(call verify-link-type,$(lt),$(d)),\ + $(call link-type-missing,$(lt),$(d))))) + +ifdef link_type_error + $(error exiting from previous errors) +endif + +# ------------------------------------------------------------------- +# Handle exported/imported includes + +# Recursively calculate flags +$(foreach export,$(EXPORTS_LIST), \ + $(eval EXPORTS.$$(export) = $$(EXPORTS.$(export).FLAGS) \ + $(foreach dep,$(EXPORTS.$(export).REEXPORT),$$(EXPORTS.$(dep))))) + +# Recursively calculate dependencies +$(foreach export,$(EXPORTS_LIST), \ + $(eval EXPORT_DEPS.$$(export) = $$(EXPORTS.$(export).DEPS) \ + $(foreach dep,$(EXPORTS.$(export).REEXPORT),$$(EXPORT_DEPS.$(dep))))) + +# Converts the recursive variables to simple variables so that we don't have to +# evaluate them for every .o rule +$(foreach export,$(EXPORTS_LIST),$(eval EXPORTS.$$(export) := $$(strip $$(EXPORTS.$$(export))))) +$(foreach export,$(EXPORTS_LIST),$(eval EXPORT_DEPS.$$(export) := $$(sort $$(EXPORT_DEPS.$$(export))))) + +# Add dependencies +$(foreach export,$(EXPORTS_LIST),$(eval $(call add-dependency,$$(EXPORTS.$$(export).USERS),$$(EXPORT_DEPS.$$(export))))) + +# ------------------------------------------------------------------- +# Figure out our module sets. +# +# Of the modules defined by the component makefiles, +# determine what we actually want to build. + + +# Expand a list of modules to the modules that they override (if any) +# $(1): The list of modules. +define module-overrides +$(foreach m,$(1),\ + $(eval _mo_overrides := $(PACKAGES.$(m).OVERRIDES) $(EXECUTABLES.$(m).OVERRIDES) $(SHARED_LIBRARIES.$(m).OVERRIDES) $(ETC.$(m).OVERRIDES))\ + $(if $(filter $(m),$(_mo_overrides)),\ + $(error Module $(m) cannot override itself),\ + $(_mo_overrides))) +endef + +########################################################### +## Expand a module name list with REQUIRED modules +########################################################### +# $(1): The variable name that holds the initial module name list. +# the variable will be modified to hold the expanded results. +# $(2): The initial module name list. +# $(3): The list of overridden modules. +# Returns empty string (maybe with some whitespaces). +define expand-required-modules +$(eval _erm_req := $(foreach m,$(2),$(ALL_MODULES.$(m).REQUIRED_FROM_TARGET))) \ +$(eval _erm_new_modules := $(sort $(filter-out $($(1)),$(_erm_req)))) \ +$(eval _erm_new_overrides := $(call module-overrides,$(_erm_new_modules))) \ +$(eval _erm_all_overrides := $(3) $(_erm_new_overrides)) \ +$(eval _erm_new_modules := $(filter-out $(_erm_all_overrides), $(_erm_new_modules))) \ +$(eval $(1) := $(filter-out $(_erm_new_overrides),$($(1)))) \ +$(eval $(1) += $(_erm_new_modules)) \ +$(if $(_erm_new_modules),\ + $(call expand-required-modules,$(1),$(_erm_new_modules),$(_erm_all_overrides))) +endef + +# Same as expand-required-modules above, but does not handle module overrides, as +# we don't intend to support them on the host. +# $(1): The variable name that holds the initial module name list. +# the variable will be modified to hold the expanded results. +# $(2): The initial module name list. +# $(3): HOST or HOST_CROSS depending on whether we're expanding host or host cross modules +# Returns empty string (maybe with some whitespaces). +define expand-required-host-modules +$(eval _erm_req := $(foreach m,$(2),$(ALL_MODULES.$(m).REQUIRED_FROM_$(3)))) \ +$(eval _erm_new_modules := $(sort $(filter-out $($(1)),$(_erm_req)))) \ +$(eval $(1) += $(_erm_new_modules)) \ +$(if $(_erm_new_modules),\ + $(call expand-required-host-modules,$(1),$(_erm_new_modules),$(3))) +endef + +# Transforms paths relative to PRODUCT_OUT to absolute paths. +# $(1): list of relative paths +# $(2): optional suffix to append to paths +define resolve-product-relative-paths + $(subst $(_vendor_path_placeholder),$(TARGET_COPY_OUT_VENDOR),\ + $(subst $(_product_path_placeholder),$(TARGET_COPY_OUT_PRODUCT),\ + $(subst $(_system_ext_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_EXT),\ + $(subst $(_odm_path_placeholder),$(TARGET_COPY_OUT_ODM),\ + $(subst $(_vendor_dlkm_path_placeholder),$(TARGET_COPY_OUT_VENDOR_DLKM),\ + $(subst $(_odm_dlkm_path_placeholder),$(TARGET_COPY_OUT_ODM_DLKM),\ + $(subst $(_system_dlkm_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_DLKM),\ + $(foreach p,$(1),$(call append-path,$(PRODUCT_OUT),$(p)$(2)))))))))) +endef + +# Returns modules included automatically as a result of certain BoardConfig +# variables being set. +define auto-included-modules + $(if $(and $(BOARD_VNDK_VERSION),$(filter true,$(KEEP_VNDK))),vndk_package) \ + $(if $(filter true,$(KEEP_VNDK)),,llndk_in_system) \ + $(if $(DEVICE_MANIFEST_FILE),vendor_manifest.xml) \ + $(if $(DEVICE_MANIFEST_SKUS),$(foreach sku, $(DEVICE_MANIFEST_SKUS),vendor_manifest_$(sku).xml)) \ + $(if $(ODM_MANIFEST_FILES),odm_manifest.xml) \ + $(if $(ODM_MANIFEST_SKUS),$(foreach sku, $(ODM_MANIFEST_SKUS),odm_manifest_$(sku).xml)) \ + +endef + +# Lists the modules particular product installs. +# The base list of modules to build for this product is specified +# by the appropriate product definition file, which was included +# by product_config.mk. +# Name resolution for PRODUCT_PACKAGES: +# foo:32 resolves to foo_32; +# foo:64 resolves to foo; +# foo resolves to both foo and foo_32 (if foo_32 is defined). +# +# Name resolution for LOCAL_REQUIRED_MODULES: +# See the select-bitness-of-required-modules definition. +# $(1): product makefile +define product-installed-modules + $(eval _pif_modules := \ + $(call get-product-var,$(1),PRODUCT_PACKAGES) \ + $(if $(filter eng,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_ENG)) \ + $(if $(filter debug,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG)) \ + $(if $(filter tests,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_TESTS)) \ + $(if $(filter asan,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_ASAN)) \ + $(if $(filter java_coverage,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) \ + $(if $(filter arm64,$(TARGET_ARCH) $(TARGET_2ND_ARCH)),$(call get-product-var,$(1),PRODUCT_PACKAGES_ARM64)) \ + $(if $(PRODUCT_SHIPPING_API_LEVEL), \ + $(if $(call math_gt_or_eq,29,$(PRODUCT_SHIPPING_API_LEVEL)),$(call get-product-var,$(1),PRODUCT_PACKAGES_SHIPPING_API_LEVEL_29)) \ + $(if $(call math_gt_or_eq,33,$(PRODUCT_SHIPPING_API_LEVEL)),$(call get-product-var,$(1),PRODUCT_PACKAGES_SHIPPING_API_LEVEL_33)) \ + $(if $(call math_gt_or_eq,34,$(PRODUCT_SHIPPING_API_LEVEL)),$(call get-product-var,$(1),PRODUCT_PACKAGES_SHIPPING_API_LEVEL_34)) \ + ) \ + $(call auto-included-modules) \ + ) \ + $(eval ### Filter out the overridden packages and executables before doing expansion) \ + $(eval _pif_overrides := $(call module-overrides,$(_pif_modules))) \ + $(eval _pif_modules := $(filter-out $(_pif_overrides), $(_pif_modules))) \ + $(eval ### Resolve the :32 :64 module name) \ + $(eval _pif_modules := $(sort $(call resolve-bitness-for-modules,TARGET,$(_pif_modules)))) \ + $(call expand-required-modules,_pif_modules,$(_pif_modules),$(_pif_overrides)) \ + $(_pif_modules) +endef + +# Lists most of the files a particular product installs. +# It gives all the installed files for all modules returned by product-installed-modules, +# and also includes PRODUCT_COPY_FILES. +define product-installed-files + $(filter-out $(HOST_OUT_ROOT)/%,$(call module-installed-files, $(call product-installed-modules,$(1)))) \ + $(call resolve-product-relative-paths,\ + $(foreach cf,$(call get-product-var,$(1),PRODUCT_COPY_FILES),$(call word-colon,2,$(cf)))) +endef + +# Similar to product-installed-files above, but handles PRODUCT_HOST_PACKAGES instead +# This does support the :32 / :64 syntax, but does not support module overrides. +define host-installed-files + $(eval _hif_modules := $(call get-product-var,$(1),PRODUCT_HOST_PACKAGES)) \ + $(eval ### Split host vs host cross modules) \ + $(eval _hcif_modules := $(filter host_cross_%,$(_hif_modules))) \ + $(eval _hif_modules := $(filter-out host_cross_%,$(_hif_modules))) \ + $(eval ### Resolve the :32 :64 module name) \ + $(eval _hif_modules := $(sort $(call resolve-bitness-for-modules,HOST,$(_hif_modules)))) \ + $(eval _hcif_modules := $(sort $(call resolve-bitness-for-modules,HOST_CROSS,$(_hcif_modules)))) \ + $(call expand-required-host-modules,_hif_modules,$(_hif_modules),HOST) \ + $(call expand-required-host-modules,_hcif_modules,$(_hcif_modules),HOST_CROSS) \ + $(filter $(HOST_OUT)/%,$(call module-installed-files, $(_hif_modules))) \ + $(filter $(HOST_CROSS_OUT)/%,$(call module-installed-files, $(_hcif_modules))) +endef + +# Fails the build if the given list is non-empty, and prints it entries (stripping PRODUCT_OUT). +# $(1): list of files to print +# $(2): heading to print on failure +define maybe-print-list-and-error +$(if $(strip $(1)), \ + $(warning $(2)) \ + $(info Offending entries:) \ + $(foreach e,$(sort $(1)),$(info $(patsubst $(PRODUCT_OUT)/%,%,$(e)))) \ + $(error Build failed) \ +) +endef + +ifeq ($(HOST_OS),darwin) + # Target builds are not supported on Mac + product_target_FILES := + product_host_FILES := $(call host-installed-files,$(INTERNAL_PRODUCT)) +else ifdef FULL_BUILD + ifneq (true,$(ALLOW_MISSING_DEPENDENCIES)) + # Check to ensure that all modules in PRODUCT_PACKAGES exist (opt in per product) + ifeq (true,$(PRODUCT_ENFORCE_PACKAGES_EXIST)) + _allow_list := $(PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST) + _modules := $(PRODUCT_PACKAGES) + # Strip :32 and :64 suffixes + _modules := $(patsubst %:32,%,$(_modules)) + _modules := $(patsubst %:64,%,$(_modules)) + # Quickly check all modules in PRODUCT_PACKAGES exist. We check for the + # existence if either or the _32 variant. + _nonexistent_modules := $(foreach m,$(_modules), \ + $(if $(or $(ALL_MODULES.$(m).PATH),$(call get-modules-for-2nd-arch,TARGET,$(m))),,$(m))) + $(call maybe-print-list-and-error,$(filter-out $(_allow_list),$(_nonexistent_modules)),\ + $(INTERNAL_PRODUCT) includes non-existent modules in PRODUCT_PACKAGES) + # TODO(b/182105280): Consider re-enabling this check when the ART modules + # have been cleaned up from the allowed_list in target/product/generic.mk. + #$(call maybe-print-list-and-error,$(filter-out $(_nonexistent_modules),$(_allow_list)),\ + # $(INTERNAL_PRODUCT) includes redundant allow list entries for non-existent PRODUCT_PACKAGES) + endif + + # Check to ensure that all modules in PRODUCT_HOST_PACKAGES exist + # + # Many host modules are Linux-only, so skip this check on Mac. If we ever have Mac-only modules, + # maybe it would make sense to have PRODUCT_HOST_PACKAGES_LINUX/_DARWIN? + ifneq ($(HOST_OS),darwin) + _modules := $(PRODUCT_HOST_PACKAGES) + # Strip :32 and :64 suffixes + _modules := $(patsubst %:32,%,$(_modules)) + _modules := $(patsubst %:64,%,$(_modules)) + _nonexistent_modules := $(foreach m,$(_modules),\ + $(if $(ALL_MODULES.$(m).REQUIRED_FROM_HOST)$(filter $(HOST_OUT_ROOT)/%,$(ALL_MODULES.$(m).INSTALLED)),,$(m))) + $(call maybe-print-list-and-error,$(_nonexistent_modules),\ + $(INTERNAL_PRODUCT) includes non-existent modules in PRODUCT_HOST_PACKAGES) + endif + endif + + # Modules may produce only host installed files in unbundled builds. + ifeq (,$(TARGET_BUILD_UNBUNDLED)) + _modules := $(call resolve-bitness-for-modules,TARGET, \ + $(PRODUCT_PACKAGES) \ + $(PRODUCT_PACKAGES_DEBUG) \ + $(PRODUCT_PACKAGES_DEBUG_ASAN) \ + $(PRODUCT_PACKAGES_ENG) \ + $(PRODUCT_PACKAGES_TESTS)) + _host_modules := $(foreach m,$(_modules), \ + $(if $(ALL_MODULES.$(m).INSTALLED),\ + $(if $(filter-out $(HOST_OUT_ROOT)/%,$(ALL_MODULES.$(m).INSTALLED)),,\ + $(m)))) + ifeq ($(TARGET_ARCH),riscv64) + # HACK: riscv64 can't build the device version of bcc and ld.mc due to a + # dependency on an old version of LLVM, but they are listed in + # base_system.mk which can't add them conditionally based on the target + # architecture. + _host_modules := $(filter-out bcc ld.mc,$(_host_modules)) + endif + $(call maybe-print-list-and-error,$(sort $(_host_modules)),\ + Host modules should be in PRODUCT_HOST_PACKAGES$(comma) not PRODUCT_PACKAGES) + endif + + product_host_FILES := $(call host-installed-files,$(INTERNAL_PRODUCT)) + product_target_FILES := $(call product-installed-files, $(INTERNAL_PRODUCT)) + # WARNING: The product_MODULES variable is depended on by external files. + # It contains the list of register names that will be installed on the device + product_MODULES := $(_pif_modules) + + # Verify the artifact path requirements made by included products. + is_asan := $(if $(filter address,$(SANITIZE_TARGET)),true) + ifeq (,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS))) + include $(BUILD_SYSTEM)/artifact_path_requirements.mk + endif +else + # We're not doing a full build, and are probably only including + # a subset of the module makefiles. Don't try to build any modules + # requested by the product, because we probably won't have rules + # to build them. + product_target_FILES := + product_host_FILES := +endif + +# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES +# and get rid of it from this list. +modules_to_install := $(sort \ + $(ALL_DEFAULT_INSTALLED_MODULES) \ + $(product_target_FILES) \ + $(product_host_FILES) \ + $(CUSTOM_MODULES) \ + ) + +# Deduplicate compatibility suite dist files across modules and packages before +# copying them to their requested locations. Assign the eval result to an unused +# var to prevent Make from trying to make a sense of it. +_unused := $(call copy-many-files, $(sort $(ALL_COMPATIBILITY_DIST_FILES))) + +ifdef is_sdk_build + # Ensure every module listed in PRODUCT_PACKAGES* gets something installed + # TODO: Should we do this for all builds and not just the sdk? + dangling_modules := + $(foreach m, $(PRODUCT_PACKAGES), \ + $(if $(strip $(ALL_MODULES.$(m).INSTALLED) $(ALL_MODULES.$(m)$(TARGET_2ND_ARCH_MODULE_SUFFIX).INSTALLED)),,\ + $(eval dangling_modules += $(m)))) + ifneq ($(dangling_modules),) + $(warning: Modules '$(dangling_modules)' in PRODUCT_PACKAGES have nothing to install!) + endif + $(foreach m, $(PRODUCT_PACKAGES_DEBUG), \ + $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ + $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_DEBUG has nothing to install!))) + $(foreach m, $(PRODUCT_PACKAGES_ENG), \ + $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ + $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_ENG has nothing to install!))) + $(foreach m, $(PRODUCT_PACKAGES_TESTS), \ + $(if $(strip $(ALL_MODULES.$(m).INSTALLED)),,\ + $(warning $(ALL_MODULES.$(m).MAKEFILE): Module '$(m)' in PRODUCT_PACKAGES_TESTS has nothing to install!))) +endif + +ifneq ($(TARGET_BUILD_APPS),) + # If this build is just for apps, only build apps and not the full system by default. + ifneq ($(filter all,$(TARGET_BUILD_APPS)),) + # If they used the magic goal "all" then build all apps in the source tree. + unbundled_build_modules := $(foreach m,$(sort $(ALL_MODULES)),$(if $(filter APPS,$(ALL_MODULES.$(m).CLASS)),$(m))) + else + unbundled_build_modules := $(sort $(TARGET_BUILD_APPS)) + endif +endif + +# build/make/core/Makefile contains extra stuff that we don't want to pollute this +# top-level makefile with. It expects that ALL_DEFAULT_INSTALLED_MODULES +# contains everything that's built during the current make, but it also further +# extends ALL_DEFAULT_INSTALLED_MODULES. +ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install) +ifeq ($(HOST_OS),linux) + include $(BUILD_SYSTEM)/Makefile +endif +modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES)) +ALL_DEFAULT_INSTALLED_MODULES := + +ifdef FULL_BUILD +# +# Used by the cleanup logic in soong_ui to remove files that should no longer +# be installed. +# + +# Include all tests, so that we remove them from the test suites / testcase +# folders when they are removed. +test_files := $(foreach ts,$(ALL_COMPATIBILITY_SUITES),$(COMPATIBILITY.$(ts).FILES)) + +$(shell mkdir -p $(PRODUCT_OUT) $(HOST_OUT)) + +$(file >$(PRODUCT_OUT)/.installable_files$(if $(filter address,$(SANITIZE_TARGET)),_asan), \ + $(sort $(patsubst $(PRODUCT_OUT)/%,%,$(filter $(PRODUCT_OUT)/%, \ + $(modules_to_install) $(test_files))))) + +$(file >$(HOST_OUT)/.installable_test_files,$(sort \ + $(patsubst $(HOST_OUT)/%,%,$(filter $(HOST_OUT)/%, \ + $(test_files))))) + +test_files := +endif + +# Some notice deps refer to module names without prefix or arch suffix where +# only the variants with them get built. +# fix-notice-deps replaces those unadorned module names with every built variant. +$(call fix-notice-deps) + +# These are additional goals that we build, in order to make sure that there +# is as little code as possible in the tree that doesn't build. +modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED)) + +# If you would like to build all goals, and not skip any intermediate +# steps, you can pass the "all" modifier goal on the commandline. +ifneq ($(filter all,$(MAKECMDGOALS)),) +modules_to_check += $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).BUILT)) +endif + +# Build docs as part of checkbuild to catch more breakages. +modules_to_check += $(ALL_DOCS) + +# for easier debugging +modules_to_check := $(sort $(modules_to_check)) +#$(error modules_to_check $(modules_to_check)) + +# ------------------------------------------------------------------- +# This is used to to get the ordering right, you can also use these, +# but they're considered undocumented, so don't complain if their +# behavior changes. +# An internal target that depends on all copied headers +# (see copy_headers.make). Other targets that need the +# headers to be copied first can depend on this target. +.PHONY: all_copied_headers +all_copied_headers: ; + +$(ALL_C_CPP_ETC_OBJECTS): | all_copied_headers + +# All the droid stuff, in directories +.PHONY: files +files: $(modules_to_install) \ + $(INSTALLED_ANDROID_INFO_TXT_TARGET) + +# ------------------------------------------------------------------- + +.PHONY: checkbuild +checkbuild: $(modules_to_check) droid_targets check-elf-files + +ifeq (true,$(ANDROID_BUILD_EVERYTHING_BY_DEFAULT)) +droid: checkbuild +endif + +.PHONY: ramdisk +ramdisk: $(INSTALLED_RAMDISK_TARGET) + +.PHONY: ramdisk_debug +ramdisk_debug: $(INSTALLED_DEBUG_RAMDISK_TARGET) + +.PHONY: ramdisk_test_harness +ramdisk_test_harness: $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) + +.PHONY: userdataimage +userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET) + +ifneq (,$(filter userdataimage, $(MAKECMDGOALS))) +$(call dist-for-goals, userdataimage, $(BUILT_USERDATAIMAGE_TARGET)) +endif + +.PHONY: cacheimage +cacheimage: $(INSTALLED_CACHEIMAGE_TARGET) + +.PHONY: bptimage +bptimage: $(INSTALLED_BPTIMAGE_TARGET) + +.PHONY: vendorimage +vendorimage: $(INSTALLED_VENDORIMAGE_TARGET) + +.PHONY: vendorbootimage +vendorbootimage: $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) + +.PHONY: vendorkernelbootimage +vendorkernelbootimage: $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) + +.PHONY: vendorbootimage_debug +vendorbootimage_debug: $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) + +.PHONY: vendorbootimage_test_harness +vendorbootimage_test_harness: $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) + +.PHONY: vendorramdisk +vendorramdisk: $(INSTALLED_VENDOR_RAMDISK_TARGET) + +.PHONY: vendorkernelramdisk +vendorkernelramdisk: $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) + +.PHONY: vendorramdisk_debug +vendorramdisk_debug: $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) + +.PHONY: vendorramdisk_test_harness +vendorramdisk_test_harness: $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) + +.PHONY: productimage +productimage: $(INSTALLED_PRODUCTIMAGE_TARGET) + +.PHONY: systemextimage +systemextimage: $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) + +.PHONY: odmimage +odmimage: $(INSTALLED_ODMIMAGE_TARGET) + +.PHONY: vendor_dlkmimage +vendor_dlkmimage: $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) + +.PHONY: odm_dlkmimage +odm_dlkmimage: $(INSTALLED_ODM_DLKMIMAGE_TARGET) + +.PHONY: system_dlkmimage +system_dlkmimage: $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) + +.PHONY: systemotherimage +systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) + +.PHONY: superimage_empty +superimage_empty: $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) + +.PHONY: bootimage +bootimage: $(INSTALLED_BOOTIMAGE_TARGET) + +.PHONY: initbootimage +initbootimage: $(INSTALLED_INIT_BOOT_IMAGE_TARGET) + +ifeq (true,$(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST)) +$(call dist-for-goals, bootimage, $(INSTALLED_BOOTIMAGE_TARGET)) +endif + +.PHONY: bootimage_debug +bootimage_debug: $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) + +.PHONY: bootimage_test_harness +bootimage_test_harness: $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET) + +.PHONY: vbmetaimage +vbmetaimage: $(INSTALLED_VBMETAIMAGE_TARGET) + +.PHONY: vbmetasystemimage +vbmetasystemimage: $(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET) + +.PHONY: vbmetavendorimage +vbmetavendorimage: $(INSTALLED_VBMETA_VENDORIMAGE_TARGET) + +.PHONY: vbmetacustomimages +vbmetacustomimages: $(foreach partition,$(call to-upper,$(BOARD_AVB_VBMETA_CUSTOM_PARTITIONS)),$(INSTALLED_VBMETA_$(partition)IMAGE_TARGET)) + +# The droidcore-unbundled target depends on the subset of targets necessary to +# perform a full system build (either unbundled or not). +.PHONY: droidcore-unbundled +droidcore-unbundled: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \ + $(INSTALLED_FILES_OUTSIDE_IMAGES) \ + $(INSTALLED_SYSTEMIMAGE_TARGET) \ + $(INSTALLED_RAMDISK_TARGET) \ + $(INSTALLED_BOOTIMAGE_TARGET) \ + $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \ + $(INSTALLED_RADIOIMAGE_TARGET) \ + $(INSTALLED_DEBUG_RAMDISK_TARGET) \ + $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \ + $(INSTALLED_RECOVERYIMAGE_TARGET) \ + $(INSTALLED_VBMETAIMAGE_TARGET) \ + $(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET) \ + $(INSTALLED_VBMETA_VENDORIMAGE_TARGET) \ + $(INSTALLED_USERDATAIMAGE_TARGET) \ + $(INSTALLED_CACHEIMAGE_TARGET) \ + $(INSTALLED_BPTIMAGE_TARGET) \ + $(INSTALLED_VENDORIMAGE_TARGET) \ + $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \ + $(INSTALLED_ODMIMAGE_TARGET) \ + $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \ + $(INSTALLED_ODM_DLKMIMAGE_TARGET) \ + $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \ + $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \ + $(INSTALLED_PRODUCTIMAGE_TARGET) \ + $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) \ + $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \ + $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) \ + $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET) \ + $(INSTALLED_FILES_FILE) \ + $(INSTALLED_FILES_JSON) \ + $(INSTALLED_FILES_FILE_VENDOR) \ + $(INSTALLED_FILES_JSON_VENDOR) \ + $(INSTALLED_FILES_FILE_ODM) \ + $(INSTALLED_FILES_JSON_ODM) \ + $(INSTALLED_FILES_FILE_VENDOR_DLKM) \ + $(INSTALLED_FILES_JSON_VENDOR_DLKM) \ + $(INSTALLED_FILES_FILE_ODM_DLKM) \ + $(INSTALLED_FILES_JSON_ODM_DLKM) \ + $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \ + $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \ + $(INSTALLED_FILES_FILE_PRODUCT) \ + $(INSTALLED_FILES_JSON_PRODUCT) \ + $(INSTALLED_FILES_FILE_SYSTEM_EXT) \ + $(INSTALLED_FILES_JSON_SYSTEM_EXT) \ + $(INSTALLED_FILES_FILE_SYSTEMOTHER) \ + $(INSTALLED_FILES_JSON_SYSTEMOTHER) \ + $(INSTALLED_FILES_FILE_RAMDISK) \ + $(INSTALLED_FILES_JSON_RAMDISK) \ + $(INSTALLED_FILES_FILE_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \ + $(INSTALLED_FILES_FILE_ROOT) \ + $(INSTALLED_FILES_JSON_ROOT) \ + $(INSTALLED_FILES_FILE_RECOVERY) \ + $(INSTALLED_FILES_JSON_RECOVERY) \ + $(INSTALLED_ANDROID_INFO_TXT_TARGET) + +# The droidcore target depends on the droidcore-unbundled subset and any other +# targets for a non-unbundled (full source) full system build. +.PHONY: droidcore +droidcore: droidcore-unbundled + +# dist_files only for putting your library into the dist directory with a full build. +.PHONY: dist_files + +$(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json) +$(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json) + +.PHONY: apps_only +ifeq ($(HOST_OS),darwin) + # Mac only supports building host modules + droid_targets: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) dist_files + +else ifneq ($(TARGET_BUILD_APPS),) + # If this build is just for apps, only build apps and not the full system by default. + + # Dist the installed files if they exist, except the installed symlinks. dist-for-goals emits + # `cp src dest` commands, which will fail to copy dangling symlinks. + apps_only_installed_files := $(foreach m,$(unbundled_build_modules),\ + $(filter-out $(ALL_MODULES.$(m).INSTALLED_SYMLINKS),$(ALL_MODULES.$(m).INSTALLED))) + $(call dist-for-goals,apps_only, $(apps_only_installed_files)) + + # Dist the bundle files if they exist. + apps_only_bundle_files := $(foreach m,$(unbundled_build_modules),\ + $(if $(ALL_MODULES.$(m).BUNDLE),$(ALL_MODULES.$(m).BUNDLE):$(m)-base.zip)) + $(call dist-for-goals,apps_only, $(apps_only_bundle_files)) + + # Dist the lint reports if they exist. + apps_only_lint_report_files := $(foreach m,$(unbundled_build_modules),\ + $(foreach report,$(ALL_MODULES.$(m).LINT_REPORTS),\ + $(report):$(m)-$(notdir $(report)))) + .PHONY: lint-check + lint-check: $(foreach f, $(apps_only_lint_report_files), $(call word-colon,1,$(f))) + $(call dist-for-goals,lint-check, $(apps_only_lint_report_files)) + + # For uninstallable modules such as static Java library, we have to dist the built file, + # as . + apps_only_dist_built_files := $(foreach m,$(unbundled_build_modules),$(if $(ALL_MODULES.$(m).INSTALLED),,\ + $(if $(ALL_MODULES.$(m).BUILT),$(ALL_MODULES.$(m).BUILT):$(m)$(suffix $(ALL_MODULES.$(m).BUILT)))\ + $(if $(ALL_MODULES.$(m).AAR),$(ALL_MODULES.$(m).AAR):$(m).aar)\ + )) + $(call dist-for-goals,apps_only, $(apps_only_dist_built_files)) + + ifeq ($(EMMA_INSTRUMENT),true) + $(JACOCO_REPORT_CLASSES_ALL) : $(apps_only_installed_files) + $(call dist-for-goals,apps_only, $(JACOCO_REPORT_CLASSES_ALL)) + endif + + $(PROGUARD_DICT_ZIP) : $(apps_only_installed_files) + $(call dist-for-goals-with-filenametag,apps_only, $(PROGUARD_DICT_ZIP) $(PROGUARD_DICT_ZIP) $(PROGUARD_DICT_MAPPING)) + $(call declare-container-license-deps,$(PROGUARD_DICT_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/) + + $(PROGUARD_USAGE_ZIP) : $(apps_only_installed_files) + $(call dist-for-goals-with-filenametag,apps_only, $(PROGUARD_USAGE_ZIP)) + $(call declare-container-license-deps,$(PROGUARD_USAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/) + + $(SYMBOLS_ZIP) : $(apps_only_installed_files) + $(call dist-for-goals-with-filenametag,apps_only, $(SYMBOLS_ZIP) $(SYMBOLS_MAPPING)) + $(call declare-container-license-deps,$(SYMBOLS_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/) + + $(COVERAGE_ZIP) : $(apps_only_installed_files) + $(call dist-for-goals,apps_only, $(COVERAGE_ZIP)) + $(call declare-container-license-deps,$(COVERAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/) + +apps_only: $(unbundled_build_modules) + +droid_targets: apps_only + +# NOTICE files for a apps_only build +$(eval $(call html-notice-rule,$(target_notice_file_html_or_xml),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/)) + +$(eval $(call text-notice-rule,$(target_notice_file_txt),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/)) + +$(call declare-0p-target,$(target_notice_file_txt)) +$(call declare-0p-target,$(target_notice_html_or_xml)) + + +else ifeq ($(TARGET_BUILD_UNBUNDLED),$(TARGET_BUILD_UNBUNDLED_IMAGE)) + + # Truth table for entering this block of code: + # TARGET_BUILD_UNBUNDLED | TARGET_BUILD_UNBUNDLED_IMAGE | Action + # -----------------------|------------------------------|------------------------- + # not set | not set | droidcore path + # not set | true | invalid + # true | not set | skip + # true | true | droidcore-unbundled path + + # We dist the following targets only for droidcore full build. These items + # can include java-related targets that would cause building framework java + # sources in a droidcore full build. + + $(call dist-for-goals, droidcore, \ + $(BUILT_OTATOOLS_PACKAGE) \ + $(APPCOMPAT_ZIP) \ + $(DEXPREOPT_TOOLS_ZIP) \ + ) + + # We dist the following targets for droidcore-unbundled (and droidcore since + # droidcore depends on droidcore-unbundled). The droidcore-unbundled target + # is a subset of droidcore. It can be used used for an unbundled build to + # avoid disting targets that would cause building framework java sources, + # which we want to avoid in an unbundled build. + + $(call dist-for-goals-with-filenametag, droidcore-unbundled, \ + $(INTERNAL_UPDATE_PACKAGE_TARGET) \ + $(INTERNAL_OTA_PACKAGE_TARGET) \ + $(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET) \ + $(BUILT_RAMDISK_16K_TARGET) \ + $(BUILT_KERNEL_16K_TARGET) \ + $(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET) \ + $(SYMBOLS_ZIP) \ + $(SYMBOLS_MAPPING) \ + $(PROGUARD_DICT_ZIP) \ + $(PROGUARD_DICT_MAPPING) \ + $(PROGUARD_USAGE_ZIP) \ + $(BUILT_TARGET_FILES_PACKAGE) \ + ) + + $(call dist-for-goals, droidcore-unbundled, \ + $(INTERNAL_OTA_METADATA) \ + $(COVERAGE_ZIP) \ + $(INSTALLED_FILES_FILE) \ + $(INSTALLED_FILES_JSON) \ + $(INSTALLED_FILES_FILE_VENDOR) \ + $(INSTALLED_FILES_JSON_VENDOR) \ + $(INSTALLED_FILES_FILE_ODM) \ + $(INSTALLED_FILES_JSON_ODM) \ + $(INSTALLED_FILES_FILE_VENDOR_DLKM) \ + $(INSTALLED_FILES_JSON_VENDOR_DLKM) \ + $(INSTALLED_FILES_FILE_ODM_DLKM) \ + $(INSTALLED_FILES_JSON_ODM_DLKM) \ + $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \ + $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \ + $(INSTALLED_FILES_FILE_PRODUCT) \ + $(INSTALLED_FILES_JSON_PRODUCT) \ + $(INSTALLED_FILES_FILE_SYSTEM_EXT) \ + $(INSTALLED_FILES_JSON_SYSTEM_EXT) \ + $(INSTALLED_FILES_FILE_SYSTEMOTHER) \ + $(INSTALLED_FILES_JSON_SYSTEMOTHER) \ + $(INSTALLED_FILES_FILE_RECOVERY) \ + $(INSTALLED_FILES_JSON_RECOVERY) \ + $(INSTALLED_BUILD_PROP_TARGET):build.prop \ + $(INSTALLED_VENDOR_BUILD_PROP_TARGET):build.prop-vendor \ + $(INSTALLED_PRODUCT_BUILD_PROP_TARGET):build.prop-product \ + $(INSTALLED_ODM_BUILD_PROP_TARGET):build.prop-odm \ + $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET):build.prop-system_ext \ + $(INSTALLED_RAMDISK_BUILD_PROP_TARGET):build.prop-ramdisk \ + $(INSTALLED_ANDROID_INFO_TXT_TARGET) \ + $(INSTALLED_MISC_INFO_TARGET) \ + $(INSTALLED_RAMDISK_TARGET) \ + $(DEXPREOPT_CONFIG_ZIP) \ + ) + + # Put a copy of the radio/bootloader files in the dist dir. + $(foreach f,$(INSTALLED_RADIOIMAGE_TARGET), \ + $(call dist-for-goals, droidcore-unbundled, $(f))) + + ifneq ($(ANDROID_BUILD_EMBEDDED),true) + $(call dist-for-goals-with-filenametag, droidcore, \ + $(APPS_ZIP) \ + $(INTERNAL_EMULATOR_PACKAGE_TARGET) \ + ) + endif + + $(call dist-for-goals, droidcore-unbundled, \ + $(INSTALLED_FILES_FILE_ROOT) \ + $(INSTALLED_FILES_JSON_ROOT) \ + ) + + $(call dist-for-goals, droidcore-unbundled, \ + $(INSTALLED_FILES_FILE_RAMDISK) \ + $(INSTALLED_FILES_JSON_RAMDISK) \ + $(INSTALLED_FILES_FILE_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \ + $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \ + $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \ + $(INSTALLED_DEBUG_RAMDISK_TARGET) \ + $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \ + $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) \ + $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \ + $(INSTALLED_VENDOR_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \ + $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \ + ) + + ifeq ($(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST),true) + $(call dist-for-goals, droidcore-unbundled, $(INSTALLED_BOOTIMAGE_TARGET)) + endif + + ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) + $(call dist-for-goals, droidcore-unbundled, \ + $(recovery_ramdisk) \ + ) + endif + + ifeq ($(EMMA_INSTRUMENT),true) + $(call dist-for-goals, dist_files, $(JACOCO_REPORT_CLASSES_ALL)) + endif + + # Put XML formatted API files in the dist dir. + $(TARGET_OUT_COMMON_INTERMEDIATES)/api.xml: $(call java-lib-files,$(ANDROID_PUBLIC_STUBS)) $(APICHECK) + $(TARGET_OUT_COMMON_INTERMEDIATES)/system-api.xml: $(call java-lib-files,$(ANDROID_SYSTEM_STUBS)) $(APICHECK) + $(TARGET_OUT_COMMON_INTERMEDIATES)/module-lib-api.xml: $(call java-lib-files,$(ANDROID_MODULE_LIB_STUBS)) $(APICHECK) + $(TARGET_OUT_COMMON_INTERMEDIATES)/system-server-api.xml: $(call java-lib-files,$(ANDROID_SYSTEM_SERVER_STUBS)) $(APICHECK) + $(TARGET_OUT_COMMON_INTERMEDIATES)/test-api.xml: $(call java-lib-files,$(ANDROID_TEST_STUBS)) $(APICHECK) + + api_xmls := $(addprefix $(TARGET_OUT_COMMON_INTERMEDIATES)/,api.xml system-api.xml module-lib-api.xml system-server-api.xml test-api.xml) + $(api_xmls): + $(hide) echo "Converting API file to XML: $@" + $(hide) mkdir -p $(dir $@) + $(hide) $(APICHECK_COMMAND) --input-api-jar $< --api-xml $@ + + $(foreach xml,$(sort $(api_xmls)),$(call declare-1p-target,$(xml),)) + + $(call dist-for-goals, dist_files, $(api_xmls)) + api_xmls := + + ifdef CLANG_COVERAGE + $(foreach f,$(SOONG_NDK_API_XML), \ + $(call dist-for-goals,droidcore,$(f):ndk_apis/$(notdir $(f)))) + $(foreach f,$(SOONG_CC_API_XML), \ + $(call dist-for-goals,droidcore,$(f):cc_apis/$(notdir $(f)))) + endif + + # For full system build (whether unbundled or not), we configure + # droid_targets to depend on droidcore-unbundled, which will set up the full + # system dependencies and also dist the subset of targets that correspond to + # an unbundled build (exclude building some framework sources). + + droid_targets: droidcore-unbundled + + ifeq (,$(TARGET_BUILD_UNBUNDLED_IMAGE)) + + # If we're building a full system (including the framework sources excluded + # by droidcore-unbundled), we configure droid_targets also to depend on + # droidcore, which includes all dist for droidcore, and will build the + # necessary framework sources. + + droid_targets: droidcore dist_files + + endif + +endif # TARGET_BUILD_UNBUNDLED == TARGET_BUILD_UNBUNDLED_IMAGE + +.PHONY: docs +docs: $(ALL_DOCS) + +.PHONY: sdk sdk_addon +ifeq ($(HOST_OS),linux) +ALL_SDK_TARGETS := $(INTERNAL_SDK_TARGET) +sdk: $(ALL_SDK_TARGETS) +$(call dist-for-goals-with-filenametag,sdk,$(ALL_SDK_TARGETS)) +$(call dist-for-goals,sdk,$(INSTALLED_BUILD_PROP_TARGET)) +endif + +# umbrella targets to assit engineers in verifying builds +.PHONY: java native target host java-host java-target native-host native-target \ + java-host-tests java-target-tests native-host-tests native-target-tests \ + java-tests native-tests host-tests target-tests tests java-dex \ + native-host-cross +# some synonyms +.PHONY: host-java target-java host-native target-native \ + target-java-tests target-native-tests +host-java : java-host +target-java : java-target +host-native : native-host +target-native : native-target +target-java-tests : java-target-tests +target-native-tests : native-target-tests +tests : host-tests target-tests + +# Phony target to run all java compilations that use javac +.PHONY: javac-check + +.PHONY: findbugs +findbugs: $(INTERNAL_FINDBUGS_HTML_TARGET) $(INTERNAL_FINDBUGS_XML_TARGET) + +.PHONY: check-elf-files +check-elf-files: + +.PHONY: dump-files +dump-files: + @echo "Target files for $(TARGET_PRODUCT)-$(TARGET_BUILD_VARIANT) ($(INTERNAL_PRODUCT)):" + @echo $(sort $(patsubst $(PRODUCT_OUT)/%,%,$(filter $(PRODUCT_OUT)/%,$(modules_to_install)))) | tr -s ' ' '\n' + @echo Successfully dumped product target file list. + +.PHONY: nothing +nothing: + @echo Successfully read the makefiles. + +.PHONY: tidy_only +tidy_only: + @echo Successfully make tidy_only. + +ndk: $(SOONG_OUT_DIR)/ndk.timestamp +.PHONY: ndk + +# Checks that allowed_deps.txt remains up to date +ifneq ($(UNSAFE_DISABLE_APEX_ALLOWED_DEPS_CHECK),true) + droidcore: ${APEX_ALLOWED_DEPS_CHECK} +endif + +# Create a license metadata rule per module. Could happen in base_rules.mk or +# notice_files.mk; except, it has to happen after fix-notice-deps to avoid +# missing dependency errors. +$(call build-license-metadata) + +# Generate SBOM in SPDX format +product_copy_files_without_owner := $(foreach pcf,$(PRODUCT_COPY_FILES),$(call word-colon,1,$(pcf)):$(call word-colon,2,$(pcf))) +ifeq ($(TARGET_BUILD_APPS),) +dest_files_without_source := $(sort $(foreach pcf,$(product_copy_files_without_owner),$(if $(wildcard $(call word-colon,1,$(pcf))),,$(call word-colon,2,$(pcf))))) +dest_files_without_source := $(addprefix $(PRODUCT_OUT)/,$(dest_files_without_source)) +filter_out_files := \ + $(PRODUCT_OUT)/apex/% \ + $(PRODUCT_OUT)/fake_packages/% \ + $(PRODUCT_OUT)/testcases/% \ + $(dest_files_without_source) +# Check if each partition image is built, if not filter out all its installed files +# Also check if a partition uses prebuilt image file, save the info if prebuilt image is used. +PREBUILT_PARTITION_COPY_FILES := +# product.img +ifndef BUILDING_PRODUCT_IMAGE +filter_out_files += $(PRODUCT_OUT)/product/% +ifdef BOARD_PREBUILT_PRODUCTIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_PRODUCTIMAGE):$(INSTALLED_PRODUCTIMAGE_TARGET) +endif +endif + +# system.img +ifndef BUILDING_SYSTEM_IMAGE +filter_out_files += $(PRODUCT_OUT)/system/% +endif +# system_dlkm.img +ifndef BUILDING_SYSTEM_DLKM_IMAGE +filter_out_files += $(PRODUCT_OUT)/system_dlkm/% +ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_SYSTEM_DLKMIMAGE):$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) +endif +endif +# system_ext.img +ifndef BUILDING_SYSTEM_EXT_IMAGE +filter_out_files += $(PRODUCT_OUT)/system_ext/% +ifdef BOARD_PREBUILT_SYSTEM_EXTIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_SYSTEM_EXTIMAGE):$(INSTALLED_SYSTEM_EXTIMAGE_TARGET) +endif +endif +# system_other.img +ifndef BUILDING_SYSTEM_OTHER_IMAGE +filter_out_files += $(PRODUCT_OUT)/system_other/% +endif + +# odm.img +ifndef BUILDING_ODM_IMAGE +filter_out_files += $(PRODUCT_OUT)/odm/% +ifdef BOARD_PREBUILT_ODMIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_ODMIMAGE):$(INSTALLED_ODMIMAGE_TARGET) +endif +endif +# odm_dlkm.img +ifndef BUILDING_ODM_DLKM_IMAGE +filter_out_files += $(PRODUCT_OUT)/odm_dlkm/% +ifdef BOARD_PREBUILT_ODM_DLKMIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_ODM_DLKMIMAGE):$(INSTALLED_ODM_DLKMIMAGE_TARGET) +endif +endif + +# vendor.img +ifndef BUILDING_VENDOR_IMAGE +filter_out_files += $(PRODUCT_OUT)/vendor/% +ifdef BOARD_PREBUILT_VENDORIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_VENDORIMAGE):$(INSTALLED_VENDORIMAGE_TARGET) +endif +endif +# vendor_dlkm.img +ifndef BUILDING_VENDOR_DLKM_IMAGE +filter_out_files += $(PRODUCT_OUT)/vendor_dlkm/% +ifdef BOARD_PREBUILT_VENDOR_DLKMIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_VENDOR_DLKMIMAGE):$(INSTALLED_VENDOR_DLKMIMAGE_TARGET) +endif +endif + +# cache.img +ifndef BUILDING_CACHE_IMAGE +filter_out_files += $(PRODUCT_OUT)/cache/% +endif + +# boot.img +ifndef BUILDING_BOOT_IMAGE +ifdef BOARD_PREBUILT_BOOTIMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_BOOTIMAGE):$(INSTALLED_BOOTIMAGE_TARGET) +endif +endif +# init_boot.img +ifndef BUILDING_INIT_BOOT_IMAGE +ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE +PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_INIT_BOOT_IMAGE):$(INSTALLED_INIT_BOOT_IMAGE_TARGET) +endif +endif + +# ramdisk.img +ifndef BUILDING_RAMDISK_IMAGE +filter_out_files += $(PRODUCT_OUT)/ramdisk/% +endif + +# recovery.img +ifndef INSTALLED_RECOVERYIMAGE_TARGET +filter_out_files += $(PRODUCT_OUT)/recovery/% +endif + +installed_files := $(sort $(filter-out $(filter_out_files),$(filter $(PRODUCT_OUT)/%,$(modules_to_install)))) +else +installed_files := $(apps_only_installed_files) +endif # TARGET_BUILD_APPS + +# sbom-metadata.csv contains all raw data collected in Make for generating SBOM in generate-sbom.py. +# There are multiple columns and each identifies the source of an installed file for a specific case. +# The columns and their uses are described as below: +# installed_file: the file path on device, e.g. /product/app/Browser2/Browser2.apk +# module_path: the path of the module that generates the installed file, e.g. packages/apps/Browser2 +# soong_module_type: Soong module type, e.g. android_app, cc_binary +# is_prebuilt_make_module: Y, if the installed file is from a prebuilt Make module, see prebuilt_internal.mk +# product_copy_files: the installed file is from variable PRODUCT_COPY_FILES, e.g. device/google/cuttlefish/shared/config/init.product.rc:product/etc/init/init.rc +# kernel_module_copy_files: the installed file is from variable KERNEL_MODULE_COPY_FILES, similar to product_copy_files +# is_platform_generated: this is an aggregated value including some small cases instead of adding more columns. It is set to Y if any case is Y +# is_build_prop: build.prop in each partition, see sysprop.mk. +# is_notice_file: NOTICE.xml.gz in each partition, see Makefile. +# is_dexpreopt_image_profile: see the usage of DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED in Soong and Make +# is_product_system_other_avbkey: see INSTALLED_PRODUCT_SYSTEM_OTHER_AVBKEY_TARGET +# is_system_other_odex_marker: see INSTALLED_SYSTEM_OTHER_ODEX_MARKER +# is_event_log_tags_file: see variable event_log_tags_file in Makefile +# is_kernel_modules_blocklist: modules.blocklist created for _dlkm partitions, see macro build-image-kernel-modules-dir in Makefile. +# is_fsverity_build_manifest_apk: BuildManifest.apk files for system and system_ext partition, see ALL_FSVERITY_BUILD_MANIFEST_APK in Makefile. +# is_linker_config: see SYSTEM_LINKER_CONFIG and vendor_linker_config_file in Makefile. +# build_output_path: the path of the built file, used to calculate checksum +# static_libraries/whole_static_libraries: list of module name of the static libraries the file links against, e.g. libclang_rt.builtins or libclang_rt.builtins_32 +# Info of all static libraries of all installed files are collected in variable _all_static_libs that is used to list all the static library files in sbom-metadata.csv. +# See the second foreach loop in the rule of sbom-metadata.csv for the detailed info of static libraries collected in _all_static_libs. +# is_static_lib: whether the file is a static library + +metadata_list := $(OUT_DIR)/.module_paths/METADATA.list +metadata_files := $(subst $(newline),$(space),$(file <$(metadata_list))) +$(PRODUCT_OUT)/sbom-metadata.csv: + rm -f $@ + echo 'installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib' >> $@ + $(eval _all_static_libs :=) + $(foreach f,$(installed_files),\ + $(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \ + $(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$f)) \ + $(eval _build_output_path := $(PRODUCT_OUT)/$(_path_on_device)) \ + $(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) \ + $(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) \ + $(eval _is_prebuilt_make_module := $(ALL_MODULES.$(_module_name).IS_PREBUILT_MAKE_MODULE)) \ + $(eval _product_copy_files := $(sort $(filter %:$(_path_on_device),$(product_copy_files_without_owner)))) \ + $(eval _kernel_module_copy_files := $(sort $(filter %$(_path_on_device),$(KERNEL_MODULE_COPY_FILES)))) \ + $(eval _is_build_prop := $(call is-build-prop,$f)) \ + $(eval _is_notice_file := $(call is-notice-file,$f)) \ + $(eval _is_dexpreopt_image_profile := $(if $(filter %:/$(_path_on_device),$(DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED)),Y)) \ + $(eval _is_product_system_other_avbkey := $(if $(findstring $f,$(INSTALLED_PRODUCT_SYSTEM_OTHER_AVBKEY_TARGET)),Y)) \ + $(eval _is_event_log_tags_file := $(if $(findstring $f,$(event_log_tags_file)),Y)) \ + $(eval _is_system_other_odex_marker := $(if $(findstring $f,$(INSTALLED_SYSTEM_OTHER_ODEX_MARKER)),Y)) \ + $(eval _is_kernel_modules_blocklist := $(if $(findstring $f,$(ALL_KERNEL_MODULES_BLOCKLIST)),Y)) \ + $(eval _is_fsverity_build_manifest_apk := $(if $(findstring $f,$(ALL_FSVERITY_BUILD_MANIFEST_APK)),Y)) \ + $(eval _is_linker_config := $(if $(findstring $f,$(SYSTEM_LINKER_CONFIG) $(vendor_linker_config_file)),Y)) \ + $(eval _is_partition_compat_symlink := $(if $(findstring $f,$(PARTITION_COMPAT_SYMLINKS)),Y)) \ + $(eval _is_flags_file := $(if $(findstring $f, $(ALL_FLAGS_FILES)),Y)) \ + $(eval _is_rootdir_symlink := $(if $(findstring $f, $(ALL_ROOTDIR_SYMLINKS)),Y)) \ + $(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)$(_is_partition_compat_symlink)$(_is_flags_file)$(_is_rootdir_symlink)) \ + $(eval _static_libs := $(ALL_INSTALLED_FILES.$f.STATIC_LIBRARIES)) \ + $(eval _whole_static_libs := $(ALL_INSTALLED_FILES.$f.WHOLE_STATIC_LIBRARIES)) \ + $(foreach l,$(_static_libs),$(eval _all_static_libs += $l:$(strip $(sort $(ALL_MODULES.$l.PATH))):$(strip $(sort $(ALL_MODULES.$l.SOONG_MODULE_TYPE))):$(ALL_STATIC_LIBRARIES.$l.BUILT_FILE))) \ + $(foreach l,$(_whole_static_libs),$(eval _all_static_libs += $l:$(strip $(sort $(ALL_MODULES.$l.PATH))):$(strip $(sort $(ALL_MODULES.$l.SOONG_MODULE_TYPE))):$(ALL_STATIC_LIBRARIES.$l.BUILT_FILE))) \ + echo '/$(_path_on_device),$(_module_path),$(_soong_module_type),$(_is_prebuilt_make_module),$(_product_copy_files),$(_kernel_module_copy_files),$(_is_platform_generated),$(_build_output_path),$(_static_libs),$(_whole_static_libs),' >> $@; \ + ) + $(foreach l,$(sort $(_all_static_libs)), \ + $(eval _lib_stem := $(call word-colon,1,$l)) \ + $(eval _module_path := $(call word-colon,2,$l)) \ + $(eval _soong_module_type := $(call word-colon,3,$l)) \ + $(eval _built_file := $(call word-colon,4,$l)) \ + $(eval _static_libs := $(ALL_STATIC_LIBRARIES.$l.STATIC_LIBRARIES)) \ + $(eval _whole_static_libs := $(ALL_STATIC_LIBRARIES.$l.WHOLE_STATIC_LIBRARIES)) \ + $(eval _is_static_lib := Y) \ + echo '$(_lib_stem).a,$(_module_path),$(_soong_module_type),,,,,$(_built_file),$(_static_libs),$(_whole_static_libs),$(_is_static_lib)' >> $@; \ + ) + +# (TODO: b/272358583 find another way of always rebuilding sbom.spdx) +# Remove the always_dirty_file.txt whenever the makefile is evaluated +$(shell rm -f $(PRODUCT_OUT)/always_dirty_file.txt) +$(PRODUCT_OUT)/always_dirty_file.txt: + touch $@ + +.PHONY: sbom +ifeq ($(TARGET_BUILD_APPS),) +sbom: $(PRODUCT_OUT)/sbom.spdx.json +$(PRODUCT_OUT)/sbom.spdx.json: $(PRODUCT_OUT)/sbom.spdx +$(PRODUCT_OUT)/sbom.spdx: $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM) $(installed_files) $(metadata_list) $(metadata_files) $(PRODUCT_OUT)/always_dirty_file.txt + rm -rf $@ + $(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json + +$(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json) +else +# Create build rules for generating SBOMs of unbundled APKs and APEXs +# $1: sbom file +# $2: sbom fragment file +# $3: installed file +# $4: sbom-metadata.csv file +define generate-app-sbom +$(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$(3))) +$(eval _module_name := $(ALL_INSTALLED_FILES.$(3))) +$(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) +$(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) +$(eval _dep_modules := $(filter %.$(_module_name),$(ALL_MODULES)) $(filter %.$(_module_name)$(TARGET_2ND_ARCH_MODULE_SUFFIX),$(ALL_MODULES))) +$(eval _is_apex := $(filter %.apex,$(3))) + +$(4): + rm -rf $$@ + echo installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib >> $$@ + echo /$(_path_on_device),$(_module_path),$(_soong_module_type),,,,,$(3),,, >> $$@ + $(if $(filter %.apex,$(3)),\ + $(foreach m,$(_dep_modules),\ + echo $(patsubst $(PRODUCT_OUT)/apex/$(_module_name)/%,%,$(ALL_MODULES.$m.INSTALLED)),$(sort $(ALL_MODULES.$m.PATH)),$(sort $(ALL_MODULES.$m.SOONG_MODULE_TYPE)),,,,,$(strip $(ALL_MODULES.$m.BUILT)),,, >> $$@;)) + +$(2): $(1) +$(1): $(4) $(3) $(GEN_SBOM) $(installed_files) $(metadata_list) $(metadata_files) + rm -rf $$@ + $(GEN_SBOM) --output_file $$@ --metadata $(4) --build_version $$(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json $(if $(filter %.apk,$(3)),--unbundled_apk,--unbundled_apex) +endef + +apps_only_sbom_files := +apps_only_fragment_files := +$(foreach f,$(filter %.apk %.apex,$(installed_files)), \ + $(eval _metadata_csv_file := $(patsubst %,%-sbom-metadata.csv,$f)) \ + $(eval _sbom_file := $(patsubst %,%.spdx.json,$f)) \ + $(eval _fragment_file := $(patsubst %,%-fragment.spdx,$f)) \ + $(eval apps_only_sbom_files += $(_sbom_file)) \ + $(eval apps_only_fragment_files += $(_fragment_file)) \ + $(eval $(call generate-app-sbom,$(_sbom_file),$(_fragment_file),$f,$(_metadata_csv_file))) \ +) + +sbom: $(apps_only_sbom_files) + +$(foreach f,$(apps_only_fragment_files),$(eval apps_only_fragment_dist_files += :sbom/$(notdir $f))) +$(foreach f,$(apps_only_sbom_files),$(eval apps_only_sbom_dist_files += :sbom/$(notdir $f))) +$(call dist-for-goals,apps_only,$(join $(apps_only_sbom_files),$(apps_only_sbom_dist_files)) $(join $(apps_only_fragment_files),$(apps_only_fragment_dist_files))) +endif + +$(call dist-write-file,$(KATI_PACKAGE_MK_DIR)/dist.mk) + +$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing legacy Make module rules ...) diff --git a/aosp/build/make/core/product_config.mk b/aosp/build/make/core/product_config.mk new file mode 100644 index 000000000..c56538409 --- /dev/null +++ b/aosp/build/make/core/product_config.mk @@ -0,0 +1,666 @@ +# +# Copyright (C) 2008 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. +# + +# --------------------------------------------------------------- +# Generic functions +# TODO: Move these to definitions.make once we're able to include +# definitions.make before config.make. + +########################################################### +## Return non-empty if $(1) is a C identifier; i.e., if it +## matches /^[a-zA-Z_][a-zA-Z0-9_]*$/. We do this by first +## making sure that it isn't empty and doesn't start with +## a digit, then by removing each valid character. If the +## final result is empty, then it was a valid C identifier. +## +## $(1): word to check +########################################################### + +_ici_digits := 0 1 2 3 4 5 6 7 8 9 +_ici_alphaunderscore := \ + a b c d e f g h i j k l m n o p q r s t u v w x y z \ + A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ +define is-c-identifier +$(strip \ + $(if $(1), \ + $(if $(filter $(addsuffix %,$(_ici_digits)),$(1)), \ + , \ + $(eval w := $(1)) \ + $(foreach c,$(_ici_digits) $(_ici_alphaunderscore), \ + $(eval w := $(subst $(c),,$(w))) \ + ) \ + $(if $(w),,TRUE) \ + $(eval w :=) \ + ) \ + ) \ + ) +endef + +# TODO: push this into the combo files; unfortunately, we don't even +# know HOST_OS at this point. +trysed := $(shell echo a | sed -E -e 's/a/b/' 2>/dev/null) +ifeq ($(trysed),b) + SED_EXTENDED := sed -E +else + trysed := $(shell echo c | sed -r -e 's/c/d/' 2>/dev/null) + ifeq ($(trysed),d) + SED_EXTENDED := sed -r + else + $(error Unknown sed version) + endif +endif + +########################################################### +## List all of the files in a subdirectory in a format +## suitable for PRODUCT_COPY_FILES and +## PRODUCT_SDK_ADDON_COPY_FILES +## +## $(1): Glob to match file name +## $(2): Source directory +## $(3): Target base directory +########################################################### + +define find-copy-subdir-files +$(shell find $(2) -name "$(1)" -type f | $(SED_EXTENDED) "s:($(2)/?(.*)):\\1\\:$(3)/\\2:" | sed "s://:/:g" | sort) +endef + +# +# Convert file file to the PRODUCT_COPY_FILES/PRODUCT_SDK_ADDON_COPY_FILES +# format: for each file F return $(F):$(PREFIX)/$(notdir $(F)) +# $(1): files list +# $(2): prefix + +define copy-files +$(foreach f,$(1),$(f):$(2)/$(notdir $(f))) +endef + +# +# Convert the list of file names to the list of PRODUCT_COPY_FILES items +# $(1): from pattern +# $(2): to pattern +# $(3): file names +# E.g., calling product-copy-files-by-pattern with +# (from/%, to/%, a b) +# returns +# from/a:to/a from/b:to/b +define product-copy-files-by-pattern +$(join $(patsubst %,$(1),$(3)),$(patsubst %,:$(2),$(3))) +endef + +# Return empty unless the board matches +define is-board-platform2 +$(filter $(1), $(TARGET_BOARD_PLATFORM)) +endef + +# Return empty unless the board is in the list +define is-board-platform-in-list2 +$(filter $(1),$(TARGET_BOARD_PLATFORM)) +endef + +# Return empty unless the board is QCOM +define is-vendor-board-qcom +$(if $(strip $(TARGET_BOARD_PLATFORM) $(QCOM_BOARD_PLATFORMS)),$(filter $(TARGET_BOARD_PLATFORM),$(QCOM_BOARD_PLATFORMS)),\ + $(error both TARGET_BOARD_PLATFORM=$(TARGET_BOARD_PLATFORM) and QCOM_BOARD_PLATFORMS=$(QCOM_BOARD_PLATFORMS))) +endef + +# --------------------------------------------------------------- +# Check for obsolete PRODUCT- and APP- goals +ifeq ($(CALLED_FROM_SETUP),true) +product_goals := $(strip $(filter PRODUCT-%,$(MAKECMDGOALS))) +ifdef product_goals + $(error The PRODUCT-* goal is no longer supported. Use `TARGET_PRODUCT= m droid` instead) +endif +unbundled_goals := $(strip $(filter APP-%,$(MAKECMDGOALS))) +ifdef unbundled_goals + $(error The APP-* goal is no longer supported. Use `TARGET_BUILD_APPS="" m droid` instead) +endif # unbundled_goals +endif + +# Default to building dalvikvm on hosts that support it... +ifeq ($(HOST_OS),linux) +# ... or if the if the option is already set +ifeq ($(WITH_HOST_DALVIK),) + WITH_HOST_DALVIK := true +endif +endif + +# --------------------------------------------------------------- +# Include the product definitions. +# We need to do this to translate TARGET_PRODUCT into its +# underlying TARGET_DEVICE before we start defining any rules. +# +include $(BUILD_SYSTEM)/node_fns.mk +include $(BUILD_SYSTEM)/product.mk + +# Read all product definitions. +# +# Products are defined in AndroidProducts.mk files: +android_products_makefiles := $(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \ + $(SRC_TARGET_DIR)/product/AndroidProducts.mk + +# An AndroidProduct.mk file sets the following variables: +# PRODUCT_MAKEFILES specifies product makefiles. Each item in this list +# is either a :path/to/file.mk, or just path/to/ +# COMMON_LUNCH_CHOICES specifies - values to be shown +# in the `lunch` menu +# STARLARK_OPT_IN_PRODUCTS specifies products to use Starlark-based +# product configuration by default + +# Builds a list of first/second elements of each pair: +# $(call _first,a:A b:B,:) returns 'a b' +# $(call _second,a-A b-B,-) returns 'A B' +_first=$(filter-out $(2)%,$(subst $(2),$(space)$(2),$(1))) +_second=$(filter-out %$(2),$(subst $(2),$(2)$(space),$(1))) + +# Returns : pair from a PRODUCT_MAKEFILE item. +# If an item is :path/to/file.mk, return it as is, +# otherwise assume that an item is path/to/.mk and +# return :path/to/.mk +_product-spec=$(strip $(if $(findstring :,$(1)),$(1),$(basename $(notdir $(1))):$(1))) + +# Reads given AndroidProduct.mk file and sets the following variables: +# ap_product_paths -- the list of : pairs +# ap_common_lunch_choices -- the list of - items +# ap_products_using_starlark_config -- the list of products using starlark config +# In addition, validates COMMON_LUNCH_CHOICES and STARLARK_OPT_IN_PRODUCTS values +define _read-ap-file + $(eval PRODUCT_MAKEFILES :=) \ + $(eval COMMON_LUNCH_CHOICES :=) \ + $(eval STARLARK_OPT_IN_PRODUCTS := ) \ + $(eval ap_product_paths :=) \ + $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \ + $(eval include $(f)) \ + $(foreach p, $(PRODUCT_MAKEFILES),$(eval ap_product_paths += $(call _product-spec,$(p)))) \ + $(eval ap_common_lunch_choices := $(COMMON_LUNCH_CHOICES)) \ + $(eval ap_products_using_starlark_config := $(STARLARK_OPT_IN_PRODUCTS)) \ + $(eval _products := $(call _first,$(ap_product_paths),:)) \ + $(eval _bad := $(filter-out $(_products),$(call _first,$(ap_common_lunch_choices),-))) \ + $(if $(_bad),$(error COMMON_LUNCH_CHOICES contains products(s) not defined in this file: $(_bad))) \ + $(eval _bad := $(filter-out %-eng %-userdebug %-user,$(ap_common_lunch_choices))) \ + $(if $(_bad),$(error invalid variant in COMMON_LUNCH_CHOICES: $(_bad))) + $(eval _bad := $(filter-out $(_products),$(ap_products_using_starlark_config))) \ + $(if $(_bad),$(error STARLARK_OPT_IN_PRODUCTS contains product(s) not defined in this file: $(_bad))) +endef + +# Build cumulative lists of all product specs/lunch choices/Starlark-based products. +product_paths := +common_lunch_choices := +products_using_starlark_config := +$(foreach f,$(android_products_makefiles), \ + $(call _read-ap-file,$(f)) \ + $(eval product_paths += $(ap_product_paths)) \ + $(eval common_lunch_choices += $(ap_common_lunch_choices)) \ + $(eval products_using_starlark_config += $(ap_products_using_starlark_config)) \ +) + +# Dedup, extract product names, etc. +product_paths := $(sort $(product_paths)) +all_named_products := $(sort $(call _first,$(product_paths),:)) +current_product_makefile := $(call _second,$(filter $(TARGET_PRODUCT):%,$(product_paths)),:) +COMMON_LUNCH_CHOICES := $(sort $(common_lunch_choices)) + +# Check that there are no duplicate product names +$(foreach p,$(all_named_products), \ + $(if $(filter 1,$(words $(filter $(p):%,$(product_paths)))),, \ + $(error Product name must be unique, "$(p)" used by $(call _second,$(filter $(p):%,$(product_paths)),:)))) + +ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),) +_product_config_saved_KATI_ALLOW_RULES := $(.KATI_ALLOW_RULES) +.KATI_ALLOW_RULES := $(ALLOW_RULES_IN_PRODUCT_CONFIG) +endif + +ifeq (,$(current_product_makefile)) + $(error Cannot locate config makefile for product "$(TARGET_PRODUCT)") +endif + +ifneq (,$(filter $(TARGET_PRODUCT),$(products_using_starlark_config))) + RBC_PRODUCT_CONFIG := true +endif + +ifndef RBC_PRODUCT_CONFIG +$(call import-products, $(current_product_makefile)) +else + $(shell mkdir -p $(OUT_DIR)/rbc) + $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_product_config.mk) + + $(shell $(OUT_DIR)/mk2rbc \ + --mode=write -r --outdir $(OUT_DIR)/rbc \ + --launcher=$(OUT_DIR)/rbc/launcher.rbc \ + --input_variables=$(OUT_DIR)/rbc/make_vars_pre_product_config.mk \ + --makefile_list=$(OUT_DIR)/.module_paths/configuration.list \ + $(current_product_makefile)) + ifneq ($(.SHELLSTATUS),0) + $(error product configuration converter failed: $(.SHELLSTATUS)) + endif + + $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_product_config_results.mk \ + $(OUT_DIR)/rbcrun --mode=rbc $(OUT_DIR)/rbc/launcher.rbc) + ifneq ($(.SHELLSTATUS),0) + $(error product configuration runner failed: $(.SHELLSTATUS)) + endif + + include $(OUT_DIR)/rbc/rbc_product_config_results.mk +endif + +# This step was already handled in the RBC product configuration. +ifeq ($(RBC_PRODUCT_CONFIG)$(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),) +# Import all the products that have made artifact path requirements, so that we can verify +# the artifacts they produce. They might be intermediate makefiles instead of real products. +$(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\ + $(if $(filter-out $(makefile),$(PRODUCTS)),$(eval $(call import-products,$(makefile))))\ +) +endif + +INTERNAL_PRODUCT := $(current_product_makefile) +# Strip and assign the PRODUCT_ variables. +$(call strip-product-vars) + +# Quick check +$(check-current-product) + +ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),) +.KATI_ALLOW_RULES := $(_saved_KATI_ALLOW_RULES) +_product_config_saved_KATI_ALLOW_RULES := +endif + +############################################################################ + +current_product_makefile := + +############################################################################# +# Check product include tag allowlist +BLUEPRINT_INCLUDE_TAGS_ALLOWLIST := \ + com.android.mainline_go \ + com.android.mainline \ + mainline_module_prebuilt_nightly \ + mainline_module_prebuilt_monthly_release +.KATI_READONLY := BLUEPRINT_INCLUDE_TAGS_ALLOWLIST +$(foreach include_tag,$(PRODUCT_INCLUDE_TAGS), \ + $(if $(filter $(include_tag),$(BLUEPRINT_INCLUDE_TAGS_ALLOWLIST)),,\ + $(call pretty-error, $(include_tag) is not in BLUEPRINT_INCLUDE_TAGS_ALLOWLIST: $(BLUEPRINT_INCLUDE_TAGS_ALLOWLIST)))) +# Create default PRODUCT_INCLUDE_TAGS +ifeq (, $(PRODUCT_INCLUDE_TAGS)) +# Soong analysis is global: even though a module might not be relevant to a specific product (e.g. build_tools for aosp_arm), +# we still analyse it. +# This means that in setups where we two have two prebuilts of module_sdk, we need a "default" to use in analysis +# This should be a no-op in aosp and internal since no Android.bp file contains blueprint_package_includes +# Use the big android one and main-based prebuilts by default +PRODUCT_INCLUDE_TAGS += com.android.mainline mainline_module_prebuilt_nightly +endif + +# AOSP and Google products currently share the same `apex_contributions` in next. +# This causes issues when building -next-userdebug in main. +# Create a temporary allowlist to ignore the google apexes listed in `contents` of apex_contributions of `next` +# *for aosp products*. +# TODO(b/308187268): Remove this denylist mechanism +# Use PRODUCT_PACKAGES to determine if this is an aosp product. aosp products do not use google signed apexes. +ignore_apex_contributions := +ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES))) + ignore_apex_contributions := true +endif +ifeq (true,$(PRODUCT_MODULE_BUILD_FROM_SOURCE)) + ignore_apex_contributions := true +endif +ifeq (true, $(ignore_apex_contributions)) +PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS += \ + prebuilt_com.google.android.adservices \ + prebuilt_com.google.android.appsearch \ + prebuilt_com.google.android.art \ + prebuilt_com.google.android.btservices \ + prebuilt_com.google.android.configinfrastructure \ + prebuilt_com.google.android.conscrypt \ + prebuilt_com.google.android.devicelock \ + prebuilt_com.google.android.healthfitness \ + prebuilt_com.google.android.ipsec \ + prebuilt_com.google.android.media \ + prebuilt_com.google.android.mediaprovider \ + prebuilt_com.google.android.ondevicepersonalization \ + prebuilt_com.google.android.os.statsd \ + prebuilt_com.google.android.rkpd \ + prebuilt_com.google.android.scheduling \ + prebuilt_com.google.android.sdkext \ + prebuilt_com.google.android.tethering \ + prebuilt_com.google.android.uwb \ + prebuilt_com.google.android.wifi +endif + +############################################################################# + +# Quick check and assign default values + +TARGET_DEVICE := $(PRODUCT_DEVICE) + +# TODO: also keep track of things like "port", "land" in product files. + +# Figure out which resoure configuration options to use for this +# product. +# If CUSTOM_LOCALES contains any locales not already included +# in PRODUCT_LOCALES, add them to PRODUCT_LOCALES. +extra_locales := $(filter-out $(PRODUCT_LOCALES),$(CUSTOM_LOCALES)) +ifneq (,$(extra_locales)) + ifneq ($(CALLED_FROM_SETUP),true) + # Don't spam stdout, because envsetup.sh may be scraping values from it. + $(info Adding CUSTOM_LOCALES [$(extra_locales)] to PRODUCT_LOCALES [$(PRODUCT_LOCALES)]) + endif + PRODUCT_LOCALES += $(extra_locales) + extra_locales := +endif + +# Add PRODUCT_LOCALES to PRODUCT_AAPT_CONFIG +PRODUCT_AAPT_CONFIG := $(PRODUCT_LOCALES) $(PRODUCT_AAPT_CONFIG) + +# Keep a copy of the space-separated config +PRODUCT_AAPT_CONFIG_SP := $(PRODUCT_AAPT_CONFIG) +PRODUCT_AAPT_CONFIG := $(subst $(space),$(comma),$(PRODUCT_AAPT_CONFIG)) + +########################################################### +## Add 'platform:' prefix to jars not in : format. +## +## This makes sure that a jar corresponds to ConfigureJarList format of and pairs +## where needed. +## +## $(1): a list of jars either in or : format +########################################################### + +define qualify-platform-jars + $(foreach jar,$(1),$(if $(findstring :,$(jar)),,platform:)$(jar)) +endef + +# Extra boot jars must be appended at the end after common boot jars. +PRODUCT_BOOT_JARS += $(PRODUCT_BOOT_JARS_EXTRA) + +PRODUCT_BOOT_JARS := $(call qualify-platform-jars,$(PRODUCT_BOOT_JARS)) + +# b/191127295: force core-icu4j onto boot image. It comes from a non-updatable APEX jar, but has +# historically been part of the boot image; even though APEX jars are not meant to be part of the +# boot image. +# TODO(b/191686720): remove PRODUCT_APEX_BOOT_JARS to avoid a special handling of core-icu4j +# in make rules. +PRODUCT_APEX_BOOT_JARS := $(filter-out com.android.i18n:core-icu4j,$(PRODUCT_APEX_BOOT_JARS)) +# All APEX jars come after /system and /system_ext jars, so adding core-icu4j at the end of the list +PRODUCT_BOOT_JARS += com.android.i18n:core-icu4j + +# The extra system server jars must be appended at the end after common system server jars. +PRODUCT_SYSTEM_SERVER_JARS += $(PRODUCT_SYSTEM_SERVER_JARS_EXTRA) + +PRODUCT_SYSTEM_SERVER_JARS := $(call qualify-platform-jars,$(PRODUCT_SYSTEM_SERVER_JARS)) + +# Sort APEX boot and system server jars. We use deterministic alphabetical order +# when constructing BOOTCLASSPATH and SYSTEMSERVERCLASSPATH definition on device +# after an update. Enforce it in the build system as well to avoid recompiling +# everything after an update due a change in the order. +PRODUCT_APEX_BOOT_JARS := $(sort $(PRODUCT_APEX_BOOT_JARS)) +PRODUCT_APEX_SYSTEM_SERVER_JARS := $(sort $(PRODUCT_APEX_SYSTEM_SERVER_JARS)) + +PRODUCT_STANDALONE_SYSTEM_SERVER_JARS := \ + $(call qualify-platform-jars,$(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS)) + +ifndef PRODUCT_SYSTEM_NAME + PRODUCT_SYSTEM_NAME := $(PRODUCT_NAME) +endif +ifndef PRODUCT_SYSTEM_DEVICE + PRODUCT_SYSTEM_DEVICE := $(PRODUCT_DEVICE) +endif +ifndef PRODUCT_SYSTEM_BRAND + PRODUCT_SYSTEM_BRAND := $(PRODUCT_BRAND) +endif +ifndef PRODUCT_MODEL + PRODUCT_MODEL := $(PRODUCT_NAME) +endif +ifndef PRODUCT_SYSTEM_MODEL + PRODUCT_SYSTEM_MODEL := $(PRODUCT_MODEL) +endif + +ifndef PRODUCT_MANUFACTURER + PRODUCT_MANUFACTURER := Huawei +endif +ifndef PRODUCT_SYSTEM_MANUFACTURER + PRODUCT_SYSTEM_MANUFACTURER := $(PRODUCT_MANUFACTURER) +endif + +ifndef PRODUCT_CHARACTERISTICS + TARGET_AAPT_CHARACTERISTICS := default +else + TARGET_AAPT_CHARACTERISTICS := $(PRODUCT_CHARACTERISTICS) +endif + +ifdef PRODUCT_DEFAULT_DEV_CERTIFICATE + ifneq (1,$(words $(PRODUCT_DEFAULT_DEV_CERTIFICATE))) + $(error PRODUCT_DEFAULT_DEV_CERTIFICATE='$(PRODUCT_DEFAULT_DEV_CERTIFICATE)', \ + only 1 certificate is allowed.) + endif +endif + +$(foreach pair,$(PRODUCT_APEX_BOOT_JARS), \ + $(eval jar := $(call word-colon,2,$(pair))) \ + $(if $(findstring $(jar), $(PRODUCT_BOOT_JARS)), \ + $(error A jar in PRODUCT_APEX_BOOT_JARS must not be in PRODUCT_BOOT_JARS, but $(jar) is))) + +ENFORCE_SYSTEM_CERTIFICATE := $(PRODUCT_ENFORCE_ARTIFACT_SYSTEM_CERTIFICATE_REQUIREMENT) +ENFORCE_SYSTEM_CERTIFICATE_ALLOW_LIST := $(PRODUCT_ARTIFACT_SYSTEM_CERTIFICATE_REQUIREMENT_ALLOW_LIST) + +PRODUCT_OTA_PUBLIC_KEYS := $(sort $(PRODUCT_OTA_PUBLIC_KEYS)) +PRODUCT_EXTRA_OTA_KEYS := $(sort $(PRODUCT_EXTRA_OTA_KEYS)) +PRODUCT_EXTRA_RECOVERY_KEYS := $(sort $(PRODUCT_EXTRA_RECOVERY_KEYS)) + +PRODUCT_VALIDATION_CHECKS := $(sort $(PRODUCT_VALIDATION_CHECKS)) + +# Resolve and setup per-module dex-preopt configs. +DEXPREOPT_DISABLED_MODULES := +# If a module has multiple setups, the first takes precedence. +_pdpmc_modules := +$(foreach c,$(PRODUCT_DEX_PREOPT_MODULE_CONFIGS),\ + $(eval m := $(firstword $(subst =,$(space),$(c))))\ + $(if $(filter $(_pdpmc_modules),$(m)),,\ + $(eval _pdpmc_modules += $(m))\ + $(eval cf := $(patsubst $(m)=%,%,$(c)))\ + $(eval cf := $(subst $(_PDPMC_SP_PLACE_HOLDER),$(space),$(cf)))\ + $(if $(filter disable,$(cf)),\ + $(eval DEXPREOPT_DISABLED_MODULES += $(m)),\ + $(eval DEXPREOPT.$(TARGET_PRODUCT).$(m).CONFIG := $(cf))))) +_pdpmc_modules := + + +# Resolve and setup per-module sanitizer configs. +# If a module has multiple setups, the first takes precedence. +_psmc_modules := +$(foreach c,$(PRODUCT_SANITIZER_MODULE_CONFIGS),\ + $(eval m := $(firstword $(subst =,$(space),$(c))))\ + $(if $(filter $(_psmc_modules),$(m)),,\ + $(eval _psmc_modules += $(m))\ + $(eval cf := $(patsubst $(m)=%,%,$(c)))\ + $(eval cf := $(subst $(_PSMC_SP_PLACE_HOLDER),$(space),$(cf)))\ + $(eval SANITIZER.$(TARGET_PRODUCT).$(m).CONFIG := $(cf)))) +_psmc_modules := + +# Reset ADB keys for non-debuggable builds +ifeq (,$(filter eng userdebug,$(TARGET_BUILD_VARIANT))) + PRODUCT_ADB_KEYS := +endif +ifneq ($(filter-out 0 1,$(words $(PRODUCT_ADB_KEYS))),) + $(error Only one file may be in PRODUCT_ADB_KEYS: $(PRODUCT_ADB_KEYS)) +endif + +# Show a warning wall of text if non-compliance-GSI products set this option. +ifdef PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT + ifeq (,$(filter gsi_arm gsi_arm64 gsi_x86 gsi_x86_64 gsi_car_arm64 gsi_car_x86_64 gsi_tv_arm gsi_tv_arm64,$(PRODUCT_NAME))) + $(warning PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT is set but \ + PRODUCT_NAME ($(PRODUCT_NAME)) doesn't look like a GSI for compliance \ + testing. This is a special configuration for compliance GSI, so do make \ + sure you understand the security implications before setting this \ + option. If you don't know what this option does, then you probably \ + shouldn't set this.) + endif +endif + +ifndef PRODUCT_USE_DYNAMIC_PARTITIONS + PRODUCT_USE_DYNAMIC_PARTITIONS := $(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS) +endif + +# All requirements of PRODUCT_USE_DYNAMIC_PARTITIONS falls back to +# PRODUCT_USE_DYNAMIC_PARTITIONS if not defined. +ifndef PRODUCT_USE_DYNAMIC_PARTITION_SIZE + PRODUCT_USE_DYNAMIC_PARTITION_SIZE := $(PRODUCT_USE_DYNAMIC_PARTITIONS) +endif + +ifndef PRODUCT_BUILD_SUPER_PARTITION + PRODUCT_BUILD_SUPER_PARTITION := $(PRODUCT_USE_DYNAMIC_PARTITIONS) +endif + +ifeq ($(PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS),) + ifdef PRODUCT_SHIPPING_API_LEVEL + ifeq (true,$(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),29)) + PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS := true + endif + endif +endif + +ifeq ($(PRODUCT_SET_DEBUGFS_RESTRICTIONS),) + ifdef PRODUCT_SHIPPING_API_LEVEL + ifeq (true,$(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),31)) + PRODUCT_SET_DEBUGFS_RESTRICTIONS := true + endif + endif +endif + +# If build command defines OVERRIDE_PRODUCT_EXTRA_VNDK_VERSIONS, +# override PRODUCT_EXTRA_VNDK_VERSIONS with it. +ifdef OVERRIDE_PRODUCT_EXTRA_VNDK_VERSIONS + PRODUCT_EXTRA_VNDK_VERSIONS := $(OVERRIDE_PRODUCT_EXTRA_VNDK_VERSIONS) +endif + +########################################### +# APEXes are by default not compressed +# +# APEX compression can be forcibly enabled (resp. disabled) by +# setting OVERRIDE_PRODUCT_COMPRESSED_APEX to true (resp. false), e.g. by +# setting the OVERRIDE_PRODUCT_COMPRESSED_APEX environment variable. +ifdef OVERRIDE_PRODUCT_COMPRESSED_APEX + PRODUCT_COMPRESSED_APEX := $(OVERRIDE_PRODUCT_COMPRESSED_APEX) +endif + +$(KATI_obsolete_var OVERRIDE_PRODUCT_EXTRA_VNDK_VERSIONS \ + ,Use PRODUCT_EXTRA_VNDK_VERSIONS instead) + +# If build command defines OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE, +# override PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE with it unless it is +# defined as `false`. If the value is `false` clear +# PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE +# OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE can be used for +# testing only. +ifdef OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE + ifeq (false,$(OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE)) + PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := + else + PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := $(OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE) + endif +else ifeq ($(PRODUCT_SHIPPING_API_LEVEL),) + # No shipping level defined. Enforce the product interface by default. + PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true +else ifeq ($(call math_gt,$(PRODUCT_SHIPPING_API_LEVEL),29),true) + # Enforce product interface if PRODUCT_SHIPPING_API_LEVEL is greater than 29. + PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true +endif + +$(KATI_obsolete_var OVERRIDE_PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE,Use PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE instead) + +# From Android V, Define PRODUCT_PRODUCT_VNDK_VERSION as current by default. +# This is required to make all devices have product variants. +ifndef PRODUCT_PRODUCT_VNDK_VERSION + PRODUCT_PRODUCT_VNDK_VERSION := current +endif + +ifdef PRODUCT_ENFORCE_RRO_EXEMPTED_TARGETS + $(error PRODUCT_ENFORCE_RRO_EXEMPTED_TARGETS is deprecated, consider using RRO for \ + $(PRODUCT_ENFORCE_RRO_EXEMPTED_TARGETS)) +endif + +# Get the board API level. +board_api_level := $(PLATFORM_SDK_VERSION) +ifdef BOARD_API_LEVEL + board_api_level := $(BOARD_API_LEVEL) +else ifdef BOARD_SHIPPING_API_LEVEL + # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level. + board_api_level := $(BOARD_SHIPPING_API_LEVEL) +endif + +# Calculate the VSR vendor API level. +VSR_VENDOR_API_LEVEL := $(board_api_level) + +ifdef PRODUCT_SHIPPING_API_LEVEL + VSR_VENDOR_API_LEVEL := $(call math_min,$(PRODUCT_SHIPPING_API_LEVEL),$(board_api_level)) +endif +.KATI_READONLY := VSR_VENDOR_API_LEVEL + +# Boolean variable determining if vendor seapp contexts is enforced +CHECK_VENDOR_SEAPP_VIOLATIONS := false +ifneq ($(call math_gt,$(VSR_VENDOR_API_LEVEL),34),) + CHECK_VENDOR_SEAPP_VIOLATIONS := true +else ifneq ($(PRODUCT_CHECK_VENDOR_SEAPP_VIOLATIONS),) + CHECK_VENDOR_SEAPP_VIOLATIONS := $(PRODUCT_CHECK_VENDOR_SEAPP_VIOLATIONS) +endif +.KATI_READONLY := CHECK_VENDOR_SEAPP_VIOLATIONS + +# Boolean variable determining if selinux labels of /dev are enforced +CHECK_DEV_TYPE_VIOLATIONS := false +ifneq ($(call math_gt,$(VSR_VENDOR_API_LEVEL),35),) + CHECK_DEV_TYPE_VIOLATIONS := true +else ifneq ($(PRODUCT_CHECK_DEV_TYPE_VIOLATIONS),) + CHECK_DEV_TYPE_VIOLATIONS := $(PRODUCT_CHECK_DEV_TYPE_VIOLATIONS) +endif +.KATI_READONLY := CHECK_DEV_TYPE_VIOLATIONS + +define product-overrides-config +$$(foreach rule,$$(PRODUCT_$(1)_OVERRIDES),\ + $$(if $$(filter 2,$$(words $$(subst :,$$(space),$$(rule)))),,\ + $$(error Rule "$$(rule)" in PRODUCT_$(1)_OVERRIDE is not :))) +endef + +$(foreach var, \ + MANIFEST_PACKAGE_NAME \ + PACKAGE_NAME \ + CERTIFICATE, \ + $(eval $(call product-overrides-config,$(var)))) + +# Macro to use below. $(1) is the name of the partition +define product-build-image-config +ifneq ($$(filter-out true false,$$(PRODUCT_BUILD_$(1)_IMAGE)),) + $$(error Invalid PRODUCT_BUILD_$(1)_IMAGE: $$(PRODUCT_BUILD_$(1)_IMAGE) -- true false and empty are supported) +endif +endef + +# Copy and check the value of each PRODUCT_BUILD_*_IMAGE variable +$(foreach image, \ + PVMFW \ + SYSTEM \ + SYSTEM_OTHER \ + VENDOR \ + PRODUCT \ + SYSTEM_EXT \ + ODM \ + VENDOR_DLKM \ + ODM_DLKM \ + SYSTEM_DLKM \ + CACHE \ + RAMDISK \ + USERDATA \ + BOOT \ + RECOVERY, \ + $(eval $(call product-build-image-config,$(image)))) + +product-build-image-config := + +$(call readonly-product-vars) diff --git a/aosp/build/make/core/tasks/tools/compatibility.mk b/aosp/build/make/core/tasks/tools/compatibility.mk new file mode 100644 index 000000000..91197934e --- /dev/null +++ b/aosp/build/make/core/tasks/tools/compatibility.mk @@ -0,0 +1,175 @@ +# Copyright (C) 2015 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. + +# Package up a compatibility test suite in a zip file. +# +# Input variables: +# test_suite_name: the name of this test suite eg. cts +# test_suite_tradefed: the name of this test suite's tradefed wrapper +# test_suite_dynamic_config: the path to this test suite's dynamic configuration file +# test_suite_readme: the path to a README file for this test suite +# test_suite_prebuilt_tools: the set of prebuilt tools to be included directly +# in the 'tools' subdirectory of the test suite. +# test_suite_tools: the set of tools for this test suite +# +# Output variables: +# compatibility_zip: the path to the output zip file. + +special_mts_test_suites := +special_mts_test_suites += mcts +special_mts_test_suites += $(mts_modules) +ifneq ($(filter $(special_mts_test_suites),$(subst -, ,$(test_suite_name))),) + test_suite_subdir := android-mts +else + test_suite_subdir := android-$(test_suite_name) +endif + +out_dir := $(HOST_OUT)/$(test_suite_name)/$(test_suite_subdir) +test_artifacts := $(COMPATIBILITY.$(test_suite_name).FILES) +test_tools := $(HOST_OUT_JAVA_LIBRARIES)/tradefed.jar \ + $(HOST_OUT_JAVA_LIBRARIES)/loganalysis.jar \ + $(HOST_OUT_JAVA_LIBRARIES)/compatibility-host-util.jar \ + $(HOST_OUT_JAVA_LIBRARIES)/compatibility-tradefed.jar \ + $(HOST_OUT_JAVA_LIBRARIES)/$(test_suite_tradefed).jar \ + $(HOST_OUT_JAVA_LIBRARIES)/$(test_suite_tradefed)-tests.jar \ + $(HOST_OUT_EXECUTABLES)/$(test_suite_tradefed) \ + $(HOST_OUT_EXECUTABLES)/test-utils-script \ + $(test_suite_readme) + +$(foreach f,$(test_suite_readme),$(if $(strip $(ALL_TARGETS.$(f).META_LIC)),,$(eval ALL_TARGETS.$(f).META_LIC := $(module_license_metadata)))) + +test_tools += $(test_suite_tools) + +# The JDK to package into the test suite zip file. Always package the linux JDK. +test_suite_jdk_dir := $(ANDROID_JAVA_HOME)/../linux-x86 +ifndef test_suite_jdk_files + # This file gets included many times, so make sure we only run the $(shell) once. + # Otherwise it will slow down every build due to all copies of it being rerun when kati + # checks the stamp file. + test_suite_jdk_files :=$= $(shell find $(test_suite_jdk_dir) -type f | sort) +endif +#test_suite_jdk := $(call intermediates-dir-for,PACKAGING,$(test_suite_name)_jdk,HOST)/jdk.zip +$(test_suite_jdk): PRIVATE_JDK_DIR := $(test_suite_jdk_dir) +$(test_suite_jdk): PRIVATE_SUBDIR := $(test_suite_subdir) +$(test_suite_jdk): $(test_suite_jdk_files) +$(test_suite_jdk): $(SOONG_ZIP) + $(SOONG_ZIP) -o $@ -P $(PRIVATE_SUBDIR)/jdk -C $(PRIVATE_JDK_DIR) -D $(PRIVATE_JDK_DIR) -sha256 + +$(call declare-license-metadata,$(test_suite_jdk),SPDX-license-identifier-GPL-2.0-with-classpath-exception,permissive,\ + $(test_suite_jdk_dir)/legal/java.base/LICENSE,JDK,prebuilts/jdk/$(notdir $(patsubst %/,%,$(dir $(test_suite_jdk_dir))))) + +# Copy license metadata +$(call declare-copy-target-license-metadata,$(out_dir)/$(notdir $(test_suite_jdk)),$(test_suite_jdk)) +$(foreach t,$(test_tools) $(test_suite_prebuilt_tools),\ + $(eval _dst := $(out_dir)/tools/$(notdir $(t)))\ + $(if $(strip $(ALL_TARGETS.$(t).META_LIC)),\ + $(call declare-copy-target-license-metadata,$(_dst),$(t)),\ + $(warning $(t) has no license metadata)\ + )\ +) +test_copied_tools := $(foreach t,$(test_tools) $(test_suite_prebuilt_tools), $(out_dir)/tools/$(notdir $(t))) $(out_dir)/$(notdir $(test_suite_jdk)) + + +# Include host shared libraries +host_shared_libs := $(call copy-many-files, $(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES)) + +$(if $(strip $(host_shared_libs)),\ + $(foreach p,$(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES),\ + $(eval _src := $(call word-colon,1,$(p)))\ + $(eval _dst := $(call word-colon,2,$(p)))\ + $(if $(strip $(ALL_TARGETS.$(_src).META_LIC)),\ + $(call declare-copy-target-license-metadata,$(_dst),$(_src)),\ + $(warning $(_src) has no license metadata for $(_dst))\ + )\ + )\ +) + +compatibility_zip_deps := \ + $(test_artifacts) \ + $(test_tools) \ + $(test_suite_prebuilt_tools) \ + $(test_suite_dynamic_config) \ + $(test_suite_jdk) \ + $(MERGE_ZIPS) \ + $(SOONG_ZIP) \ + $(host_shared_libs) \ + $(test_suite_extra_deps) \ + +compatibility_zip_resources := $(out_dir)/tools $(out_dir)/testcases $(out_dir)/lib $(out_dir)/lib64 + +# Test Suite NOTICE files +test_suite_notice_txt := $(out_dir)/NOTICE.txt +test_suite_notice_html := $(out_dir)/NOTICE.html + +compatibility_zip_deps += $(test_suite_notice_txt) +compatibility_zip_resources += $(test_suite_notice_txt) + +compatibility_tests_list_zip := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)-tests_list.zip + +compatibility_zip := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name).zip +$(compatibility_zip) : .KATI_IMPLICIT_OUTPUTS := $(compatibility_tests_list_zip) +$(compatibility_zip): PRIVATE_OUT_DIR := $(out_dir) +$(compatibility_zip): PRIVATE_TOOLS := $(test_tools) $(test_suite_prebuilt_tools) +$(compatibility_zip): PRIVATE_SUITE_NAME := $(test_suite_name) +$(compatibility_zip): PRIVATE_DYNAMIC_CONFIG := $(test_suite_dynamic_config) +$(compatibility_zip): PRIVATE_RESOURCES := $(compatibility_zip_resources) +$(compatibility_zip): PRIVATE_JDK := $(test_suite_jdk) +$(compatibility_zip): PRIVATE_tests_list := $(out_dir)-tests_list +$(compatibility_zip): PRIVATE_tests_list_zip := $(compatibility_tests_list_zip) +ifeq ($(strip $(HAS_BUILD_NUMBER)),true) +$(compatibility_zip): $(BUILD_NUMBER_FILE) +endif +$(compatibility_zip): $(compatibility_zip_deps) | $(ADB) $(ACP) +# Make dir structure + mkdir -p $(PRIVATE_OUT_DIR)/tools $(PRIVATE_OUT_DIR)/testcases + rm -f $@ $@.tmp $@.jdk + echo $(BUILD_NUMBER_FROM_FILE) > $(PRIVATE_OUT_DIR)/tools/version.txt +# Copy tools + cp $(PRIVATE_TOOLS) $(PRIVATE_OUT_DIR)/tools + $(if $(PRIVATE_DYNAMIC_CONFIG),$(hide) cp $(PRIVATE_DYNAMIC_CONFIG) $(PRIVATE_OUT_DIR)/testcases/$(PRIVATE_SUITE_NAME).dynamic) + find $(PRIVATE_RESOURCES) | sort >$@.list + $(SOONG_ZIP) -d -o $@.tmp -C $(dir $@) -l $@.list -sha256 + $(MERGE_ZIPS) $@ $@.tmp $(PRIVATE_JDK) + rm -f $@.tmp +# Build a list of tests + rm -f $(PRIVATE_tests_list) + $(hide) grep -e .*\\.config$$ $@.list | sed s%$(PRIVATE_OUT_DIR)/testcases/%%g > $(PRIVATE_tests_list) + $(SOONG_ZIP) -d -o $(PRIVATE_tests_list_zip) -j -f $(PRIVATE_tests_list) + rm -f $(PRIVATE_tests_list) + +$(call declare-0p-target,$(compatibility_tests_list_zip),) + +$(call declare-1p-container,$(compatibility_zip),) +$(call declare-container-license-deps,$(compatibility_zip),$(compatibility_zip_deps) $(test_copied_tools), $(out_dir)/:/) + +$(eval $(call html-notice-rule,$(test_suite_notice_html),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip))) +$(eval $(call text-notice-rule,$(test_suite_notice_txt),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip))) + +$(call declare-0p-target,$(test_suite_notice_html)) +$(call declare-0p-target,$(test_suite_notice_txt)) + +$(call declare-1p-copy-files,$(test_suite_dynamic_config),) +$(call declare-1p-copy-files,$(test_suite_prebuilt_tools),) + +# Reset all input variables +test_suite_name := +test_suite_tradefed := +test_suite_dynamic_config := +test_suite_readme := +test_suite_prebuilt_tools := +test_suite_tools := +test_suite_jdk := +test_suite_jdk_dir := +host_shared_libs := +test_suite_extra_deps := diff --git a/aosp/build/make/target/product/base_vendor.mk b/aosp/build/make/target/product/base_vendor.mk new file mode 100644 index 000000000..083f227f8 --- /dev/null +++ b/aosp/build/make/target/product/base_vendor.mk @@ -0,0 +1,107 @@ +# +# Copyright (C) 2018 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. +# + +# Base modules and settings for recovery. +PRODUCT_PACKAGES += \ + adbd.recovery \ + android.hardware.health@2.0-impl-default.recovery \ + cgroups.recovery.json \ + charger.recovery \ + init_second_stage.recovery \ + ld.config.recovery.txt \ + linker.recovery \ + otacerts.recovery \ + recovery \ + servicemanager.recovery \ + shell_and_utilities_recovery \ + watchdogd.recovery \ + +PRODUCT_VENDOR_PROPERTIES += \ + ro.recovery.usb.vid?=18D1 \ + ro.recovery.usb.adb.pid?=D001 \ + ro.recovery.usb.fastboot.pid?=4EE0 \ + +# These had been pulled in via init_second_stage.recovery, but may not be needed. +PRODUCT_HOST_PACKAGES += \ + e2fsdroid \ + mke2fs \ + sload_f2fs \ + make_f2fs \ + +PRODUCT_HOST_PACKAGES += \ + icu-data_host_i18n_apex + +# Base modules and settings for the vendor partition. +PRODUCT_PACKAGES += \ + com.android.hardware.cas \ + boringssl_self_test_vendor \ + dumpsys_vendor \ + fs_config_files_nonsystem \ + fs_config_dirs_nonsystem \ + gralloc.default \ + group_odm \ + group_vendor \ + init_vendor \ + libbundlewrapper \ + libclearkeycasplugin \ + libdownmix \ + libdrmclearkeyplugin \ + libdynproc \ + libeffectproxy \ + libeffects \ + libhapticgenerator \ + libldnhncr \ + libreference-ril \ + libreverbwrapper \ + libril \ + libvisualizer \ + passwd_odm \ + passwd_vendor \ + selinux_policy_nonsystem \ + shell_and_utilities_vendor \ + android.hardware.media.omx@1.0-service \ + +# Base modules when shipping api level is less than or equal to 34 +PRODUCT_PACKAGES_SHIPPING_API_LEVEL_34 += \ + android.hidl.memory@1.0-impl.vendor \ + +# OMX not supported for 64bit_only builds +# Only supported when SHIPPING_API_LEVEL is less than or equal to 33 +#ifneq ($(TARGET_SUPPORTS_OMX_SERVICE),false) +# PRODUCT_PACKAGES_SHIPPING_API_LEVEL_33 += \ +# android.hardware.media.omx@1.0-service \ +# +#endif + +# Base modules when shipping api level is less than or equal to 33 +PRODUCT_PACKAGES_SHIPPING_API_LEVEL_33 += \ + android.hardware.cas@1.2-service \ + +# Base modules when shipping api level is less than or equal to 29 +PRODUCT_PACKAGES_SHIPPING_API_LEVEL_29 += \ + android.hardware.configstore@1.1-service \ + vndservice \ + vndservicemanager \ + +# VINTF data for vendor image +PRODUCT_PACKAGES += \ + vendor_compatibility_matrix.xml \ + +# Base modules and settings for the debug ramdisk, which is then packed +# into a boot-debug.img and a vendor_boot-debug.img. +PRODUCT_PACKAGES += \ + adb_debug.prop \ + userdebug_plat_sepolicy.cil diff --git a/aosp/build/make/target/product/handheld_product.mk b/aosp/build/make/target/product/handheld_product.mk new file mode 100644 index 000000000..cdef7b7a4 --- /dev/null +++ b/aosp/build/make/target/product/handheld_product.mk @@ -0,0 +1,38 @@ +# +# Copyright (C) 2019 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. +# + +# This makefile contains the product partition contents for +# a generic phone or tablet device. Only add something here if +# it definitely doesn't belong on other types of devices (if it +# does, use base_product.mk). +$(call inherit-product, $(SRC_TARGET_DIR)/product/media_product.mk) + +# /product packages +PRODUCT_PACKAGES += \ + Browser2 \ + Calendar \ + Contacts \ + DeskClock \ + Gallery2 \ + LatinIME \ + Music \ + preinstalled-packages-platform-handheld-product.xml \ + QuickSearchBox \ + SettingsIntelligence \ + frameworks-base-overlays + +PRODUCT_PACKAGES_DEBUG += \ + frameworks-base-overlays-debug diff --git a/aosp/build/make/target/product/updatable_apex.mk b/aosp/build/make/target/product/updatable_apex.mk new file mode 100644 index 000000000..c45b2289e --- /dev/null +++ b/aosp/build/make/target/product/updatable_apex.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2019 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. +# + +# com.android.apex.cts.shim.v1_prebuilt overrides CtsShimPrebuilt +# and CtsShimPrivPrebuilt since they are packaged inside the APEX. +PRODUCT_PACKAGES += com.android.apex.cts.shim.v1_prebuilt +PRODUCT_SYSTEM_PROPERTIES := ro.apex.updatable=false + +# Use compressed apexes in pre-installed partitions. +# Note: this doesn't mean that all pre-installed apexes will be compressed. +# Whether an apex is compressed or not is controlled at apex Soong module +# via compresible property. +PRODUCT_COMPRESSED_APEX := false diff --git a/aosp/build/soong/ui/build/paths/config.go b/aosp/build/soong/ui/build/paths/config.go new file mode 100644 index 000000000..c41d3b06c --- /dev/null +++ b/aosp/build/soong/ui/build/paths/config.go @@ -0,0 +1,148 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// 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. + +package paths + +import "runtime" + +type PathConfig struct { + // Whether to create the symlink in the new PATH for this tool. + Symlink bool + + // Whether to log about usages of this tool to the soong.log + Log bool + + // Whether to exit with an error instead of invoking the underlying tool. + Error bool + + // Whether we use a linux-specific prebuilt for this tool. On Darwin, + // we'll allow the host executable instead. + LinuxOnlyPrebuilt bool +} + +// These binaries can be run from $PATH, nonhermetically. There should be as +// few as possible of these, since this means that the build depends on tools +// that are not shipped in the source tree and whose behavior is therefore +// unpredictable. +var Allowed = PathConfig{ + Symlink: true, + Log: false, + Error: false, +} + +// This tool is specifically disallowed and calling it will result in an +// "executable no found" error. +var Forbidden = PathConfig{ + Symlink: false, + Log: true, + Error: true, +} + +// This tool is allowed, but access to it will be logged. +var Log = PathConfig{ + Symlink: true, + Log: true, + Error: false, +} + +// The configuration used if the tool is not listed in the config below. +// Currently this will create the symlink, but log and error when it's used. In +// the future, I expect the symlink to be removed, and this will be equivalent +// to Forbidden. This applies to every tool not specifically mentioned in the +// configuration. +var Missing = PathConfig{ + Symlink: true, + Log: true, + Error: true, +} + +// This is used for binaries for which we have prebuilt versions, but only for +// Linux. Thus, their execution from $PATH is only allowed on Mac OS. +var LinuxOnlyPrebuilt = PathConfig{ + Symlink: false, + Log: true, + Error: true, + LinuxOnlyPrebuilt: true, +} + +func GetConfig(name string) PathConfig { + if config, ok := Configuration[name]; ok { + return config + } + return Missing +} + +// This list specifies whether a particular binary from $PATH is allowed to be +// run during the build. For more documentation, see path_interposer.go . +var Configuration = map[string]PathConfig{ + "bash": Allowed, + "diff": Allowed, + "dlv": Allowed, + "expr": Allowed, + "fuser": Allowed, + "gcert": Allowed, + "gcertstatus": Allowed, + "gcloud": Allowed, + "getopt": Allowed, + "git": Allowed, + "hexdump": Allowed, + "jar": Allowed, + "java": Allowed, + "javap": Allowed, + "lsof": Allowed, + "openssl": Allowed, + "pstree": Allowed, + "rsync": Allowed, + "sh": Allowed, + "stubby": Allowed, + "tr": Allowed, + "unzip": Allowed, + "zip": Allowed, + "meson.py": Allowed, + "ninja": Allowed, + + // Host toolchain is removed. In-tree toolchain should be used instead. + // GCC also can't find cc1 with this implementation. + "ar": Forbidden, + "as": Forbidden, + "cc": Forbidden, + "clang": Forbidden, + "clang++": Forbidden, + "gcc": Forbidden, + "g++": Forbidden, + "ld": Forbidden, + "ld.bfd": Forbidden, + "ld.gold": Forbidden, + "pkg-config": Forbidden, + + // These are toybox tools that only work on Linux. + "pgrep": LinuxOnlyPrebuilt, + "pkill": LinuxOnlyPrebuilt, + "ps": LinuxOnlyPrebuilt, +} + +func init() { + if runtime.GOOS == "darwin" { + Configuration["sw_vers"] = Allowed + Configuration["xcrun"] = Allowed + + // We don't have darwin prebuilts for some tools, + // so allow the host versions. + for name, config := range Configuration { + if config.LinuxOnlyPrebuilt { + Configuration[name] = Allowed + } + } + } +} diff --git a/aosp/device/generic/goldfish-opengl/Android.mk b/aosp/device/generic/goldfish-opengl/Android.mk new file mode 100644 index 000000000..bb6d2ed11 --- /dev/null +++ b/aosp/device/generic/goldfish-opengl/Android.mk @@ -0,0 +1,156 @@ +# This is the top-level build file for the Android HW OpenGL ES emulation +# in Android. +# +# You must define BUILD_EMULATOR_OPENGL to 'true' in your environment to +# build the following files. +# +# Also define BUILD_EMULATOR_OPENGL_DRIVER to 'true' to build the gralloc +# stuff as well. +# +# Top-level for all modules +GOLDFISH_OPENGL_PATH := $(call my-dir) + +HARDWARE_GOOGLE_GFXSTREAM_PATH := $(GOLDFISH_OPENGL_PATH)/../../../hardware/google/gfxstream + +ifeq (true,$(GOLDFISH_OPENGL_BUILD_FOR_HOST)) +ENABLE_GOLDFISH_OPENGL_FOLDER := true +else +ifneq ($(filter $(GOLDFISH_OPENGL_PATH),$(PRODUCT_SOONG_NAMESPACES)),) +ENABLE_GOLDFISH_OPENGL_FOLDER := true +endif +endif + +ifeq (true,$(ENABLE_GOLDFISH_OPENGL_FOLDER)) + +# There are two kinds of builds for goldfish-opengl: +# 1. The standard guest build, denoted by BUILD_EMULATOR_OPENGL +# 2. The host-side build, denoted by GOLDFISH_OPENGL_BUILD_FOR_HOST +# +# Variable controlling whether the build for goldfish-opengl +# libraries (including their Android.mk's) should be triggered. +GOLDFISH_OPENGL_SHOULD_BUILD := false + +# In the host build, some libraries have name collisions with +# other libraries, so we have this variable here to control +# adding a suffix to the names of libraries. Should be blank +# for the guest build. +GOLDFISH_OPENGL_LIB_SUFFIX := + +# Directory containing common headers used by several modules +# This is always set to a module's LOCAL_C_INCLUDES +# See the definition of emugl-begin-module in common.mk +EMUGL_COMMON_INCLUDES := \ + $(HARDWARE_GOOGLE_GFXSTREAM_PATH)/guest/iostream/include/libOpenglRender \ + $(HARDWARE_GOOGLE_GFXSTREAM_PATH)/guest/include + +# This is always set to a module's LOCAL_CFLAGS +# See the definition of emugl-begin-module in common.mk +EMUGL_COMMON_CFLAGS := + +# Whether or not to build the Vulkan library. +GFXSTREAM := false + +# Host build +ifeq (true,$(GOLDFISH_OPENGL_BUILD_FOR_HOST)) + +GOLDFISH_OPENGL_SHOULD_BUILD := true +GOLDFISH_OPENGL_LIB_SUFFIX := _host + +GFXSTREAM := true + +# Set modern defaults for the codename, version, etc. +PLATFORM_VERSION_CODENAME:=Q +PLATFORM_SDK_VERSION:=29 +IS_AT_LEAST_OPD1:=true + +# The host-side Android framework implementation +HOST_EMUGL_PATH := $(GOLDFISH_OPENGL_PATH)/../../../external/qemu/android/android-emugl +EMUGL_COMMON_INCLUDES += $(HOST_EMUGL_PATH)/guest + +EMUGL_COMMON_CFLAGS += \ + -DPLATFORM_SDK_VERSION=29 \ + -DGOLDFISH_HIDL_GRALLOC \ + -DHOST_BUILD \ + -DANDROID \ + -DGL_GLEXT_PROTOTYPES \ + -fvisibility=default \ + -DPAGE_SIZE=4096 \ + -DGFXSTREAM \ + -DENABLE_ANDROID_HEALTH_MONITOR \ + -Wno-unused-parameter + +endif # GOLDFISH_OPENGL_BUILD_FOR_HOST + +ifeq (true,$(BUILD_EMULATOR_OPENGL)) # Guest build + +GOLDFISH_OPENGL_SHOULD_BUILD := true + +EMUGL_COMMON_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) + +ifeq (O, $(PLATFORM_VERSION_CODENAME)) +EMUGL_COMMON_CFLAGS += -DGOLDFISH_HIDL_GRALLOC +endif + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 25 && echo isApi26OrHigher),isApi26OrHigher) +EMUGL_COMMON_CFLAGS += -DGOLDFISH_HIDL_GRALLOC +endif + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 18 && echo PreJellyBeanMr2),PreJellyBeanMr2) + ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) + EMUGL_COMMON_CFLAGS += -DHAVE_ARM_TLS_REGISTER + endif +endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 16 && echo PreJellyBean),PreJellyBean) + EMUGL_COMMON_CFLAGS += -DALOG_ASSERT=LOG_ASSERT + EMUGL_COMMON_CFLAGS += -DALOGE=LOGE + EMUGL_COMMON_CFLAGS += -DALOGW=LOGW + EMUGL_COMMON_CFLAGS += -DALOGD=LOGD + EMUGL_COMMON_CFLAGS += -DALOGV=LOGV +endif + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27 && echo isApi28OrHigher),isApi28OrHigher) + GFXSTREAM := true + EMUGL_COMMON_CFLAGS += -DGFXSTREAM +endif + +# Include common definitions used by all the modules included later +# in this build file. This contains the definition of all useful +# emugl-xxxx functions. +# +include $(GOLDFISH_OPENGL_PATH)/common.mk + +endif # BUILD_EMULATOR_OPENGL (guest build) + +ifeq (true,$(GOLDFISH_OPENGL_SHOULD_BUILD)) + +# Uncomment the following line if you want to enable debug traces +# in the GLES emulation libraries. +# EMUGL_COMMON_CFLAGS += -DEMUGL_DEBUG=1 + +# IMPORTANT: ORDER IS CRUCIAL HERE +# +# For the import/export feature to work properly, you must include +# modules below in correct order. That is, if module B depends on +# module A, then it must be included after module A below. +# +# This ensures that anything exported by module A will be correctly +# be imported by module B when it is declared. +# +# Note that the build system will complain if you try to import a +# module that hasn't been declared yet anyway. +# +ifneq (true,$(GOLDFISH_OPENGL_BUILD_FOR_HOST)) +include $(GOLDFISH_OPENGL_PATH)/system/hals/Android.mk +endif + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 28 -o $(IS_AT_LEAST_QPR1) = true && echo isApi29OrHigher),isApi29OrHigher) + # hardware codecs enabled after P + #include $(GOLDFISH_OPENGL_PATH)/system/codecs/omx/common/Android.mk + #include $(GOLDFISH_OPENGL_PATH)/system/codecs/omx/plugin/Android.mk + #include $(GOLDFISH_OPENGL_PATH)/system/codecs/omx/avcdec/Android.mk + #include $(GOLDFISH_OPENGL_PATH)/system/codecs/omx/vpxdec/Android.mk +endif + +endif + +endif # ENABLE_GOLDFISH_OPENGL_FOLDER diff --git a/aosp/device/generic/goldfish/manifest.xml b/aosp/device/generic/goldfish/manifest.xml new file mode 100644 index 000000000..0de1762a1 --- /dev/null +++ b/aosp/device/generic/goldfish/manifest.xml @@ -0,0 +1,69 @@ + + + android.hardware.bluetooth + hwbinder + 1.1 + + IBluetoothHci + default + + + + android.hardware.graphics.allocator + hwbinder + 2.0 + + IAllocator + default + + + + android.hardware.graphics.mapper + passthrough + 2.1 + + IMapper + default + + + + android.hardware.graphics.composer + hwbinder + 2.3 + + IComposer + default + + + + android.hardware.media.omx + hwbinder + 1.0 + + IOmx + default + + + IOmxStore + default + + + + android.hardware.sensors + hwbinder + 1.0 + + ISensors + default + + + + android.hardware.gnss + hwbinder + 1.0 + + IGnss + default + + + diff --git a/aosp/device/generic/goldfish/product/generic.mk b/aosp/device/generic/goldfish/product/generic.mk new file mode 100644 index 000000000..a0383aa5a --- /dev/null +++ b/aosp/device/generic/goldfish/product/generic.mk @@ -0,0 +1,357 @@ +# +# 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. + +# +# This file is to configure vendor/data partitions of emulator-related products +# +$(call inherit-product-if-exists, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk) + +# Enable Scoped Storage related +$(call inherit-product, $(SRC_TARGET_DIR)/product/emulated_storage.mk) + +PRODUCT_SHIPPING_API_LEVEL := 34 +PRODUCT_FULL_TREBLE_OVERRIDE := true +DEVICE_MANIFEST_FILE += device/generic/goldfish/manifest.xml + +PRODUCT_SOONG_NAMESPACES += \ + device/generic/goldfish \ + device/generic/goldfish-opengl + +TARGET_USES_MKE2FS := true + +# RKPD +PRODUCT_PRODUCT_PROPERTIES += \ + remote_provisioning.enable_rkpd=true \ + remote_provisioning.hostname=remoteprovisioning.googleapis.com + +PRODUCT_VENDOR_PROPERTIES += \ + ro.control_privapp_permissions=enforce \ + ro.crypto.dm_default_key.options_format.version=2 \ + ro.crypto.volume.filenames_mode=aes-256-cts \ + ro.hardware.audio.tinyalsa.period_count=4 \ + ro.hardware.audio.tinyalsa.period_size_multiplier=2 \ + ro.hardware.audio.tinyalsa.host_latency_ms=80 \ + ro.hardware.power=ranchu \ + ro.incremental.enable=yes \ + ro.logd.size=1M \ + ro.kernel.qemu=1 \ + ro.soc.manufacturer=AOSP \ + ro.soc.model=ranchu \ + ro.surface_flinger.has_HDR_display=false \ + ro.surface_flinger.has_wide_color_display=false \ + ro.surface_flinger.protected_contents=false \ + ro.surface_flinger.supports_background_blur=1 \ + ro.surface_flinger.use_color_management=false \ + ro.zygote.disable_gl_preload=1 \ + debug.sf.vsync_reactor_ignore_present_fences=true \ + debug.stagefright.c2inputsurface=-1 \ + debug.stagefright.ccodec=4 \ + graphics.gpu.profiler.support=true \ + persist.sys.zram_enabled=1 \ + wifi.direct.interface=p2p-dev-wlan0 \ + wifi.interface=wlan0 \ + +# Device modules +PRODUCT_PACKAGES += \ + android.hardware.drm-service-lazy.clearkey \ + android.hardware.gatekeeper@1.0-service.software \ + android.hardware.usb-service.example \ + vulkan.ranchu \ + libandroidemu \ + libOpenglCodecCommon \ + libOpenglSystemCommon \ + qemu-export-property \ + qemu-props \ + stagefright \ + android.hardware.graphics.composer3-service.ranchu \ + toybox_vendor \ + android.hardware.wifi-service \ + android.hardware.media.c2@1.0-service-goldfish \ + libcodec2_goldfish_vp8dec \ + libcodec2_goldfish_vp9dec \ + libcodec2_goldfish_avcdec \ + libcodec2_goldfish_hevcdec \ + sh_vendor \ + local_time.default \ + SdkSetup \ + goldfish_overlay_connectivity_gsi \ + libGoldfishProfiler \ + dlkm_loader + +ifneq ($(filter %_minigbm, $(TARGET_PRODUCT)),) +PRODUCT_VENDOR_PROPERTIES += ro.hardware.gralloc=minigbm +PRODUCT_PACKAGES += \ + android.hardware.graphics.allocator-service.minigbm \ + android.hardware.graphics.mapper@4.0-impl.minigbm \ + mapper.minigbm +else +PRODUCT_VENDOR_PROPERTIES += ro.hardware.gralloc=kbox +PRODUCT_PACKAGES += \ + android.hardware.graphics.allocator@3.0-service.ranchu \ + android.hardware.graphics.mapper@3.0-impl-ranchu +endif + +ifneq ($(EMULATOR_DISABLE_RADIO),true) +PRODUCT_PACKAGES += \ + libcuttlefish-ril-2 \ + libgoldfish-rild \ + EmulatorRadioConfig \ + EmulatorTetheringConfigOverlay + +DEVICE_MANIFEST_FILE += device/generic/goldfish/radio/manifest.radio.xml +DISABLE_RILD_OEM_HOOK := true +endif + +ifneq ($(EMULATOR_VENDOR_NO_BIOMETRICS), true) +PRODUCT_PACKAGES += \ + android.hardware.biometrics.fingerprint-service.ranchu \ + android.hardware.biometrics.face-service.example \ + +PRODUCT_COPY_FILES += \ + frameworks/native/data/etc/android.hardware.fingerprint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.fingerprint.xml \ + frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.biometrics.face.xml \ + +endif + +ifneq ($(BUILD_EMULATOR_OPENGL),false) +PRODUCT_PACKAGES += \ + libGLESv1_CM_emulation \ + lib_renderControl_enc \ + libEGL_emulation \ + libGLESv2_enc \ + libvulkan_enc \ + libGLESv2_emulation \ + libGLESv1_enc \ + libEGL_angle \ + libGLESv1_CM_angle \ + libGLESv2_angle +endif + +# Enable bluetooth +PRODUCT_PACKAGES += \ + android.hardware.bluetooth-service.default \ + android.hardware.bluetooth.audio-impl \ + bt_vhci_forwarder \ + +# Bluetooth hardware properties. +ifeq ($(TARGET_PRODUCT_PROP),) +TARGET_PRODUCT_PROP := $(LOCAL_PATH)/bluetooth.prop +endif + +PRODUCT_PACKAGES += \ + android.hardware.security.keymint-service +PRODUCT_COPY_FILES += \ + frameworks/native/data/etc/android.hardware.keystore.app_attest_key.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.keystore.app_attest_key.xml + +PRODUCT_PACKAGES += \ + DisplayCutoutEmulationEmu01Overlay \ + EmulationPixelFoldOverlay \ + SystemUIEmulationPixelFoldOverlay \ + EmulationPixel8ProOverlay \ + SystemUIEmulationPixel8ProOverlay \ + EmulationPixel8Overlay \ + SystemUIEmulationPixel8Overlay \ + EmulationPixel7ProOverlay \ + SystemUIEmulationPixel7ProOverlay \ + EmulationPixel7Overlay \ + SystemUIEmulationPixel7Overlay \ + EmulationPixel7aOverlay \ + SystemUIEmulationPixel7aOverlay \ + EmulationPixel6ProOverlay \ + SystemUIEmulationPixel6ProOverlay \ + EmulationPixel6Overlay \ + SystemUIEmulationPixel6Overlay \ + EmulationPixel6aOverlay \ + SystemUIEmulationPixel6aOverlay \ + EmulationPixel5Overlay \ + SystemUIEmulationPixel5Overlay \ + EmulationPixel4XLOverlay \ + SystemUIEmulationPixel4XLOverlay \ + EmulationPixel4Overlay \ + SystemUIEmulationPixel4Overlay \ + EmulationPixel4aOverlay \ + SystemUIEmulationPixel4aOverlay \ + EmulationPixel3XLOverlay \ + SystemUIEmulationPixel3XLOverlay \ + EmulationPixel3Overlay \ + SystemUIEmulationPixel3Overlay \ + EmulationPixel3aOverlay \ + SystemUIEmulationPixel3aOverlay \ + EmulationPixel3aXLOverlay \ + SystemUIEmulationPixel3aXLOverlay \ + EmulationPixel2XLOverlay \ + NavigationBarMode2ButtonOverlay \ + +ifneq ($(EMULATOR_VENDOR_NO_GNSS),true) +PRODUCT_PACKAGES += android.hardware.gnss-service.ranchu +endif + +EMULATOR_VENDOR_NO_SENSORS=true +ifneq ($(EMULATOR_VENDOR_NO_SENSORS),true) +PRODUCT_PACKAGES += \ + android.hardware.sensors-service.multihal \ + android.hardware.sensors@2.1-impl.ranchu +# TODO(rkir): +# add a soong namespace and move this into a.h.sensors@2.1-impl.ranchu +# as prebuilt_etc. For now soong_namespace causes a build break because the fw +# refers to our wifi HAL in random places. +PRODUCT_COPY_FILES += \ + device/generic/goldfish/sensors/hals.conf:$(TARGET_COPY_OUT_VENDOR)/etc/sensors/hals.conf +endif + +ifneq ($(EMULATOR_VENDOR_NO_CAMERA),true) +PRODUCT_SOONG_NAMESPACES += \ + hardware/google/camera \ + hardware/google/camera/devices/EmulatedCamera \ + +PRODUCT_PACKAGES += \ + android.hardware.camera.provider.ranchu \ + android.hardware.camera.provider@2.7-service-google \ + libgooglecamerahwl_impl \ + +PRODUCT_COPY_FILES += \ + device/generic/goldfish/camera/media/profiles.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml \ + device/generic/goldfish/camera/media/codecs_google_video_default.xml:${TARGET_COPY_OUT_VENDOR}/etc/media_codecs_google_video.xml \ + device/generic/goldfish/camera/media/codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \ + device/generic/goldfish/camera/media/codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \ + device/generic/goldfish/camera/media/codecs_performance_c2.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance_c2.xml \ + frameworks/native/data/etc/android.hardware.camera.flash-autofocus.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.flash-autofocus.xml \ + frameworks/native/data/etc/android.hardware.camera.concurrent.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.concurrent.xml \ + frameworks/native/data/etc/android.hardware.camera.front.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.front.xml \ + frameworks/native/data/etc/android.hardware.camera.full.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.full.xml \ + frameworks/native/data/etc/android.hardware.camera.raw.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.raw.xml \ + hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_back.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_back.json \ + hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_front.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_front.json \ + hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json \ + +endif + +ifneq ($(EMULATOR_VENDOR_NO_SOUND),true) +PRODUCT_PACKAGES += \ + android.hardware.audio.service \ + android.hardware.audio@7.1-impl.ranchu \ + android.hardware.audio.effect@7.0-impl \ + +DEVICE_MANIFEST_FILE += device/generic/goldfish/audio/android.hardware.audio.effects@7.0.xml + +PRODUCT_COPY_FILES += \ + device/generic/goldfish/audio/policy/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \ + device/generic/goldfish/audio/policy/primary_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/primary_audio_policy_configuration.xml \ + frameworks/av/services/audiopolicy/config/bluetooth_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/bluetooth_audio_policy_configuration_7_0.xml \ + frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \ + frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \ + frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \ + frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \ + +endif + +# WiFi: vendor side +PRODUCT_PACKAGES += \ + mac80211_create_radios \ + dhcpclient \ + hostapd \ + wpa_supplicant \ + +# Window Extensions +$(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk) + +# "Hello, world!" HAL implementations, mostly for compliance +PRODUCT_PACKAGES += \ + android.hardware.atrace@1.0-service \ + com.android.hardware.authsecret \ + android.hardware.contexthub-service.example \ + android.hardware.dumpstate-service.example \ + android.hardware.health-service.example \ + android.hardware.health.storage-service.default \ + android.hardware.lights-service.example \ + android.hardware.power-service.example \ + android.hardware.power.stats-service.example \ + com.android.hardware.rebootescrow \ + android.hardware.thermal@2.0-service.mock \ + android.hardware.vibrator-service.example + +# TVs don't use a hardware identity service. +ifneq ($(PRODUCT_IS_ATV_SDK),true) + PRODUCT_PACKAGES += \ + android.hardware.identity-service.example +endif + +PRODUCT_COPY_FILES += \ + device/generic/goldfish/data/empty_data_disk:data/empty_data_disk \ + device/generic/goldfish/data/etc/dtb.img:dtb.img \ + device/generic/goldfish/data/etc/encryptionkey.img:encryptionkey.img \ + device/generic/goldfish/emulator-info.txt:data/misc/emulator/version.txt \ + device/generic/goldfish/data/etc/apns-conf.xml:data/misc/apns/apns-conf.xml \ + device/generic/goldfish/radio/RadioConfig/radioconfig.xml:data/misc/emulator/config/radioconfig.xml \ + device/generic/goldfish/data/etc/iccprofile_for_sim0.xml:data/misc/modem_simulator/iccprofile_for_sim0.xml \ + device/google/cuttlefish/host/commands/modem_simulator/files/iccprofile_for_sim0_for_CtsCarrierApiTestCases.xml:data/misc/modem_simulator/iccprofile_for_carrierapitests.xml \ + device/generic/goldfish/data/etc/numeric_operator.xml:data/misc/modem_simulator/etc/modem_simulator/files/numeric_operator.xml \ + device/generic/goldfish/data/etc/local.prop:data/local.prop \ + device/generic/goldfish/init.ranchu.adb.setup.sh:$(TARGET_COPY_OUT_SYSTEM_EXT)/bin/init.ranchu.adb.setup.sh \ + device/generic/goldfish/init.ranchu-core.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.ranchu-core.sh \ + device/generic/goldfish/init.ranchu-net.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.ranchu-net.sh \ + device/generic/goldfish/init.ranchu.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.ranchu.rc \ + device/generic/goldfish/init.system_ext.rc:$(TARGET_COPY_OUT_SYSTEM_EXT)/etc/init/init.system_ext.rc \ + device/generic/goldfish/ueventd.ranchu.rc:$(TARGET_COPY_OUT_VENDOR)/etc/ueventd.rc \ + device/generic/goldfish/input/virtio_input_rotary.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_rotary.idc \ + device/generic/goldfish/input/qwerty2.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/qwerty2.idc \ + device/generic/goldfish/input/qwerty2.kcm:$(TARGET_COPY_OUT_VENDOR)/usr/keychars/qwerty2.kcm \ + device/generic/goldfish/input/qwerty2.kl:$(TARGET_COPY_OUT_VENDOR)/usr/keylayout/qwerty2.kl \ + device/generic/goldfish/input/virtio_input_multi_touch_1.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_1.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_2.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_2.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_3.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_3.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_4.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_4.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_5.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_5.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_6.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_6.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_7.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_7.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_8.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_8.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_9.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_9.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_10.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_10.idc \ + device/generic/goldfish/input/virtio_input_multi_touch_11.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/virtio_input_multi_touch_11.idc \ + device/generic/goldfish/display_settings_app_compat.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings_app_compat.xml \ + device/generic/goldfish/display_settings_freeform.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings_freeform.xml \ + device/generic/goldfish/display_settings.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings.xml \ + device/generic/goldfish/pixel_fold/device_state_configuration.xml:/data/misc/pixel_fold/devicestate/device_state_configuration.xml \ + device/generic/goldfish/pixel_fold/display_layout_configuration.xml:/data/misc/pixel_fold/displayconfig/display_layout_configuration.xml \ + device/generic/goldfish/pixel_fold/display_settings.xml:/data/misc/pixel_fold/display_settings.xml \ + device/generic/goldfish/data/etc/config.ini:config.ini \ + device/generic/goldfish/wifi/wpa_supplicant.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \ + frameworks/native/data/etc/android.hardware.bluetooth_le.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth_le.xml \ + frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \ + frameworks/native/data/etc/android.hardware.wifi.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.xml \ + frameworks/native/data/etc/android.hardware.wifi.passpoint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.passpoint.xml \ + frameworks/native/data/etc/android.hardware.wifi.direct.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.direct.xml \ + frameworks/av/media/libstagefright/data/media_codecs_google_audio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_audio.xml \ + frameworks/av/media/libstagefright/data/media_codecs_google_telephony.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_telephony.xml \ + frameworks/native/data/etc/android.hardware.touchscreen.multitouch.jazzhand.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.touchscreen.multitouch.jazzhand.xml \ + frameworks/native/data/etc/android.hardware.vulkan.level-1.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.level.xml \ + frameworks/native/data/etc/android.hardware.vulkan.compute-0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.compute.xml \ + frameworks/native/data/etc/android.hardware.vulkan.version-1_3.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.version.xml \ + frameworks/native/data/etc/android.software.vulkan.deqp.level-latest.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.vulkan.deqp.level.xml \ + frameworks/native/data/etc/android.software.opengles.deqp.level-latest.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.opengles.deqp.level.xml \ + frameworks/native/data/etc/android.software.autofill.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.autofill.xml \ + frameworks/native/data/etc/android.software.verified_boot.xml:${TARGET_COPY_OUT_PRODUCT}/etc/permissions/android.software.verified_boot.xml \ + device/generic/goldfish/data/etc/permissions/privapp-permissions-goldfish.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/permissions/privapp-permissions-goldfish.xml \ + +ifneq ($(EMULATOR_DISABLE_RADIO),true) +# Android TV ingests this file, but declares its own set of hardware permissions. +ifneq ($(PRODUCT_IS_ATV_SDK),true) + PRODUCT_COPY_FILES+= \ + device/generic/goldfish/data/etc/handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/handheld_core_hardware.xml +endif +endif + +# Goldfish uses 6.X kernels. +PRODUCT_ENABLE_UFFD_GC := true diff --git a/aosp/external/crosvm/rutabaga_gfx/Android.bp b/aosp/external/crosvm/rutabaga_gfx/Android.bp new file mode 100644 index 000000000..7543f48fc --- /dev/null +++ b/aosp/external/crosvm/rutabaga_gfx/Android.bp @@ -0,0 +1,109 @@ +// This file is generated by cargo_embargo. +// Do not modify this file as changes will be overridden on upgrade. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_crosvm_license" + // to get the below license kinds: + // SPDX-license-identifier-BSD + default_applicable_licenses: ["external_crosvm_license"], +} + +rust_library { + name: "librutabaga_gfx", + defaults: ["crosvm_inner_defaults"], + host_supported: true, + crate_name: "rutabaga_gfx", + cargo_env_compat: true, + cargo_pkg_version: "0.1.2", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "gfxstream", + "virgl_renderer", + ], + rustlibs: [ + "libcfg_if", + "liblibc", + "liblog_rust", + "libnix", + "libthiserror", + "libzerocopy", + ], + proc_macros: ["libremain"], + cfgs: [ + "gfxstream_unstable", + ], + shared_libs: [ + "libdrm", + "libepoxy", + "libgfxstream_backend", + "libvirglrenderer", + // "libcutils", // TODO: needed? + ], +} + +rust_library { + name: "librutabaga_gfx_gfxstream", + defaults: ["crosvm_inner_defaults"], + host_supported: true, + vendor_available: true, + crate_name: "rutabaga_gfx", + cargo_env_compat: true, + cargo_pkg_version: "0.1.2", + srcs: ["src/lib.rs"], + edition: "2021", + rustlibs: [ + "libcfg_if", + "liblibc", + "liblog_rust", + "libnix", + "libthiserror", + "libzerocopy", + ], + proc_macros: ["libremain"], + cfgs: [ + "gfxstream_unstable", + ], + features: ["gfxstream"], + shared_libs: [ + "libgfxstream_backend", + ], +} + +rust_test { + name: "rutabaga_gfx_test_src_lib", + defaults: ["crosvm_inner_defaults"], + host_supported: true, + crate_name: "rutabaga_gfx", + cargo_env_compat: true, + cargo_pkg_version: "0.1.2", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2021", + features: [ + "gfxstream", + "virgl_renderer", + ], + rustlibs: [ + "libcfg_if", + "liblibc", + "liblog_rust", + "libnix", + "libthiserror", + "libzerocopy", + ], + proc_macros: ["libremain"], + shared_libs: [ + "libdrm", + "libepoxy", + "libgfxstream_backend", + "libvirglrenderer", + // "libcutils", // TODO: needed? + ], +} diff --git a/aosp/external/minigbm/Android.bp b/aosp/external/minigbm/Android.bp new file mode 100644 index 000000000..25a1b0f29 --- /dev/null +++ b/aosp/external/minigbm/Android.bp @@ -0,0 +1,238 @@ +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package { + default_applicable_licenses: ["external_minigbm_license"], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// See: http://go/android-license-faq +license { + name: "external_minigbm_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-BSD", + "SPDX-license-identifier-MIT", + ], + license_text: [ + "LICENSE", + ], +} + +filegroup { + name: "minigbm_core_files", + + srcs: [ + "amdgpu.c", + "drv.c", + "drv_array_helpers.c", + "drv_helpers.c", + "dumb_driver.c", + "i915.c", + "mediatek.c", + "msm.c", + "rockchip.c", + "vc4.c", + "virtgpu.c", + "virtgpu_cross_domain.c", + "virtgpu_virgl.c", + ], +} + +filegroup { + name: "minigbm_gralloc_common_files", + + srcs: [ + "cros_gralloc/cros_gralloc_buffer.cc", + "cros_gralloc/cros_gralloc_helpers.cc", + "cros_gralloc/cros_gralloc_driver.cc", + ], +} + +filegroup { + name: "minigbm_gralloc0_files", + srcs: ["cros_gralloc/gralloc0/gralloc0.cc"], +} + +cc_defaults { + name: "minigbm_defaults", + + cflags: [ + "-D_GNU_SOURCE=1", + "-D_FILE_OFFSET_BITS=64", + "-Wall", + "-Wsign-compare", + "-Wpointer-arith", + "-Wcast-qual", + "-Wcast-align", + "-Wno-unused-parameter", + ], + + product_variables: { + platform_sdk_version: { + cflags: ["-DANDROID_API_LEVEL=%d"], + }, + }, +} + +cc_library_headers { + name: "minigbm_headers", + host_supported: true, + vendor_available: true, + export_include_dirs: ["."], +} + +cc_defaults { + name: "minigbm_cros_gralloc_defaults", + + defaults: ["minigbm_defaults"], + + header_libs: [ + "libhardware_headers", + "libnativebase_headers", + "libsystem_headers", + "minigbm_headers", + ], + + static_libs: ["libarect"], + + vendor: true, + + shared_libs: [ + "libcutils", + "libdmabufheap", + "libdrm", + "libnativewindow", + "libsync", + "liblog", + ], +} + +cc_defaults { + name: "minigbm_cros_gralloc_library_defaults", + + defaults: ["minigbm_cros_gralloc_defaults"], + srcs: [ + ":minigbm_core_files", + ":minigbm_gralloc_common_files", + ], +} + +cc_defaults { + name: "minigbm_cros_gralloc0_defaults", + + defaults: ["minigbm_cros_gralloc_defaults"], + relative_install_path: "hw", + + srcs: [":minigbm_gralloc0_files"], +} + +// Generic +cc_library_shared { + name: "libminigbm_gralloc", + defaults: ["minigbm_cros_gralloc_library_defaults"], + cflags: ["-DHAS_DMABUF_SYSTEM_HEAP"], +} + +cc_library_shared { + name: "gralloc.minigbm", + defaults: ["minigbm_cros_gralloc0_defaults"], + shared_libs: ["libminigbm_gralloc"], +} + +cc_library_headers { + name: "libminigbm_gralloc_headers", + host_supported: true, + vendor_available: true, + export_include_dirs: ["cros_gralloc"], + visibility: [ + "//device/generic/goldfish-opengl/system/hwc3:__subpackages__", + ], +} + +// Intel +cc_library_shared { + name: "libminigbm_gralloc_intel", + defaults: ["minigbm_cros_gralloc_library_defaults"], + cflags: ["-DDRV_I915"], + enabled: false, + arch: { + x86: { + enabled: true, + }, + x86_64: { + enabled: true, + }, + }, +} + +cc_library_shared { + name: "gralloc.minigbm_intel", + defaults: ["minigbm_cros_gralloc0_defaults"], + shared_libs: ["libminigbm_gralloc_intel"], + enabled: false, + arch: { + x86: { + enabled: true, + }, + x86_64: { + enabled: true, + }, + }, +} + +// Meson +cc_library_shared { + name: "libminigbm_gralloc_meson", + defaults: ["minigbm_cros_gralloc_library_defaults"], + cflags: ["-DDRV_MESON"], +} + +cc_library_shared { + name: "gralloc.minigbm_meson", + defaults: ["minigbm_cros_gralloc0_defaults"], + shared_libs: ["libminigbm_gralloc_meson"], +} + +// MSM +cc_library_shared { + name: "libminigbm_gralloc_msm", + defaults: ["minigbm_cros_gralloc_library_defaults"], + cflags: [ + "-DDRV_MSM", + "-DQCOM_DISABLE_COMPRESSED_NV12", + "-DHAS_DMABUF_SYSTEM_HEAP", + ], +} + +cc_library_shared { + name: "gralloc.minigbm_msm", + defaults: ["minigbm_cros_gralloc0_defaults"], + shared_libs: ["libminigbm_gralloc_msm"], +} + +// ARCVM +cc_library_shared { + name: "libminigbm_gralloc_arcvm", + defaults: ["minigbm_cros_gralloc_library_defaults"], + cflags: ["-DVIRTIO_GPU_NEXT"], +} + +cc_library_shared { + name: "gralloc.minigbm_arcvm", + defaults: ["minigbm_cros_gralloc0_defaults"], + shared_libs: ["libminigbm_gralloc_arcvm"], +} diff --git a/aosp/external/minijail/libminijail.c b/aosp/external/minijail/libminijail.c new file mode 100644 index 000000000..6d6175adb --- /dev/null +++ b/aosp/external/minijail/libminijail.c @@ -0,0 +1,3975 @@ +/* Copyright 2012 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#define _BSD_SOURCE +#define _DEFAULT_SOURCE +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "landlock_util.h" +#include "libminijail-private.h" +#include "libminijail.h" + +#include "signal_handler.h" +#include "syscall_filter.h" +#include "syscall_wrapper.h" +#include "system.h" +#include "util.h" + +/* Until these are reliably available in linux/prctl.h. */ +#ifndef PR_ALT_SYSCALL +#define PR_ALT_SYSCALL 0x43724f53 +#endif + +/* New cgroup namespace might not be in linux-headers yet. */ +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0x02000000 +#endif + +#define MAX_CGROUPS 10 /* 10 different controllers supported by Linux. */ + +#define MAX_RLIMITS 32 /* Currently there are 15 supported by Linux. */ + +#define MAX_PRESERVED_FDS 128U + +/* Keyctl commands. */ +#define KEYCTL_JOIN_SESSION_KEYRING 1 + +/* + * The userspace equivalent of MNT_USER_SETTABLE_MASK, which is the mask of all + * flags that can be modified by MS_REMOUNT. + */ +#define MS_USER_SETTABLE_MASK \ + (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_NODIRATIME | \ + MS_RELATIME | MS_RDONLY) + +/* + * TODO(b/235960683): Drop this after CrOS upgrades to glibc >= 2.34 + * because MS_NOSYMFOLLOW will be defined in sys/mount.h. + */ +#ifndef MS_NOSYMFOLLOW +/* Added locally in kernels 4.x+. */ +#define MS_NOSYMFOLLOW 256 +#endif + +struct minijail_rlimit { + int type; + rlim_t cur; + rlim_t max; +}; + +struct mountpoint { + char *src; + char *dest; + char *type; + char *data; + int has_data; + unsigned long flags; + struct mountpoint *next; +}; + +struct minijail_remount { + unsigned long remount_mode; + char *mount_name; + struct minijail_remount *next; +}; + +struct hook { + minijail_hook_t hook; + void *payload; + minijail_hook_event_t event; + struct hook *next; +}; + +struct fs_rule { + char *path; + uint64_t landlock_flags; + struct fs_rule *next; +}; + +struct preserved_fd { + int parent_fd; + int child_fd; +}; + +struct minijail { + /* + * WARNING: if you add a flag here you need to make sure it's + * accounted for in minijail_pre{enter|exec}() below. + */ + struct { + bool uid : 1; + bool gid : 1; + bool inherit_suppl_gids : 1; + bool set_suppl_gids : 1; + bool keep_suppl_gids : 1; + bool use_caps : 1; + bool capbset_drop : 1; + bool set_ambient_caps : 1; + bool vfs : 1; + bool enter_vfs : 1; + bool pids : 1; + bool ipc : 1; + bool uts : 1; + bool net : 1; + bool enter_net : 1; + bool ns_cgroups : 1; + bool userns : 1; + bool disable_setgroups : 1; + bool seccomp : 1; + bool remount_proc_ro : 1; + bool no_new_privs : 1; + bool seccomp_filter : 1; + bool seccomp_filter_tsync : 1; + bool seccomp_filter_logging : 1; + bool seccomp_filter_allow_speculation : 1; + bool chroot : 1; + bool pivot_root : 1; + bool mount_dev : 1; + bool mount_tmp : 1; + bool do_init : 1; + bool run_as_init : 1; + bool pid_file : 1; + bool cgroups : 1; + bool alt_syscall : 1; + bool reset_signal_mask : 1; + bool reset_signal_handlers : 1; + bool close_open_fds : 1; + bool new_session_keyring : 1; + bool forward_signals : 1; + bool setsid : 1; + } flags; + uid_t uid; + gid_t gid; + gid_t usergid; + char *user; + size_t suppl_gid_count; + gid_t *suppl_gid_list; + uint64_t caps; + uint64_t cap_bset; + pid_t initpid; + int mountns_fd; + int netns_fd; + char *chrootdir; + char *pid_file_path; + char *uidmap; + char *gidmap; + char *hostname; + char *preload_path; + size_t filter_len; + struct sock_fprog *filter_prog; + char *alt_syscall_table; + struct mountpoint *mounts_head; + struct mountpoint *mounts_tail; + size_t mounts_count; + unsigned long remount_mode; + struct minijail_remount *remounts_head; + struct minijail_remount *remounts_tail; + size_t tmpfs_size; + bool using_minimalistic_mountns; + struct fs_rule *fs_rules_head; + struct fs_rule *fs_rules_tail; + char *cgroups[MAX_CGROUPS]; + size_t cgroup_count; + struct minijail_rlimit rlimits[MAX_RLIMITS]; + size_t rlimit_count; + uint64_t securebits_skip_mask; + struct hook *hooks_head; + struct hook *hooks_tail; + struct preserved_fd preserved_fds[MAX_PRESERVED_FDS]; + size_t preserved_fd_count; + char *seccomp_policy_path; +}; + +static void run_hooks_or_die(const struct minijail *j, + minijail_hook_event_t event); + +static bool seccomp_is_logging_allowed(const struct minijail *j) +{ + return seccomp_default_ret_log() || j->flags.seccomp_filter_logging; +} + +static void free_mounts_list(struct minijail *j) +{ + while (j->mounts_head) { + struct mountpoint *m = j->mounts_head; + j->mounts_head = j->mounts_head->next; + free(m->data); + free(m->type); + free(m->dest); + free(m->src); + free(m); + } + // No need to clear mounts_head as we know it's NULL after the loop. + j->mounts_tail = NULL; +} + +static void free_remounts_list(struct minijail *j) +{ + while (j->remounts_head) { + struct minijail_remount *m = j->remounts_head; + j->remounts_head = j->remounts_head->next; + free(m->mount_name); + free(m); + } + // No need to clear remounts_head as we know it's NULL after the loop. + j->remounts_tail = NULL; +} + +/* + * Writes exactly n bytes from buf to file descriptor fd. + * Returns 0 on success or a negative error code on error. + */ +static int write_exactly(int fd, const void *buf, size_t n) +{ + const char *p = buf; + while (n > 0) { + const ssize_t written = write(fd, p, n); + if (written < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + p += written; + n -= written; + } + + return 0; +} + +/* Closes *pfd and sets it to -1. */ +static void close_and_reset(int *pfd) +{ + if (*pfd != -1) + close(*pfd); + *pfd = -1; +} + +/* + * Strip out flags meant for the parent. + * We keep things that are not inherited across execve(2) (e.g. capabilities), + * or are easier to set after execve(2) (e.g. seccomp filters). + */ +void minijail_preenter(struct minijail *j) +{ + j->flags.vfs = 0; + j->flags.enter_vfs = 0; + j->flags.ns_cgroups = 0; + j->flags.net = 0; + j->flags.uts = 0; + j->flags.remount_proc_ro = 0; + j->flags.pids = 0; + j->flags.do_init = 0; + j->flags.run_as_init = 0; + j->flags.pid_file = 0; + j->flags.cgroups = 0; + j->flags.forward_signals = 0; + j->flags.setsid = 0; + j->remount_mode = 0; + free_remounts_list(j); +} + +/* Adds a rule for a given path to apply once minijail is entered. */ +int add_fs_restriction_path(struct minijail *j, + const char *path, + uint64_t landlock_flags) +{ + struct fs_rule *r = calloc(1, sizeof(*r)); + if (!r) + return -ENOMEM; + r->path = strdup(path); + r->landlock_flags = landlock_flags; + + if (j->fs_rules_tail) { + j->fs_rules_tail->next = r; + j->fs_rules_tail = r; + } else { + j->fs_rules_head = r; + j->fs_rules_tail = r; + } + + return 0; +} + +bool mount_has_bind_flag(struct mountpoint *m) { + return !!(m->flags & MS_BIND); +} + +bool mount_has_readonly_flag(struct mountpoint *m) { + return !!(m->flags & MS_RDONLY); +} + +bool mount_events_allowed(struct mountpoint *m) { + return !!(m->flags & MS_SHARED) || !!(m->flags & MS_SLAVE); +} + +/* + * Strip out flags meant for the child. + * We keep things that are inherited across execve(2). + */ +void minijail_preexec(struct minijail *j) +{ + int vfs = j->flags.vfs; + int enter_vfs = j->flags.enter_vfs; + int ns_cgroups = j->flags.ns_cgroups; + int net = j->flags.net; + int uts = j->flags.uts; + int remount_proc_ro = j->flags.remount_proc_ro; + int userns = j->flags.userns; + if (j->user) + free(j->user); + j->user = NULL; + if (j->suppl_gid_list) + free(j->suppl_gid_list); + j->suppl_gid_list = NULL; + if (j->preload_path) + free(j->preload_path); + j->preload_path = NULL; + free_mounts_list(j); + memset(&j->flags, 0, sizeof(j->flags)); + /* Now restore anything we meant to keep. */ + j->flags.vfs = vfs; + j->flags.enter_vfs = enter_vfs; + j->flags.ns_cgroups = ns_cgroups; + j->flags.net = net; + j->flags.uts = uts; + j->flags.remount_proc_ro = remount_proc_ro; + j->flags.userns = userns; + /* Note, |pids| will already have been used before this call. */ +} + +/* Minijail API. */ + +struct minijail API *minijail_new(void) +{ + struct minijail *j = calloc(1, sizeof(struct minijail)); + if (j) { + j->remount_mode = MS_PRIVATE; + j->using_minimalistic_mountns = false; + } + return j; +} + +void API minijail_change_uid(struct minijail *j, uid_t uid) +{ + if (uid == 0) + die("useless change to uid 0"); + j->uid = uid; + j->flags.uid = 1; +} + +void API minijail_change_gid(struct minijail *j, gid_t gid) +{ + if (gid == 0) + die("useless change to gid 0"); + j->gid = gid; + j->flags.gid = 1; +} + +void API minijail_set_supplementary_gids(struct minijail *j, size_t size, + const gid_t *list) +{ + size_t i; + + if (j->flags.inherit_suppl_gids) + die("cannot inherit *and* set supplementary groups"); + if (j->flags.keep_suppl_gids) + die("cannot keep *and* set supplementary groups"); + + if (size == 0) { + /* Clear supplementary groups. */ + j->suppl_gid_list = NULL; + j->suppl_gid_count = 0; + j->flags.set_suppl_gids = 1; + return; + } + + /* Copy the gid_t array. */ + j->suppl_gid_list = calloc(size, sizeof(gid_t)); + if (!j->suppl_gid_list) { + die("failed to allocate internal supplementary group array"); + } + for (i = 0; i < size; i++) { + j->suppl_gid_list[i] = list[i]; + } + j->suppl_gid_count = size; + j->flags.set_suppl_gids = 1; +} + +void API minijail_keep_supplementary_gids(struct minijail *j) +{ + j->flags.keep_suppl_gids = 1; +} + +int API minijail_change_user(struct minijail *j, const char *user) +{ + uid_t uid; + gid_t gid; + int rc = lookup_user(user, &uid, &gid); + if (rc) + return rc; + minijail_change_uid(j, uid); + j->user = strdup(user); + if (!j->user) + return -ENOMEM; + j->usergid = gid; + return 0; +} + +int API minijail_change_group(struct minijail *j, const char *group) +{ + gid_t gid; + int rc = lookup_group(group, &gid); + if (rc) + return rc; + minijail_change_gid(j, gid); + return 0; +} + +void API minijail_use_seccomp(struct minijail *j) +{ + j->flags.seccomp = 1; +} + +void API minijail_no_new_privs(struct minijail *j) +{ + j->flags.no_new_privs = 1; +} + +void API minijail_use_seccomp_filter(struct minijail *j) +{ + j->flags.seccomp_filter = 1; +} + +void API minijail_set_seccomp_filter_tsync(struct minijail *j) +{ + if (j->filter_len > 0 && j->filter_prog != NULL) { + die("minijail_set_seccomp_filter_tsync() must be called " + "before minijail_parse_seccomp_filters()"); + } + + if (seccomp_is_logging_allowed(j) && !seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, we don't want to use + * SECCOMP_RET_TRAP to both kill the entire process and report + * failing syscalls, since it will be brittle. Just bail. + */ + die("SECCOMP_RET_LOG not available, cannot use logging with " + "thread sync at the same time"); + } + + j->flags.seccomp_filter_tsync = 1; +} + +void API minijail_set_seccomp_filter_allow_speculation(struct minijail *j) +{ + if (j->filter_len > 0 && j->filter_prog != NULL) { + die("minijail_set_seccomp_filter_allow_speculation() must be " + "called before minijail_parse_seccomp_filters()"); + } + + j->flags.seccomp_filter_allow_speculation = 1; +} + +void API minijail_log_seccomp_filter_failures(struct minijail *j) +{ + if (j->filter_len > 0 && j->filter_prog != NULL) { + die("minijail_log_seccomp_filter_failures() must be called " + "before minijail_parse_seccomp_filters()"); + } + + if (j->flags.seccomp_filter_tsync && !seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, we don't want to use + * SECCOMP_RET_TRAP to both kill the entire process and report + * failing syscalls, since it will be brittle. Just bail. + */ + die("SECCOMP_RET_LOG not available, cannot use thread sync " + "with logging at the same time"); + } + + if (debug_logging_allowed()) { + j->flags.seccomp_filter_logging = 1; + } else { + warn("non-debug build: ignoring request to enable seccomp " + "logging"); + } +} + +void API minijail_set_using_minimalistic_mountns(struct minijail *j) +{ + j->using_minimalistic_mountns = true; +} + +void API minijail_add_minimalistic_mountns_fs_rules(struct minijail *j) +{ + struct mountpoint *m = j->mounts_head; + bool landlock_enabled_by_profile = false; + if (!j->using_minimalistic_mountns) + return; + + /* Apply Landlock rules. */ + while (m) { + landlock_enabled_by_profile = true; + minijail_add_fs_restriction_rx(j, m->dest); + /* Allow rw if mounted as writable, or mount flags allow mount events.*/ + if (!mount_has_readonly_flag(m) || mount_events_allowed(m)) + minijail_add_fs_restriction_rw(j, m->dest); + m = m->next; + } + if (landlock_enabled_by_profile) { + minijail_enable_default_fs_restrictions(j); + minijail_add_fs_restriction_edit(j, "/dev"); + minijail_add_fs_restriction_ro(j, "/proc"); + if (j->flags.vfs) + minijail_add_fs_restriction_rw(j, "/tmp"); + } +} + +void API minijail_enable_default_fs_restrictions(struct minijail *j) +{ + // Common library locations. + minijail_add_fs_restriction_rx(j, "/lib"); + minijail_add_fs_restriction_rx(j, "/lib64"); + minijail_add_fs_restriction_rx(j, "/usr/lib"); + minijail_add_fs_restriction_rx(j, "/usr/lib64"); + // Common locations for services invoking Minijail. + minijail_add_fs_restriction_rx(j, "/bin"); + minijail_add_fs_restriction_rx(j, "/sbin"); + minijail_add_fs_restriction_rx(j, "/usr/sbin"); + minijail_add_fs_restriction_rx(j, "/usr/bin"); +} + +void API minijail_use_caps(struct minijail *j, uint64_t capmask) +{ + /* + * 'minijail_use_caps' configures a runtime-capabilities-only + * environment, including a bounding set matching the thread's runtime + * (permitted|inheritable|effective) sets. + * Therefore, it will override any existing bounding set configurations + * since the latter would allow gaining extra runtime capabilities from + * file capabilities. + */ + if (j->flags.capbset_drop) { + warn("overriding bounding set configuration"); + j->cap_bset = 0; + j->flags.capbset_drop = 0; + } + j->caps = capmask; + j->flags.use_caps = 1; +} + +void API minijail_capbset_drop(struct minijail *j, uint64_t capmask) +{ + if (j->flags.use_caps) { + /* + * 'minijail_use_caps' will have already configured a capability + * bounding set matching the (permitted|inheritable|effective) + * sets. Abort if the user tries to configure a separate + * bounding set. 'minijail_capbset_drop' and 'minijail_use_caps' + * are mutually exclusive. + */ + die("runtime capabilities already configured, can't drop " + "bounding set separately"); + } + j->cap_bset = capmask; + j->flags.capbset_drop = 1; +} + +void API minijail_set_ambient_caps(struct minijail *j) +{ + j->flags.set_ambient_caps = 1; +} + +void API minijail_reset_signal_mask(struct minijail *j) +{ + j->flags.reset_signal_mask = 1; +} + +void API minijail_reset_signal_handlers(struct minijail *j) +{ + j->flags.reset_signal_handlers = 1; +} + +void API minijail_namespace_vfs(struct minijail *j) +{ + j->flags.vfs = 1; +} + +void API minijail_namespace_enter_vfs(struct minijail *j, const char *ns_path) +{ + /* Note: Do not use O_CLOEXEC here. We'll close it after we use it. */ + int ns_fd = open(ns_path, O_RDONLY); + if (ns_fd < 0) { + pdie("failed to open namespace '%s'", ns_path); + } + j->mountns_fd = ns_fd; + j->flags.enter_vfs = 1; +} + +void API minijail_new_session_keyring(struct minijail *j) +{ + j->flags.new_session_keyring = 1; +} + +void API minijail_skip_setting_securebits(struct minijail *j, + uint64_t securebits_skip_mask) +{ + j->securebits_skip_mask = securebits_skip_mask; +} + +void API minijail_remount_mode(struct minijail *j, unsigned long mode) +{ + j->remount_mode = mode; +} + +void API minijail_skip_remount_private(struct minijail *j) +{ + j->remount_mode = 0; +} + +void API minijail_namespace_pids(struct minijail *j) +{ + j->flags.vfs = 1; + j->flags.remount_proc_ro = 1; + j->flags.pids = 1; + j->flags.do_init = 1; +} + +void API minijail_namespace_pids_rw_proc(struct minijail *j) +{ + j->flags.vfs = 1; + j->flags.pids = 1; + j->flags.do_init = 1; +} + +void API minijail_namespace_ipc(struct minijail *j) +{ + j->flags.ipc = 1; +} + +void API minijail_namespace_uts(struct minijail *j) +{ + j->flags.uts = 1; +} + +int API minijail_namespace_set_hostname(struct minijail *j, const char *name) +{ + if (j->hostname) + return -EINVAL; + minijail_namespace_uts(j); + j->hostname = strdup(name); + if (!j->hostname) + return -ENOMEM; + return 0; +} + +void API minijail_namespace_net(struct minijail *j) +{ + j->flags.net = 1; +} + +void API minijail_namespace_enter_net(struct minijail *j, const char *ns_path) +{ + /* Note: Do not use O_CLOEXEC here. We'll close it after we use it. */ + int ns_fd = open(ns_path, O_RDONLY); + if (ns_fd < 0) { + pdie("failed to open namespace '%s'", ns_path); + } + j->netns_fd = ns_fd; + j->flags.enter_net = 1; +} + +void API minijail_namespace_cgroups(struct minijail *j) +{ + j->flags.ns_cgroups = 1; +} + +void API minijail_close_open_fds(struct minijail *j) +{ + j->flags.close_open_fds = 1; +} + +void API minijail_remount_proc_readonly(struct minijail *j) +{ + j->flags.vfs = 1; + j->flags.remount_proc_ro = 1; +} + +void API minijail_namespace_user(struct minijail *j) +{ + j->flags.userns = 1; +} + +void API minijail_namespace_user_disable_setgroups(struct minijail *j) +{ + j->flags.disable_setgroups = 1; +} + +int API minijail_uidmap(struct minijail *j, const char *uidmap) +{ + j->uidmap = strdup(uidmap); + if (!j->uidmap) + return -ENOMEM; + char *ch; + for (ch = j->uidmap; *ch; ch++) { + if (*ch == ',') + *ch = '\n'; + } + return 0; +} + +int API minijail_gidmap(struct minijail *j, const char *gidmap) +{ + j->gidmap = strdup(gidmap); + if (!j->gidmap) + return -ENOMEM; + char *ch; + for (ch = j->gidmap; *ch; ch++) { + if (*ch == ',') + *ch = '\n'; + } + return 0; +} + +void API minijail_inherit_usergroups(struct minijail *j) +{ + j->flags.inherit_suppl_gids = 1; +} + +void API minijail_run_as_init(struct minijail *j) +{ + /* + * Since the jailed program will become 'init' in the new PID namespace, + * Minijail does not need to fork an 'init' process. + */ + j->flags.run_as_init = 1; +} + +int API minijail_enter_chroot(struct minijail *j, const char *dir) +{ + if (j->chrootdir) + return -EINVAL; + j->chrootdir = strdup(dir); + if (!j->chrootdir) + return -ENOMEM; + j->flags.chroot = 1; + return 0; +} + +int API minijail_enter_pivot_root(struct minijail *j, const char *dir) +{ + if (j->chrootdir) + return -EINVAL; + j->chrootdir = strdup(dir); + if (!j->chrootdir) + return -ENOMEM; + j->flags.pivot_root = 1; + return 0; +} + +char API *minijail_get_original_path(struct minijail *j, + const char *path_inside_chroot) +{ + struct mountpoint *b; + + b = j->mounts_head; + while (b) { + /* + * If |path_inside_chroot| is the exact destination of a + * mount, then the original path is exactly the source of + * the mount. + * for example: "-b /some/path/exe,/chroot/path/exe" + * mount source = /some/path/exe, mount dest = + * /chroot/path/exe Then when getting the original path of + * "/chroot/path/exe", the source of that mount, + * "/some/path/exe" is what should be returned. + */ + if (streq(b->dest, path_inside_chroot)) + return strdup(b->src); + + /* + * If |path_inside_chroot| is within the destination path of a + * mount, take the suffix of the chroot path relative to the + * mount destination path, and append it to the mount source + * path. + */ + if (!strncmp(b->dest, path_inside_chroot, strlen(b->dest))) { + const char *relative_path = + path_inside_chroot + strlen(b->dest); + return path_join(b->src, relative_path); + } + b = b->next; + } + + /* If there is a chroot path, append |path_inside_chroot| to that. */ + if (j->chrootdir) + return path_join(j->chrootdir, path_inside_chroot); + + /* No chroot, so the path outside is the same as it is inside. */ + return strdup(path_inside_chroot); +} + +void API minijail_mount_dev(struct minijail *j) +{ + j->flags.mount_dev = 1; +} + +void API minijail_mount_tmp(struct minijail *j) +{ + minijail_mount_tmp_size(j, 64 * 1024 * 1024); +} + +void API minijail_mount_tmp_size(struct minijail *j, size_t size) +{ + j->tmpfs_size = size; + j->flags.mount_tmp = 1; +} + +int API minijail_write_pid_file(struct minijail *j, const char *path) +{ + j->pid_file_path = strdup(path); + if (!j->pid_file_path) + return -ENOMEM; + j->flags.pid_file = 1; + return 0; +} + +int API minijail_add_to_cgroup(struct minijail *j, const char *path) +{ + if (j->cgroup_count >= MAX_CGROUPS) + return -ENOMEM; + j->cgroups[j->cgroup_count] = strdup(path); + if (!j->cgroups[j->cgroup_count]) + return -ENOMEM; + j->cgroup_count++; + j->flags.cgroups = 1; + return 0; +} + +int API minijail_rlimit(struct minijail *j, int type, rlim_t cur, rlim_t max) +{ + size_t i; + + if (j->rlimit_count >= MAX_RLIMITS) + return -ENOMEM; + /* It's an error if the caller sets the same rlimit multiple times. */ + for (i = 0; i < j->rlimit_count; i++) { + if (j->rlimits[i].type == type) + return -EEXIST; + } + + j->rlimits[j->rlimit_count].type = type; + j->rlimits[j->rlimit_count].cur = cur; + j->rlimits[j->rlimit_count].max = max; + j->rlimit_count++; + return 0; +} + +int API minijail_forward_signals(struct minijail *j) +{ + j->flags.forward_signals = 1; + return 0; +} + +int API minijail_create_session(struct minijail *j) +{ + j->flags.setsid = 1; + return 0; +} + +int API minijail_add_fs_restriction_rx(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ_EXECUTE); +} + +int API minijail_add_fs_restriction_ro(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, ACCESS_FS_ROUGHLY_READ); +} + +int API minijail_add_fs_restriction_rw(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_BASIC_WRITE); +} + +int API minijail_add_fs_restriction_advanced_rw(struct minijail *j, + const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_FULL_WRITE); +} + +int API minijail_add_fs_restriction_edit(struct minijail *j, + const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_EDIT); +} + +static bool is_valid_bind_path(const char *path) +{ + if (!block_symlinks_in_bindmount_paths()) { + return true; + } + + /* + * tokenize() will modify both the |prefixes| pointer and the contents + * of the string, so: + * -Copy |BINDMOUNT_ALLOWED_PREFIXES| since it lives in .rodata. + * -Save the original pointer for free()ing. + */ + char *prefixes = strdup(BINDMOUNT_ALLOWED_PREFIXES); + attribute_cleanup_str char *orig_prefixes = prefixes; + (void)orig_prefixes; + + char *prefix = NULL; + bool found_prefix = false; + if (!is_canonical_path(path)) { + while ((prefix = tokenize(&prefixes, ",")) != NULL) { + if (path_is_parent(prefix, path)) { + found_prefix = true; + break; + } + } + if (!found_prefix) { + /* + * If the path does not include one of the allowed + * prefixes, fail. + */ + warn("path '%s' is not a canonical path", path); + return false; + } + } + return true; +} + +int API minijail_mount_with_data(struct minijail *j, const char *src, + const char *dest, const char *type, + unsigned long flags, const char *data) +{ + struct mountpoint *m; + + if (*dest != '/') + return -EINVAL; + m = calloc(1, sizeof(*m)); + if (!m) + return -ENOMEM; + m->dest = strdup(dest); + if (!m->dest) + goto error; + m->src = strdup(src); + if (!m->src) + goto error; + m->type = strdup(type); + if (!m->type) + goto error; + + if (!data || !data[0]) { + /* + * Set up secure defaults for certain filesystems. Adding this + * fs-specific logic here kind of sucks, but considering how + * people use these in practice, it's probably OK. If they want + * the kernel defaults, they can pass data="" instead of NULL. + */ + if (streq(type, "tmpfs")) { + /* tmpfs defaults to mode=1777 and size=50%. */ + data = "mode=0755,size=10M"; + } + } + if (data) { + m->data = strdup(data); + if (!m->data) + goto error; + m->has_data = 1; + } + + /* If they don't specify any flags, default to secure ones. */ + if (flags == 0) + flags = MS_NODEV | MS_NOEXEC | MS_NOSUID; + m->flags = flags; + + /* + * Unless asked to enter an existing namespace, force vfs namespacing + * so the mounts don't leak out into the containing vfs namespace. + * If Minijail is being asked to enter the root vfs namespace this will + * leak mounts, but it's unlikely that the user would ask to do that by + * mistake. + */ + if (!j->flags.enter_vfs) + minijail_namespace_vfs(j); + + if (j->mounts_tail) + j->mounts_tail->next = m; + else + j->mounts_head = m; + j->mounts_tail = m; + j->mounts_count++; + + return 0; + +error: + free(m->type); + free(m->src); + free(m->dest); + free(m); + return -ENOMEM; +} + +int API minijail_mount(struct minijail *j, const char *src, const char *dest, + const char *type, unsigned long flags) +{ + return minijail_mount_with_data(j, src, dest, type, flags, NULL); +} + +int API minijail_bind(struct minijail *j, const char *src, const char *dest, + int writeable) +{ + unsigned long flags = MS_BIND; + + /* + * Check for symlinks in bind-mount source paths to warn the user early. + * Minijail will perform one final check immediately before the mount() + * call. + */ + if (!is_valid_bind_path(src)) { + warn("src '%s' is not a valid bind mount path", src); + return -ELOOP; + } + + /* + * Symlinks in |dest| are blocked by the ChromiumOS LSM: + * /security/chromiumos/lsm.c#77 + */ + + if (!writeable) + flags |= MS_RDONLY; + + /* + * |type| is ignored for bind mounts, use it to signal that this mount + * came from minijail_bind(). + * TODO(b/238362528): Implement a better way to signal this. + */ + return minijail_mount(j, src, dest, "minijail_bind", flags); +} + +int API minijail_add_remount(struct minijail *j, const char *mount_name, + unsigned long remount_mode) +{ + struct minijail_remount *m; + + if (*mount_name != '/') + return -EINVAL; + m = calloc(1, sizeof(*m)); + if (!m) + return -ENOMEM; + m->mount_name = strdup(mount_name); + if (!m->mount_name) { + free(m); + return -ENOMEM; + } + + m->remount_mode = remount_mode; + + if (j->remounts_tail) + j->remounts_tail->next = m; + else + j->remounts_head = m; + j->remounts_tail = m; + + return 0; +} + +int API minijail_add_hook(struct minijail *j, minijail_hook_t hook, + void *payload, minijail_hook_event_t event) +{ + struct hook *c; + + if (hook == NULL) + return -EINVAL; + if (event >= MINIJAIL_HOOK_EVENT_MAX) + return -EINVAL; + c = calloc(1, sizeof(*c)); + if (!c) + return -ENOMEM; + + c->hook = hook; + c->payload = payload; + c->event = event; + + if (j->hooks_tail) + j->hooks_tail->next = c; + else + j->hooks_head = c; + j->hooks_tail = c; + + return 0; +} + +int API minijail_preserve_fd(struct minijail *j, int parent_fd, int child_fd) +{ + if (parent_fd < 0 || child_fd < 0) + return -EINVAL; + if (j->preserved_fd_count >= MAX_PRESERVED_FDS) + return -ENOMEM; + j->preserved_fds[j->preserved_fd_count].parent_fd = parent_fd; + j->preserved_fds[j->preserved_fd_count].child_fd = child_fd; + j->preserved_fd_count++; + return 0; +} + +int API minijail_set_preload_path(struct minijail *j, const char *preload_path) +{ + if (j->preload_path) + return -EINVAL; + j->preload_path = strdup(preload_path); + if (!j->preload_path) + return -ENOMEM; + return 0; +} + +static void clear_seccomp_options(struct minijail *j) +{ + j->flags.seccomp_filter = 0; + j->flags.seccomp_filter_tsync = 0; + j->flags.seccomp_filter_logging = 0; + j->flags.seccomp_filter_allow_speculation = 0; + j->filter_len = 0; + j->filter_prog = NULL; + j->flags.no_new_privs = 0; + if (j->seccomp_policy_path) { + free(j->seccomp_policy_path); + } + j->seccomp_policy_path = NULL; +} + +static int seccomp_should_use_filters(struct minijail *j) +{ + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL) == -1) { + /* + * |errno| will be set to EINVAL when seccomp has not been + * compiled into the kernel. On certain platforms and kernel + * versions this is not a fatal failure. In that case, and only + * in that case, disable seccomp and skip loading the filters. + */ + if ((errno == EINVAL) && seccomp_can_softfail()) { + warn("not loading seccomp filters, seccomp filter not " + "supported"); + clear_seccomp_options(j); + return 0; + } + /* + * If |errno| != EINVAL or seccomp_can_softfail() is false, + * we can proceed. Worst case scenario minijail_enter() will + * abort() if seccomp fails. + */ + } + if (j->flags.seccomp_filter_tsync) { + /* Are the seccomp(2) syscall and the TSYNC option supported? */ + if (sys_seccomp(SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_TSYNC, NULL) == -1) { + int saved_errno = errno; + if (saved_errno == ENOSYS && seccomp_can_softfail()) { + warn("seccomp(2) syscall not supported"); + clear_seccomp_options(j); + return 0; + } else if (saved_errno == EINVAL && + seccomp_can_softfail()) { + warn( + "seccomp filter thread sync not supported"); + clear_seccomp_options(j); + return 0; + } + /* + * Similar logic here. If seccomp_can_softfail() is + * false, or |errno| != ENOSYS, or |errno| != EINVAL, + * we can proceed. Worst case scenario minijail_enter() + * will abort() if seccomp or TSYNC fail. + */ + } + } + if (j->flags.seccomp_filter_allow_speculation) { + /* Is the SPEC_ALLOW flag supported? */ + if (!seccomp_filter_flags_available( + SECCOMP_FILTER_FLAG_SPEC_ALLOW)) { + warn("allowing speculative execution on seccomp " + "processes not supported"); + j->flags.seccomp_filter_allow_speculation = 0; + } + } + return 1; +} + +static int set_seccomp_filters_internal(struct minijail *j, + const struct sock_fprog *filter, + bool owned) +{ + struct sock_fprog *fprog; + + if (owned) { + /* + * If |owned| is true, it's OK to cast away the const-ness since + * we'll own the pointer going forward. + */ + fprog = (struct sock_fprog *)filter; + } else { + fprog = malloc(sizeof(struct sock_fprog)); + if (!fprog) + return -ENOMEM; + fprog->len = filter->len; + fprog->filter = malloc(sizeof(struct sock_filter) * fprog->len); + if (!fprog->filter) { + free(fprog); + return -ENOMEM; + } + memcpy(fprog->filter, filter->filter, + sizeof(struct sock_filter) * fprog->len); + } + + if (j->filter_prog) { + free(j->filter_prog->filter); + free(j->filter_prog); + } + + j->filter_len = fprog->len; + j->filter_prog = fprog; + return 0; +} + +static int parse_seccomp_filters(struct minijail *j, const char *filename, + FILE *policy_file) +{ + struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog)); + if (!fprog) + return -ENOMEM; + + struct filter_options filteropts; + + /* + * Figure out filter options. + * Allow logging? + */ + filteropts.allow_logging = + debug_logging_allowed() && seccomp_is_logging_allowed(j); + + /* What to do on a blocked system call? */ + if (filteropts.allow_logging) { + if (seccomp_ret_log_available()) + filteropts.action = ACTION_RET_LOG; + else + filteropts.action = ACTION_RET_TRAP; + } else { + if (j->flags.seccomp_filter_tsync) { + if (seccomp_ret_kill_process_available()) { + filteropts.action = ACTION_RET_KILL_PROCESS; + } else { + filteropts.action = ACTION_RET_TRAP; + } + } else { + filteropts.action = ACTION_RET_KILL; + } + } + + /* + * If SECCOMP_RET_LOG is not available, need to allow extra syscalls + * for logging. + */ + filteropts.allow_syscalls_for_logging = + filteropts.allow_logging && !seccomp_ret_log_available(); + + /* Whether to fail on duplicate syscalls. */ + filteropts.allow_duplicate_syscalls = allow_duplicate_syscalls(); + + if (compile_filter(filename, policy_file, fprog, &filteropts)) { + free(fprog); + return -1; + } + + return set_seccomp_filters_internal(j, fprog, true /* owned */); +} + +void API minijail_parse_seccomp_filters(struct minijail *j, const char *path) +{ + if (!seccomp_should_use_filters(j)) + return; + + attribute_cleanup_fp FILE *file = fopen(path, "re"); + if (!file) { + pdie("failed to open seccomp filter file '%s'", path); + } + + if (parse_seccomp_filters(j, path, file) != 0) { + die("failed to compile seccomp filter BPF program in '%s'", + path); + } + if (j->seccomp_policy_path) { + free(j->seccomp_policy_path); + } + j->seccomp_policy_path = strdup(path); +} + +void API minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd) +{ + char *fd_path, *path; + attribute_cleanup_fp FILE *file = NULL; + + if (!seccomp_should_use_filters(j)) + return; + + file = fdopen(fd, "r"); + if (!file) { + pdie("failed to associate stream with fd %d", fd); + } + + if (asprintf(&fd_path, "/proc/self/fd/%d", fd) == -1) + pdie("failed to create path for fd %d", fd); + path = realpath(fd_path, NULL); + if (path == NULL) + pwarn("failed to get path of fd %d", fd); + free(fd_path); + + if (parse_seccomp_filters(j, path ? path : "", file) != 0) { + die("failed to compile seccomp filter BPF program from fd %d", + fd); + } + if (j->seccomp_policy_path) { + free(j->seccomp_policy_path); + } + j->seccomp_policy_path = path; +} + +void API minijail_set_seccomp_filters(struct minijail *j, + const struct sock_fprog *filter) +{ + if (!seccomp_should_use_filters(j)) + return; + + if (seccomp_is_logging_allowed(j)) { + die("minijail_log_seccomp_filter_failures() is incompatible " + "with minijail_set_seccomp_filters()"); + } + + /* + * set_seccomp_filters_internal() can only fail with ENOMEM. + * Furthermore, since we won't own the incoming filter, it will not be + * modified. + */ + if (set_seccomp_filters_internal(j, filter, false /* owned */) < 0) { + die("failed to set seccomp filter"); + } +} + +int API minijail_use_alt_syscall(struct minijail *j, const char *table) +{ + j->alt_syscall_table = strdup(table); + if (!j->alt_syscall_table) + return -ENOMEM; + j->flags.alt_syscall = 1; + return 0; +} + +struct marshal_state { + size_t available; + size_t total; + char *buf; +}; + +static void marshal_state_init(struct marshal_state *state, char *buf, + size_t available) +{ + state->available = available; + state->buf = buf; + state->total = 0; +} + +static void marshal_append(struct marshal_state *state, const void *src, + size_t length) +{ + size_t copy_len = MIN(state->available, length); + + /* Up to |available| will be written. */ + if (copy_len) { + memcpy(state->buf, src, copy_len); + state->buf += copy_len; + state->available -= copy_len; + } + /* |total| will contain the expected length. */ + state->total += length; +} + +static void marshal_append_string(struct marshal_state *state, const char *src) +{ + marshal_append(state, src, strlen(src) + 1); +} + +static void marshal_mount(struct marshal_state *state, + const struct mountpoint *m) +{ + marshal_append(state, m->src, strlen(m->src) + 1); + marshal_append(state, m->dest, strlen(m->dest) + 1); + marshal_append(state, m->type, strlen(m->type) + 1); + marshal_append(state, (char *)&m->has_data, sizeof(m->has_data)); + if (m->has_data) + marshal_append(state, m->data, strlen(m->data) + 1); + marshal_append(state, (char *)&m->flags, sizeof(m->flags)); +} + +static void minijail_marshal_helper(struct marshal_state *state, + const struct minijail *j) +{ + struct mountpoint *m = NULL; + size_t i; + + marshal_append(state, (char *)j, sizeof(*j)); + if (j->user) + marshal_append_string(state, j->user); + if (j->suppl_gid_list) { + marshal_append(state, j->suppl_gid_list, + j->suppl_gid_count * sizeof(gid_t)); + } + if (j->chrootdir) + marshal_append_string(state, j->chrootdir); + if (j->hostname) + marshal_append_string(state, j->hostname); + if (j->alt_syscall_table) { + marshal_append(state, j->alt_syscall_table, + strlen(j->alt_syscall_table) + 1); + } + if (j->flags.seccomp_filter && j->filter_prog) { + struct sock_fprog *fp = j->filter_prog; + marshal_append(state, (char *)fp->filter, + fp->len * sizeof(struct sock_filter)); + } + for (m = j->mounts_head; m; m = m->next) { + marshal_mount(state, m); + } + for (i = 0; i < j->cgroup_count; ++i) + marshal_append_string(state, j->cgroups[i]); + if (j->seccomp_policy_path) + marshal_append_string(state, j->seccomp_policy_path); +} + +size_t API minijail_size(const struct minijail *j) +{ + struct marshal_state state; + marshal_state_init(&state, NULL, 0); + minijail_marshal_helper(&state, j); + return state.total; +} + +int minijail_marshal(const struct minijail *j, char *buf, size_t available) +{ + struct marshal_state state; + marshal_state_init(&state, buf, available); + minijail_marshal_helper(&state, j); + return (state.total > available); +} + +int minijail_unmarshal(struct minijail *j, char *serialized, size_t length) +{ + size_t i; + size_t count; + int ret = -EINVAL; + + if (length < sizeof(*j)) + goto out; + memcpy((void *)j, serialized, sizeof(*j)); + serialized += sizeof(*j); + length -= sizeof(*j); + + /* Potentially stale pointers not used as signals. */ + j->preload_path = NULL; + j->pid_file_path = NULL; + j->uidmap = NULL; + j->gidmap = NULL; + j->mounts_head = NULL; + j->mounts_tail = NULL; + j->remounts_head = NULL; + j->remounts_tail = NULL; + j->filter_prog = NULL; + j->hooks_head = NULL; + j->hooks_tail = NULL; + j->fs_rules_head = NULL; + j->fs_rules_tail = NULL; + + if (j->user) { /* stale pointer */ + char *user = consumestr(&serialized, &length); + if (!user) + goto clear_pointers; + j->user = strdup(user); + if (!j->user) + goto clear_pointers; + } + + if (j->suppl_gid_list) { /* stale pointer */ + if (j->suppl_gid_count > NGROUPS_MAX) { + goto bad_gid_list; + } + size_t gid_list_size = j->suppl_gid_count * sizeof(gid_t); + void *gid_list_bytes = + consumebytes(gid_list_size, &serialized, &length); + if (!gid_list_bytes) + goto bad_gid_list; + + j->suppl_gid_list = calloc(j->suppl_gid_count, sizeof(gid_t)); + if (!j->suppl_gid_list) + goto bad_gid_list; + + memcpy(j->suppl_gid_list, gid_list_bytes, gid_list_size); + } + + if (j->chrootdir) { /* stale pointer */ + char *chrootdir = consumestr(&serialized, &length); + if (!chrootdir) + goto bad_chrootdir; + j->chrootdir = strdup(chrootdir); + if (!j->chrootdir) + goto bad_chrootdir; + } + + if (j->hostname) { /* stale pointer */ + char *hostname = consumestr(&serialized, &length); + if (!hostname) + goto bad_hostname; + j->hostname = strdup(hostname); + if (!j->hostname) + goto bad_hostname; + } + + if (j->alt_syscall_table) { /* stale pointer */ + char *alt_syscall_table = consumestr(&serialized, &length); + if (!alt_syscall_table) + goto bad_syscall_table; + j->alt_syscall_table = strdup(alt_syscall_table); + if (!j->alt_syscall_table) + goto bad_syscall_table; + } + + if (j->flags.seccomp_filter && j->filter_len > 0) { + size_t ninstrs = j->filter_len; + if (ninstrs > (SIZE_MAX / sizeof(struct sock_filter)) || + ninstrs > USHRT_MAX) + goto bad_filters; + + size_t program_len = ninstrs * sizeof(struct sock_filter); + void *program = consumebytes(program_len, &serialized, &length); + if (!program) + goto bad_filters; + + j->filter_prog = malloc(sizeof(struct sock_fprog)); + if (!j->filter_prog) + goto bad_filters; + + j->filter_prog->len = ninstrs; + j->filter_prog->filter = malloc(program_len); + if (!j->filter_prog->filter) + goto bad_filter_prog_instrs; + + memcpy(j->filter_prog->filter, program, program_len); + } + + count = j->mounts_count; + j->mounts_count = 0; + for (i = 0; i < count; ++i) { + unsigned long *flags; + int *has_data; + const char *dest; + const char *type; + const char *data = NULL; + const char *src = consumestr(&serialized, &length); + if (!src) + goto bad_mounts; + dest = consumestr(&serialized, &length); + if (!dest) + goto bad_mounts; + type = consumestr(&serialized, &length); + if (!type) + goto bad_mounts; + has_data = + consumebytes(sizeof(*has_data), &serialized, &length); + if (!has_data) + goto bad_mounts; + if (*has_data) { + data = consumestr(&serialized, &length); + if (!data) + goto bad_mounts; + } + flags = consumebytes(sizeof(*flags), &serialized, &length); + if (!flags) + goto bad_mounts; + if (minijail_mount_with_data(j, src, dest, type, *flags, data)) + goto bad_mounts; + } + + count = j->cgroup_count; + j->cgroup_count = 0; + for (i = 0; i < count; ++i) { + char *cgroup = consumestr(&serialized, &length); + if (!cgroup) + goto bad_cgroups; + j->cgroups[i] = strdup(cgroup); + if (!j->cgroups[i]) + goto bad_cgroups; + ++j->cgroup_count; + } + + if (j->seccomp_policy_path) { /* stale pointer */ + char *seccomp_policy_path = consumestr(&serialized, &length); + if (!seccomp_policy_path) + goto bad_cgroups; + j->seccomp_policy_path = strdup(seccomp_policy_path); + if (!j->seccomp_policy_path) + goto bad_cgroups; + } + + return 0; + + /* + * If more is added after j->seccomp_policy_path, then this is needed: + * if (j->seccomp_policy_path) + * free(j->seccomp_policy_path); + */ + +bad_cgroups: + free_mounts_list(j); + free_remounts_list(j); + for (i = 0; i < j->cgroup_count; ++i) + free(j->cgroups[i]); +bad_mounts: + if (j->filter_prog && j->filter_prog->filter) + free(j->filter_prog->filter); +bad_filter_prog_instrs: + if (j->filter_prog) + free(j->filter_prog); +bad_filters: + if (j->alt_syscall_table) + free(j->alt_syscall_table); +bad_syscall_table: + if (j->hostname) + free(j->hostname); +bad_hostname: + if (j->chrootdir) + free(j->chrootdir); +bad_chrootdir: + if (j->suppl_gid_list) + free(j->suppl_gid_list); +bad_gid_list: + if (j->user) + free(j->user); +clear_pointers: + j->user = NULL; + j->suppl_gid_list = NULL; + j->chrootdir = NULL; + j->hostname = NULL; + j->alt_syscall_table = NULL; + j->cgroup_count = 0; + j->seccomp_policy_path = NULL; +out: + return ret; +} + +struct dev_spec { + const char *name; + mode_t mode; + dev_t major, minor; +}; + +// clang-format off +static const struct dev_spec device_nodes[] = { + { +"null", + S_IFCHR | 0666, 1, 3, + }, + { + "zero", + S_IFCHR | 0666, 1, 5, + }, + { + "full", + S_IFCHR | 0666, 1, 7, + }, + { + "urandom", + S_IFCHR | 0444, 1, 9, + }, + { + "tty", + S_IFCHR | 0666, 5, 0, + }, +}; +// clang-format on + +struct dev_sym_spec { + const char *source, *dest; +}; + +static const struct dev_sym_spec device_symlinks[] = { + { + "ptmx", + "pts/ptmx", + }, + { + "fd", + "/proc/self/fd", + }, + { + "stdin", + "fd/0", + }, + { + "stdout", + "fd/1", + }, + { + "stderr", + "fd/2", + }, +}; + +/* + * Clean up the temporary dev path we had setup previously. In case of errors, + * we don't want to go leaking empty tempdirs. + */ +static void mount_dev_cleanup(char *dev_path) +{ + umount2(dev_path, MNT_DETACH); + rmdir(dev_path); + free(dev_path); +} + +/* + * Set up the pseudo /dev path at the temporary location. + * See mount_dev_finalize for more details. + */ +static int mount_dev(char **dev_path_ret) +{ + int ret; + attribute_cleanup_fd int dev_fd = -1; + size_t i; + mode_t mask; + char *dev_path; + + /* + * Create a temp path for the /dev init. We'll relocate this to the + * final location later on in the startup process. + */ + dev_path = *dev_path_ret = strdup("/tmp/minijail.dev.XXXXXX"); + if (dev_path == NULL || mkdtemp(dev_path) == NULL) + pdie("could not create temp path for /dev"); + + /* Set up the empty /dev mount point first. */ + ret = mount("minijail-devfs", dev_path, "tmpfs", MS_NOEXEC | MS_NOSUID, + "size=5M,mode=755"); + if (ret) { + rmdir(dev_path); + return ret; + } + + /* We want to set the mode directly from the spec. */ + mask = umask(0); + + /* Get a handle to the temp dev path for *at funcs below. */ + dev_fd = open(dev_path, O_DIRECTORY | O_PATH | O_CLOEXEC); + if (dev_fd < 0) { + ret = 1; + goto done; + } + + /* Create all the nodes in /dev. */ + for (i = 0; i < ARRAY_SIZE(device_nodes); ++i) { + const struct dev_spec *ds = &device_nodes[i]; + ret = mknodat(dev_fd, ds->name, ds->mode, + makedev(ds->major, ds->minor)); + if (ret) + goto done; + } + + /* Create all the symlinks in /dev. */ + for (i = 0; i < ARRAY_SIZE(device_symlinks); ++i) { + const struct dev_sym_spec *ds = &device_symlinks[i]; + ret = symlinkat(ds->dest, dev_fd, ds->source); + if (ret) + goto done; + } + + /* Create empty dir for glibc shared mem APIs. */ + ret = mkdirat(dev_fd, "shm", 01777); + if (ret) + goto done; + + /* Restore old mask. */ +done: + umask(mask); + + if (ret) + mount_dev_cleanup(dev_path); + + return ret; +} + +/* + * Relocate the temporary /dev mount to its final /dev place. + * We have to do this two step process so people can bind mount extra + * /dev paths like /dev/log. + */ +static int mount_dev_finalize(const struct minijail *j, char *dev_path) +{ + int ret = -1; + char *dest = NULL; + + /* Unmount the /dev mount if possible. */ + if (umount2("/dev", MNT_DETACH)) + goto done; + + if (asprintf(&dest, "%s/dev", j->chrootdir ?: "") < 0) + goto done; + + if (mount(dev_path, dest, NULL, MS_MOVE, NULL)) + goto done; + + ret = 0; +done: + free(dest); + mount_dev_cleanup(dev_path); + + return ret; +} + +/* + * mount_one: Applies mounts from @m for @j, recursing as needed. + * @j Minijail these mounts are for + * @m Head of list of mounts + * + * Returns 0 for success. + */ +static int mount_one(const struct minijail *j, struct mountpoint *m, + const char *dev_path) +{ + int ret; + char *dest; + bool do_remount = false; + bool has_bind_flag = mount_has_bind_flag(m); + bool has_remount_flag = !!(m->flags & MS_REMOUNT); + unsigned long original_mnt_flags = 0; + + /* We assume |dest| has a leading "/". */ + if (dev_path && strncmp("/dev/", m->dest, 5) == 0) { + /* + * Since the temp path is rooted at /dev, skip that dest part. + */ + if (asprintf(&dest, "%s%s", dev_path, m->dest + 4) < 0) + return -ENOMEM; + } else { + if (asprintf(&dest, "%s%s", j->chrootdir ?: "", m->dest) < 0) + return -ENOMEM; + } + + ret = setup_mount_destination(m->src, dest, j->uid, j->gid, + has_bind_flag); + if (ret) { + warn("cannot create mount target '%s'", dest); + goto error; + } + + /* + * Remount bind mounts that: + * - Come from the minijail_bind() API, and + * - Add the 'ro' flag + * since 'bind' and other flags can't both be specified in the same + * mount(2) call. + * Callers using minijail_mount() to perform bind mounts are expected to + * know what they're doing and call minijail_mount() with MS_REMOUNT as + * needed. + * Therefore, if the caller is asking for a remount (using MS_REMOUNT), + * there is no need to do an extra remount here. + */ + if (has_bind_flag && strcmp(m->type, "minijail_bind") == 0 && + !has_remount_flag) { + /* + * Grab the mount flags of the source. These are used to figure + * out whether the bind mount needs to be remounted read-only. + */ + if (get_mount_flags(m->src, &original_mnt_flags)) { + warn("cannot get mount flags for '%s'", m->src); + goto error; + } + + if ((m->flags & MS_RDONLY) != + (original_mnt_flags & MS_RDONLY)) { + do_remount = 1; + /* + * Restrict the mount flags to those that are + * user-settable in a MS_REMOUNT request, but excluding + * MS_RDONLY. The user-requested mount flags will + * dictate whether the remount will have that flag or + * not. + */ + original_mnt_flags &= + (MS_USER_SETTABLE_MASK & ~MS_RDONLY); + } + } + + /* + * Do a final check for symlinks in |m->src|. + * |m->src| will only contain a valid path when purely bind-mounting + * (but not when remounting a bind mount). + * + * Short of having a version of mount(2) that can take fd's, this is the + * smallest we can make the TOCTOU window. + */ + if (has_bind_flag && !has_remount_flag && !is_valid_bind_path(m->src)) { + warn("src '%s' is not a valid bind mount path", m->src); + goto error; + } + + ret = mount(m->src, dest, m->type, m->flags, m->data); + if (ret) { + pwarn("cannot mount '%s' as '%s' with flags %#lx", m->src, dest, + m->flags); + goto error; + } + + /* Remount *after* the initial mount. */ + if (do_remount) { + ret = + mount(m->src, dest, NULL, + m->flags | original_mnt_flags | MS_REMOUNT, m->data); + if (ret) { + pwarn( + "cannot bind-remount '%s' as '%s' with flags %#lx", + m->src, dest, + m->flags | original_mnt_flags | MS_REMOUNT); + goto error; + } + } + + free(dest); + if (m->next) + return mount_one(j, m->next, dev_path); + return 0; + +error: + free(dest); + return ret; +} + +static void process_mounts_or_die(const struct minijail *j) +{ + /* + * We have to mount /dev first in case there are bind mounts from + * the original /dev into the new unique tmpfs one. + */ + char *dev_path = NULL; + if (j->flags.mount_dev && mount_dev(&dev_path)) + pdie("mount_dev failed"); + + if (j->mounts_head && mount_one(j, j->mounts_head, dev_path)) { + warn("mount_one failed with /dev at '%s'", dev_path); + + if (dev_path) + mount_dev_cleanup(dev_path); + + _exit(MINIJAIL_ERR_MOUNT); + } + + /* + * Once all bind mounts have been processed, move the temp dev to + * its final /dev home. + */ + if (j->flags.mount_dev && mount_dev_finalize(j, dev_path)) + pdie("mount_dev_finalize failed"); +} + +static int enter_chroot(const struct minijail *j) +{ + run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_CHROOT); + + if (chroot(j->chrootdir)) + return -errno; + + if (chdir("/")) + return -errno; + + return 0; +} + +static int enter_pivot_root(const struct minijail *j) +{ + attribute_cleanup_fd int oldroot = -1; + attribute_cleanup_fd int newroot = -1; + + run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_CHROOT); + + /* + * Keep the fd for both old and new root. + * It will be used in fchdir(2) later. + */ + oldroot = open("/", O_DIRECTORY | O_RDONLY | O_CLOEXEC); + if (oldroot < 0) + pdie("failed to open / for fchdir"); + newroot = open(j->chrootdir, O_DIRECTORY | O_RDONLY | O_CLOEXEC); + if (newroot < 0) + pdie("failed to open %s for fchdir", j->chrootdir); + + /* + * To ensure j->chrootdir is the root of a filesystem, + * do a self bind mount. + */ + if (mount(j->chrootdir, j->chrootdir, "bind", MS_BIND | MS_REC, "")) + pdie("failed to bind mount '%s'", j->chrootdir); + if (chdir(j->chrootdir)) + return -errno; + if (syscall(SYS_pivot_root, ".", ".")) + pdie("pivot_root"); + + /* + * Now the old root is mounted on top of the new root. Use fchdir(2) to + * change to the old root and unmount it. + */ + if (fchdir(oldroot)) + pdie("failed to fchdir to old /"); + + /* + * If skip_remount_private was enabled for minijail_enter(), + * there could be a shared mount point under |oldroot|. In that case, + * mounts under this shared mount point will be unmounted below, and + * this unmounting will propagate to the original mount namespace + * (because the mount point is shared). To prevent this unexpected + * unmounting, remove these mounts from their peer groups by recursively + * remounting them as MS_PRIVATE. + */ + if (mount(NULL, ".", NULL, MS_REC | MS_PRIVATE, NULL)) + pdie("failed to mount(/, private) before umount(/)"); + /* The old root might be busy, so use lazy unmount. */ + if (umount2(".", MNT_DETACH)) + pdie("umount(/)"); + /* Change back to the new root. */ + if (fchdir(newroot)) + return -errno; + if (chroot("/")) + return -errno; + /* Set correct CWD for getcwd(3). */ + if (chdir("/")) + return -errno; + + return 0; +} + +static int mount_tmp(const struct minijail *j) +{ + const char fmt[] = "size=%zu,mode=1777"; + /* Count for the user storing ULLONG_MAX literally + extra space. */ + char data[sizeof(fmt) + sizeof("18446744073709551615ULL")]; + int ret; + + ret = snprintf(data, sizeof(data), fmt, j->tmpfs_size); + + if (ret <= 0) + pdie("tmpfs size spec error"); + else if ((size_t)ret >= sizeof(data)) + pdie("tmpfs size spec too large"); + + unsigned long flags = MS_NODEV | MS_NOEXEC | MS_NOSUID; + + if (block_symlinks_in_noninit_mountns_tmp()) { + flags |= MS_NOSYMFOLLOW; + } + + return mount("none", "/tmp", "tmpfs", flags, data); +} + +static int remount_proc_readonly(const struct minijail *j) +{ + const char *kProcPath = "/proc"; + const unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID; + /* + * Right now, we're holding a reference to our parent's old mount of + * /proc in our namespace, which means using MS_REMOUNT here would + * mutate our parent's mount as well, even though we're in a VFS + * namespace (!). Instead, remove their mount from our namespace lazily + * (MNT_DETACH) and make our own. + * + * However, we skip this in the user namespace case because it will + * invariably fail. Every mount namespace is "owned" by the + * user namespace of the process that creates it. Mount namespace A is + * "less privileged" than mount namespace B if A is created off of B, + * and B is owned by a different user namespace. + * When a less privileged mount namespace is created, the mounts used to + * initialize it (coming from the more privileged mount namespace) come + * as a unit, and are locked together. This means that code running in + * the new mount (and user) namespace cannot piecemeal unmount + * individual mounts inherited from a more privileged mount namespace. + * See https://man7.org/linux/man-pages/man7/mount_namespaces.7.html, + * "Restrictions on mount namespaces" for details. + * + * This happens in our use case because we first enter a new user + * namespace (on clone(2)) and then we unshare(2) a new mount namespace, + * which means the new mount namespace is less privileged than its + * parent mount namespace. This would also happen if we entered a new + * mount namespace on clone(2), since the user namespace is created + * first. + * In all other non-user-namespace cases the new mount namespace is + * similarly privileged as the parent mount namespace so unmounting a + * single mount is allowed. + * + * We still remount /proc as read-only in the user namespace case + * because while a process with CAP_SYS_ADMIN in the new user namespace + * can unmount the RO mount and get at the RW mount, an attacker with + * access only to a write primitive will not be able to modify /proc. + */ + if (!j->flags.userns && umount2(kProcPath, MNT_DETACH)) + return -errno; + if (mount("proc", kProcPath, "proc", kSafeFlags | MS_RDONLY, "")) + return -errno; + return 0; +} + +static void kill_child_and_die(const struct minijail *j, const char *msg) +{ + kill(j->initpid, SIGKILL); + die("%s", msg); +} + +static void write_pid_file_or_die(const struct minijail *j) +{ + if (write_pid_to_path(j->initpid, j->pid_file_path)) + kill_child_and_die(j, "failed to write pid file"); +} + +static void add_to_cgroups_or_die(const struct minijail *j) +{ + size_t i; + + for (i = 0; i < j->cgroup_count; ++i) { + if (write_pid_to_path(j->initpid, j->cgroups[i])) + kill_child_and_die(j, "failed to add to cgroups"); + } +} + +static void set_rlimits_or_die(const struct minijail *j) +{ + size_t i; + + for (i = 0; i < j->rlimit_count; ++i) { + struct rlimit limit; + limit.rlim_cur = j->rlimits[i].cur; + limit.rlim_max = j->rlimits[i].max; + if (prlimit(j->initpid, j->rlimits[i].type, &limit, NULL)) + kill_child_and_die(j, "failed to set rlimit"); + } +} + +static void write_ugid_maps_or_die(const struct minijail *j) +{ + if (j->uidmap && write_proc_file(j->initpid, j->uidmap, "uid_map") != 0) + kill_child_and_die(j, "failed to write uid_map"); + if (j->gidmap && j->flags.disable_setgroups) { + /* + * Older kernels might not have the /proc//setgroups files. + */ + int ret = write_proc_file(j->initpid, "deny", "setgroups"); + if (ret != 0) { + if (ret == -ENOENT) { + /* + * See + * http://man7.org/linux/man-pages/man7/user_namespaces.7.html. + */ + warn("could not disable setgroups(2)"); + } else + kill_child_and_die( + j, "failed to disable setgroups(2)"); + } + } + if (j->gidmap && write_proc_file(j->initpid, j->gidmap, "gid_map") != 0) + kill_child_and_die(j, "failed to write gid_map"); +} + +static void enter_user_namespace(const struct minijail *j) +{ + int uid = j->flags.uid ? j->uid : 0; + int gid = j->flags.gid ? j->gid : 0; + if (j->gidmap && setresgid(gid, gid, gid)) { + pdie("user_namespaces: setresgid(%d, %d, %d) failed", gid, gid, + gid); + } + if (j->uidmap && setresuid(uid, uid, uid)) { + pdie("user_namespaces: setresuid(%d, %d, %d) failed", uid, uid, + uid); + } +} + +static void parent_setup_complete(int *pipe_fds) +{ + close_and_reset(&pipe_fds[0]); + close_and_reset(&pipe_fds[1]); +} + +/* + * wait_for_parent_setup: Called by the child process to wait for any + * further parent-side setup to complete before continuing. + */ +static void wait_for_parent_setup(int *pipe_fds) +{ + char buf; + + close_and_reset(&pipe_fds[1]); + + /* Wait for parent to complete setup and close the pipe. */ + if (read(pipe_fds[0], &buf, 1) != 0) + die("failed to sync with parent"); + close_and_reset(&pipe_fds[0]); +} + +static void drop_ugid(const struct minijail *j) +{ + if (j->flags.inherit_suppl_gids + j->flags.keep_suppl_gids + + j->flags.set_suppl_gids > + 1) { + die("can only do one of inherit, keep, or set supplementary " + "groups"); + } + + if (j->flags.inherit_suppl_gids) { + if (initgroups(j->user, j->usergid)) + pdie("initgroups(%s, %d) failed", j->user, j->usergid); + } else if (j->flags.set_suppl_gids) { + if (setgroups(j->suppl_gid_count, j->suppl_gid_list)) + pdie("setgroups(suppl_gids) failed"); + } else if (!j->flags.keep_suppl_gids && !j->flags.disable_setgroups) { + /* + * Only attempt to clear supplementary groups if we are changing + * users or groups, and if the caller did not request to disable + * setgroups (used when entering a user namespace as a + * non-privileged user). + */ + if ((j->flags.uid || j->flags.gid) && setgroups(0, NULL)) + pdie("setgroups(0, NULL) failed"); + } + + if (j->flags.gid && setresgid(j->gid, j->gid, j->gid)) + pdie("setresgid(%d, %d, %d) failed", j->gid, j->gid, j->gid); + + if (j->flags.uid && setresuid(j->uid, j->uid, j->uid)) + pdie("setresuid(%d, %d, %d) failed", j->uid, j->uid, j->uid); +} + +static void drop_capbset(uint64_t keep_mask, unsigned int last_valid_cap) +{ + const uint64_t one = 1; + unsigned int i; + for (i = 0; i < sizeof(keep_mask) * 8 && i <= last_valid_cap; ++i) { + if (keep_mask & (one << i)) + continue; + if (prctl(PR_CAPBSET_DROP, i)) + pdie("could not drop capability from bounding set"); + } +} + +static void drop_caps(const struct minijail *j, unsigned int last_valid_cap) +{ + if (!j->flags.use_caps) + return; + + cap_t caps = cap_get_proc(); + cap_value_t flag[1]; + const size_t ncaps = sizeof(j->caps) * 8; + const uint64_t one = 1; + unsigned int i; + if (!caps) + die("can't get process caps"); + if (cap_clear(caps)) + die("can't clear caps"); + + for (i = 0; i < ncaps && i <= last_valid_cap; ++i) { + /* Keep CAP_SETPCAP for dropping bounding set bits. */ + if (i != CAP_SETPCAP && !(j->caps & (one << i))) + continue; + flag[0] = i; + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, flag, CAP_SET)) + die("can't add effective cap"); + if (cap_set_flag(caps, CAP_PERMITTED, 1, flag, CAP_SET)) + die("can't add permitted cap"); + if (cap_set_flag(caps, CAP_INHERITABLE, 1, flag, CAP_SET)) + die("can't add inheritable cap"); + } + if (cap_set_proc(caps)) + die("can't apply initial cleaned capset"); + + /* + * Instead of dropping the bounding set first, do it here in case + * the caller had a more permissive bounding set which could + * have been used above to raise a capability that wasn't already + * present. This requires CAP_SETPCAP, so we raised/kept it above. + * + * However, if we're asked to skip setting *and* locking the + * SECURE_NOROOT securebit, also skip dropping the bounding set. + * If the caller wants to regain all capabilities when executing a + * set-user-ID-root program, allow them to do so. The default behavior + * (i.e. the behavior without |securebits_skip_mask| set) will still put + * the jailed process tree in a capabilities-only environment. + * + * We check the negated skip mask for SECURE_NOROOT and + * SECURE_NOROOT_LOCKED. If the bits are set in the negated mask they + * will *not* be skipped in lock_securebits(), and therefore we should + * drop the bounding set. + */ + if (secure_noroot_set_and_locked(~j->securebits_skip_mask)) { + drop_capbset(j->caps, last_valid_cap); + } else { + warn("SECURE_NOROOT not set, not dropping bounding set"); + } + + /* If CAP_SETPCAP wasn't specifically requested, now we remove it. */ + if ((j->caps & (one << CAP_SETPCAP)) == 0) { + flag[0] = CAP_SETPCAP; + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, flag, CAP_CLEAR)) + die("can't clear effective cap"); + if (cap_set_flag(caps, CAP_PERMITTED, 1, flag, CAP_CLEAR)) + die("can't clear permitted cap"); + if (cap_set_flag(caps, CAP_INHERITABLE, 1, flag, CAP_CLEAR)) + die("can't clear inheritable cap"); + } + + if (cap_set_proc(caps)) + die("can't apply final cleaned capset"); + + /* + * If ambient capabilities are supported, clear all capabilities first, + * then raise the requested ones. + */ + if (j->flags.set_ambient_caps) { + if (!cap_ambient_supported()) { + pdie("ambient capabilities not supported"); + } + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) != + 0) { + pdie("can't clear ambient capabilities"); + } + + for (i = 0; i < ncaps && i <= last_valid_cap; ++i) { + if (!(j->caps & (one << i))) + continue; + + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, + 0) != 0) { + pdie("prctl(PR_CAP_AMBIENT, " + "PR_CAP_AMBIENT_RAISE, %u) failed", + i); + } + } + } + + cap_free(caps); +} + +/* Creates a ruleset for current inodes then calls landlock_restrict_self(). */ +static void apply_landlock_restrictions(const struct minijail *j) +{ + struct fs_rule *r; + attribute_cleanup_fd int ruleset_fd = -1; + + r = j->fs_rules_head; + while (r) { + if (ruleset_fd < 0) { + struct minijail_landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = HANDLED_ACCESS_TYPES + }; + ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) { + const int err = errno; + pwarn("Failed to create a ruleset"); + switch (err) { + case ENOSYS: + pwarn("Landlock is not supported by the current kernel"); + break; + case EOPNOTSUPP: + pwarn("Landlock is currently disabled by kernel config"); + break; + } + return; + } + } + populate_ruleset_internal(r->path, ruleset_fd, r->landlock_flags); + r = r->next; + } + + if (ruleset_fd >= 0) { + if (landlock_restrict_self(ruleset_fd, 0)) { + pdie("Failed to enforce ruleset"); + } + } +} + +static void set_seccomp_filter(const struct minijail *j) +{ + /* + * Set no_new_privs. See and + * in the kernel source tree for an explanation of the parameters. + */ + if (j->flags.no_new_privs) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + pdie("prctl(PR_SET_NO_NEW_PRIVS)"); + } + + /* + * Code running with ASan + * (https://github.com/google/sanitizers/wiki/AddressSanitizer) + * will make system calls not included in the syscall filter policy, + * which will likely crash the program. Skip setting seccomp filter in + * that case. + * 'running_with_asan()' has no inputs and is completely defined at + * build time, so this cannot be used by an attacker to skip setting + * seccomp filter. + */ + if (j->flags.seccomp_filter && running_with_asan()) { + warn("running with (HW)ASan, not setting seccomp filter"); + return; + } + + if (j->flags.seccomp_filter) { + if (seccomp_is_logging_allowed(j)) { + warn("logging seccomp filter failures"); + if (!seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, + * install the SIGSYS handler first. + */ + if (install_sigsys_handler()) + pdie( + "failed to install SIGSYS handler"); + } + } else if (j->flags.seccomp_filter_tsync) { + /* + * If setting thread sync, + * reset the SIGSYS signal handler so that + * the entire thread group is killed. + */ + if (signal(SIGSYS, SIG_DFL) == SIG_ERR) + pdie("failed to reset SIGSYS disposition"); + } + } + + /* + * Install the syscall filter. + */ +#if 0 + if (j->flags.seccomp_filter) { + if (j->flags.seccomp_filter_tsync || + j->flags.seccomp_filter_allow_speculation) { + int filter_flags = + (j->flags.seccomp_filter_tsync + ? SECCOMP_FILTER_FLAG_TSYNC + : 0) | + (j->flags.seccomp_filter_allow_speculation + ? SECCOMP_FILTER_FLAG_SPEC_ALLOW + : 0); + if (sys_seccomp(SECCOMP_SET_MODE_FILTER, filter_flags, + j->filter_prog)) { + pdie("seccomp(tsync) failed"); + } + } else { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, + j->filter_prog)) { + pdie("prctl(seccomp_filter) failed"); + } + } + } +#endif +} + +static pid_t forward_pid = -1; + +static void forward_signal(int sig, siginfo_t *siginfo attribute_unused, + void *void_context attribute_unused) +{ + if (forward_pid != -1) { + kill(forward_pid, sig); + } +} + +static void install_signal_handlers(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = &forward_signal; + act.sa_flags = SA_SIGINFO | SA_RESTART; + + /* Handle all signals, except SIGCHLD. */ + for (int sig = 1; sig < NSIG; sig++) { + /* + * We don't care if we get EINVAL: that just means that we + * can't handle this signal, so let's skip it and continue. + */ + sigaction(sig, &act, NULL); + } + /* Reset SIGCHLD's handler. */ + signal(SIGCHLD, SIG_DFL); + + /* Handle real-time signals. */ + for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++) { + sigaction(sig, &act, NULL); + } +} + +static const char *lookup_hook_name(minijail_hook_event_t event) +{ + switch (event) { + case MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS: + return "pre-drop-caps"; + case MINIJAIL_HOOK_EVENT_PRE_EXECVE: + return "pre-execve"; + case MINIJAIL_HOOK_EVENT_PRE_CHROOT: + return "pre-chroot"; + case MINIJAIL_HOOK_EVENT_MAX: + /* + * Adding this in favor of a default case to force the + * compiler to error out if a new enum value is added. + */ + break; + } + return "unknown"; +} + +static void run_hooks_or_die(const struct minijail *j, + minijail_hook_event_t event) +{ + int rc; + int hook_index = 0; + for (struct hook *c = j->hooks_head; c; c = c->next) { + if (c->event != event) + continue; + rc = c->hook(c->payload); + if (rc != 0) { + errno = -rc; + pdie("%s hook (index %d) failed", + lookup_hook_name(event), hook_index); + } + /* Only increase the index within the same hook event type. */ + ++hook_index; + } +} + +void API minijail_enter(const struct minijail *j) +{ + /* + * If we're dropping caps, get the last valid cap from /proc now, + * since /proc can be unmounted before drop_caps() is called. + */ + unsigned int last_valid_cap = 0; + if (j->flags.capbset_drop || j->flags.use_caps) + last_valid_cap = get_last_valid_cap(); + + if (j->flags.pids) + die("tried to enter a pid-namespaced jail;" + " try minijail_run()?"); + + if (j->flags.inherit_suppl_gids && !j->user) + die("cannot inherit supplementary groups without setting a " + "username"); + + /* + * We can't recover from failures if we've dropped privileges partially, + * so we don't even try. If any of our operations fail, we abort() the + * entire process. + */ + if (j->flags.enter_vfs) { + if (setns(j->mountns_fd, CLONE_NEWNS)) + pdie("setns(CLONE_NEWNS) failed"); + close(j->mountns_fd); + } + + if (j->flags.vfs) { + if (unshare(CLONE_NEWNS)) + pdie("unshare(CLONE_NEWNS) failed"); + /* + * By default, remount all filesystems as private, unless + * - Passed a specific remount mode, in which case remount with + * that, + * - Asked not to remount at all, in which case skip the + * mount(2) call. + * https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt + */ + if (j->remount_mode) { + if (mount(NULL, "/", NULL, MS_REC | j->remount_mode, + NULL)) + pdie("mount(NULL, /, NULL, " + "MS_REC | j->remount_mode, NULL) failed"); + + struct minijail_remount *temp = j->remounts_head; + while (temp) { + if (temp->remount_mode < j->remount_mode) + die("cannot remount %s as stricter " + "than the root dir", + temp->mount_name); + if (mount(NULL, temp->mount_name, NULL, + MS_REC | temp->remount_mode, NULL)) + pdie("mount(NULL, %s, NULL, " + "MS_REC | temp->remount_mode, " + "NULL) failed", + temp->mount_name); + temp = temp->next; + } + } + } + + if (j->flags.ipc && unshare(CLONE_NEWIPC)) { + pdie("unshare(CLONE_NEWIPC) failed"); + } + + if (j->flags.uts) { + if (unshare(CLONE_NEWUTS)) + pdie("unshare(CLONE_NEWUTS) failed"); + + if (j->hostname && + sethostname(j->hostname, strlen(j->hostname))) + pdie("sethostname(%s) failed", j->hostname); + } + + if (j->flags.enter_net) { + if (setns(j->netns_fd, CLONE_NEWNET)) + pdie("setns(CLONE_NEWNET) failed"); + close(j->netns_fd); + } else if (j->flags.net) { + if (unshare(CLONE_NEWNET)) + pdie("unshare(CLONE_NEWNET) failed"); + config_net_loopback(); + } + + if (j->flags.ns_cgroups && unshare(CLONE_NEWCGROUP)) + pdie("unshare(CLONE_NEWCGROUP) failed"); + + if (j->flags.new_session_keyring) { + if (syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0) + pdie("keyctl(KEYCTL_JOIN_SESSION_KEYRING) failed"); + } + + /* We have to process all the mounts before we chroot/pivot_root. */ + process_mounts_or_die(j); + + if (j->flags.chroot && enter_chroot(j)) + pdie("chroot"); + + if (j->flags.pivot_root && enter_pivot_root(j)) + pdie("pivot_root"); + + if (j->flags.mount_tmp && mount_tmp(j)) + pdie("mount_tmp"); + + if (j->flags.remount_proc_ro && remount_proc_readonly(j)) + pdie("remount"); + + run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS); + + /* + * If we're only dropping capabilities from the bounding set, but not + * from the thread's (permitted|inheritable|effective) sets, do it now. + */ + if (j->flags.capbset_drop) { + drop_capbset(j->cap_bset, last_valid_cap); + } + + /* + * POSIX capabilities are a bit tricky. We must set SECBIT_KEEP_CAPS + * before drop_ugid() below as the latter would otherwise drop all + * capabilities. + */ + if (j->flags.use_caps) { + /* + * When using ambient capabilities, CAP_SET{GID,UID} can be + * inherited across execve(2), so SECBIT_KEEP_CAPS is not + * strictly needed. + */ + bool require_keep_caps = !j->flags.set_ambient_caps; + if (lock_securebits(j->securebits_skip_mask, + require_keep_caps) < 0) { + pdie("locking securebits failed"); + } + } + + if (j->flags.no_new_privs) { + /* + * If we're setting no_new_privs, we can drop privileges + * before setting seccomp filter. This way filter policies + * don't need to allow privilege-dropping syscalls. + */ + drop_ugid(j); + drop_caps(j, last_valid_cap); + + // Landlock is applied as late as possible. If no_new_privs is + // set, then it can be applied after dropping caps. + apply_landlock_restrictions(j); + set_seccomp_filter(j); + } else { + apply_landlock_restrictions(j); + + /* + * If we're not setting no_new_privs, + * we need to set seccomp filter *before* dropping privileges. + * WARNING: this means that filter policies *must* allow + * setgroups()/setresgid()/setresuid() for dropping root and + * capget()/capset()/prctl() for dropping caps. + */ + set_seccomp_filter(j); + drop_ugid(j); + drop_caps(j, last_valid_cap); + } + + /* + * Select the specified alternate syscall table. The table must not + * block prctl(2) if we're using seccomp as well. + */ + if (j->flags.alt_syscall) { + if (prctl(PR_ALT_SYSCALL, 1, j->alt_syscall_table)) + pdie("prctl(PR_ALT_SYSCALL) failed"); + } + + /* + * seccomp has to come last since it cuts off all the other + * privilege-dropping syscalls :) + */ + if (j->flags.seccomp && prctl(PR_SET_SECCOMP, 1)) { + if ((errno == EINVAL) && seccomp_can_softfail()) { + warn("seccomp not supported"); + return; + } + pdie("prctl(PR_SET_SECCOMP) failed"); + } +} + +/* TODO(wad): will visibility affect this variable? */ +static int init_exitstatus = 0; + +static void init_term(int sig attribute_unused) +{ + _exit(init_exitstatus); +} + +static void init(pid_t rootpid) +{ + pid_t pid; + int status; + /* So that we exit with the right status. */ + signal(SIGTERM, init_term); + /* TODO(wad): self jail with seccomp filters here. */ + while ((pid = wait(&status)) > 0) { + /* + * This loop will only end when either there are no processes + * left inside our pid namespace or we get a signal. + */ + if (pid == rootpid) + init_exitstatus = status; + } + if (!WIFEXITED(init_exitstatus)) + _exit(MINIJAIL_ERR_INIT); + _exit(WEXITSTATUS(init_exitstatus)); +} + +int API minijail_from_fd(int fd, struct minijail *j) +{ + size_t sz = 0; + size_t bytes = read(fd, &sz, sizeof(sz)); + attribute_cleanup_str char *buf = NULL; + int r; + if (sizeof(sz) != bytes) + return -EINVAL; + if (sz > USHRT_MAX) /* arbitrary check */ + return -E2BIG; + buf = malloc(sz); + if (!buf) + return -ENOMEM; + bytes = read(fd, buf, sz); + if (bytes != sz) + return -EINVAL; + r = minijail_unmarshal(j, buf, sz); + return r; +} + +int API minijail_to_fd(struct minijail *j, int fd) +{ + size_t sz = minijail_size(j); + if (!sz) + return -EINVAL; + + attribute_cleanup_str char *buf = malloc(sz); + if (!buf) + return -ENOMEM; + + int err = minijail_marshal(j, buf, sz); + if (err) + return err; + + /* Sends [size][minijail]. */ + err = write_exactly(fd, &sz, sizeof(sz)); + if (err) + return err; + + return write_exactly(fd, buf, sz); +} + +int API minijail_copy_jail(const struct minijail *from, struct minijail *out) +{ + size_t sz = minijail_size(from); + if (!sz) + return -EINVAL; + + attribute_cleanup_str char *buf = malloc(sz); + if (!buf) + return -ENOMEM; + + int err = minijail_marshal(from, buf, sz); + if (err) + return err; + + return minijail_unmarshal(out, buf, sz); +} + +static int setup_preload(const struct minijail *j attribute_unused, + char ***child_env attribute_unused) +{ +#if defined(__ANDROID__) + /* Don't use LDPRELOAD on Android. */ + return 0; +#else + const char *preload_path = j->preload_path ?: PRELOADPATH; + char *newenv = NULL; + int ret = 0; + const char *oldenv = minijail_getenv(*child_env, kLdPreloadEnvVar); + + if (!oldenv) + oldenv = ""; + + /* Only insert a separating space if we have something to separate... */ + if (asprintf(&newenv, "%s%s%s", oldenv, oldenv[0] != '\0' ? " " : "", + preload_path) < 0) { + return -1; + } + + ret = minijail_setenv(child_env, kLdPreloadEnvVar, newenv, 1); + free(newenv); + return ret; +#endif +} + +/* + * This is for logging purposes and does not change the enforced seccomp + * filter. + */ +static int setup_seccomp_policy_path(const struct minijail *j, + char ***child_env) +{ + return minijail_setenv(child_env, kSeccompPolicyPathEnvVar, + j->seccomp_policy_path ? j->seccomp_policy_path + : "NO-LABEL", + 1 /* overwrite */); +} + +static int setup_pipe(char ***child_env, int fds[2]) +{ + int r = pipe(fds); + char fd_buf[11]; + if (r) + return r; + r = snprintf(fd_buf, sizeof(fd_buf), "%d", fds[0]); + if (r <= 0) + return -EINVAL; + return minijail_setenv(child_env, kFdEnvVar, fd_buf, 1); +} + +static int close_open_fds(int *inheritable_fds, size_t size) +{ + const char *kFdPath = "/proc/self/fd"; + + DIR *d = opendir(kFdPath); + struct dirent *dir_entry; + + if (d == NULL) + return -1; + int dir_fd = dirfd(d); + while ((dir_entry = readdir(d)) != NULL) { + size_t i; + char *end; + bool should_close = true; + const int fd = strtol(dir_entry->d_name, &end, 10); + + if ((*end) != '\0') { + continue; + } + /* + * We might have set up some pipes that we want to share with + * the parent process, and should not be closed. + */ + for (i = 0; i < size; ++i) { + if (fd == inheritable_fds[i]) { + should_close = false; + break; + } + } + /* Also avoid closing the directory fd. */ + if (should_close && fd != dir_fd) + close(fd); + } + closedir(d); + return 0; +} + +/* Return true if the specified file descriptor is already open. */ +static int fd_is_open(int fd) +{ + return fcntl(fd, F_GETFD) != -1 || errno != EBADF; +} + +static_assert(FD_SETSIZE >= MAX_PRESERVED_FDS * 2 - 1, + "If true, ensure_no_fd_conflict will always find an unused fd."); + +/* If parent_fd will be used by a child fd, move it to an unused fd. */ +static int ensure_no_fd_conflict(const fd_set *child_fds, int child_fd, + int *parent_fd) +{ + if (!FD_ISSET(*parent_fd, child_fds)) { + return 0; + } + + /* + * If no other parent_fd matches the child_fd then use it instead of a + * temporary. + */ + int fd = child_fd; + if (fd == -1 || fd_is_open(fd)) { + fd = FD_SETSIZE - 1; + while (FD_ISSET(fd, child_fds) || fd_is_open(fd)) { + --fd; + if (fd < 0) { + die("failed to find an unused fd"); + } + } + } + + int ret = dup2(*parent_fd, fd); + /* + * warn() opens a file descriptor so it needs to happen after dup2 to + * avoid unintended side effects. This can be avoided by reordering the + * mapping requests so that the source fds with overlap are mapped + * first (unless there are cycles). + */ + warn("mapped fd overlap: moving %d to %d", *parent_fd, fd); + if (ret == -1) { + return -1; + } + + *parent_fd = fd; + return 0; +} + +/* + * Populate child_fds_out with the set of file descriptors that will be replaced + * by redirect_fds(). + * + * NOTE: This creates temporaries for parent file descriptors that would + * otherwise be overwritten during redirect_fds(). + */ +static int get_child_fds(struct minijail *j, fd_set *child_fds_out) +{ + /* Relocate parent_fds that would be replaced by a child_fd. */ + for (size_t i = 0; i < j->preserved_fd_count; i++) { + int child_fd = j->preserved_fds[i].child_fd; + if (FD_ISSET(child_fd, child_fds_out)) { + die("fd %d is mapped more than once", child_fd); + } + + int *parent_fd = &j->preserved_fds[i].parent_fd; + if (ensure_no_fd_conflict(child_fds_out, child_fd, parent_fd) == + -1) { + return -1; + } + + FD_SET(child_fd, child_fds_out); + } + return 0; +} + +/* + * Structure holding resources and state created when running a minijail. + */ +struct minijail_run_state { + pid_t child_pid; + int pipe_fds[2]; + int stdin_fds[2]; + int stdout_fds[2]; + int stderr_fds[2]; + int child_sync_pipe_fds[2]; + char **child_env; +}; + +/* + * Move pipe_fds if they conflict with a child_fd. + */ +static int avoid_pipe_conflicts(struct minijail_run_state *state, + fd_set *child_fds_out) +{ + int *pipe_fds[] = { + state->pipe_fds, state->child_sync_pipe_fds, state->stdin_fds, + state->stdout_fds, state->stderr_fds, + }; + for (size_t i = 0; i < ARRAY_SIZE(pipe_fds); ++i) { + if (pipe_fds[i][0] != -1 && + ensure_no_fd_conflict(child_fds_out, -1, &pipe_fds[i][0]) == + -1) { + return -1; + } + if (pipe_fds[i][1] != -1 && + ensure_no_fd_conflict(child_fds_out, -1, &pipe_fds[i][1]) == + -1) { + return -1; + } + } + return 0; +} + +/* + * Redirect j->preserved_fds from the parent_fd to the child_fd. + * + * NOTE: This will clear FD_CLOEXEC since otherwise the child_fd would not be + * inherited after the exec call. + */ +static int redirect_fds(struct minijail *j, fd_set *child_fds) +{ + for (size_t i = 0; i < j->preserved_fd_count; i++) { + if (j->preserved_fds[i].parent_fd == + j->preserved_fds[i].child_fd) { + // Clear CLOEXEC if it is set so the FD will be + // inherited by the child. + int flags = + fcntl(j->preserved_fds[i].child_fd, F_GETFD); + if (flags == -1 || (flags & FD_CLOEXEC) == 0) { + continue; + } + + // Currently FD_CLOEXEC is cleared without being + // restored. It may make sense to track when this + // happens and restore FD_CLOEXEC in the child process. + flags &= ~FD_CLOEXEC; + if (fcntl(j->preserved_fds[i].child_fd, F_SETFD, + flags) == -1) { + pwarn("failed to clear CLOEXEC for %d", + j->preserved_fds[i].parent_fd); + } + continue; + } + if (dup2(j->preserved_fds[i].parent_fd, + j->preserved_fds[i].child_fd) == -1) { + return -1; + } + } + + /* + * After all fds have been duped, we are now free to close all parent + * fds that are *not* child fds. + */ + for (size_t i = 0; i < j->preserved_fd_count; i++) { + int parent_fd = j->preserved_fds[i].parent_fd; + if (!FD_ISSET(parent_fd, child_fds)) { + close(parent_fd); + } + } + return 0; +} + +static void minijail_free_run_state(struct minijail_run_state *state) +{ + state->child_pid = -1; + + int *fd_pairs[] = {state->pipe_fds, state->stdin_fds, state->stdout_fds, + state->stderr_fds, state->child_sync_pipe_fds}; + for (size_t i = 0; i < ARRAY_SIZE(fd_pairs); ++i) { + close_and_reset(&fd_pairs[i][0]); + close_and_reset(&fd_pairs[i][1]); + } + + minijail_free_env(state->child_env); + state->child_env = NULL; +} + +/* Set up stdin/stdout/stderr file descriptors in the child. */ +static void setup_child_std_fds(struct minijail *j, + struct minijail_run_state *state) +{ + struct { + const char *name; + int from; + int to; + } fd_map[] = { + {"stdin", state->stdin_fds[0], STDIN_FILENO}, + {"stdout", state->stdout_fds[1], STDOUT_FILENO}, + {"stderr", state->stderr_fds[1], STDERR_FILENO}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(fd_map); ++i) { + if (fd_map[i].from == -1 || fd_map[i].from == fd_map[i].to) + continue; + if (dup2(fd_map[i].from, fd_map[i].to) == -1) + die("failed to set up %s pipe", fd_map[i].name); + } + + /* Close temporary pipe file descriptors. */ + int *std_pipes[] = {state->stdin_fds, state->stdout_fds, + state->stderr_fds}; + for (size_t i = 0; i < ARRAY_SIZE(std_pipes); ++i) { + close_and_reset(&std_pipes[i][0]); + close_and_reset(&std_pipes[i][1]); + } + + /* + * If any of stdin, stdout, or stderr are TTYs, or setsid flag is + * set, create a new session. This prevents the jailed process from + * using the TIOCSTI ioctl to push characters into the parent process + * terminal's input buffer, therefore escaping the jail. + * + * Since it has just forked, the child will not be a process group + * leader, and this call to setsid() should always succeed. + */ + if (j->flags.setsid || isatty(STDIN_FILENO) || isatty(STDOUT_FILENO) || + isatty(STDERR_FILENO)) { + if (setsid() < 0) { + pdie("setsid() failed"); + } + + if (isatty(STDIN_FILENO)) { + if (ioctl(STDIN_FILENO, TIOCSCTTY, 0) != 0) { + pwarn("failed to set controlling terminal"); + } + } + } +} + +/* + * Structure that specifies how to start a minijail. + * + * filename - The program to exec in the child. Should be NULL if elf_fd is set. + * elf_fd - A fd to be used with fexecve. Should be -1 if filename is set. + * NOTE: either filename or elf_fd is required if |exec_in_child| = 1. + * argv - Arguments for the child program. Required if |exec_in_child| = 1. + * envp - Environment for the child program. Available if |exec_in_child| = 1. + * use_preload - If true use LD_PRELOAD. + * exec_in_child - If true, run |filename|. Otherwise, the child will return to + * the caller. + * pstdin_fd - Filled with stdin pipe if non-NULL. + * pstdout_fd - Filled with stdout pipe if non-NULL. + * pstderr_fd - Filled with stderr pipe if non-NULL. + * pchild_pid - Filled with the pid of the child process if non-NULL. + */ +struct minijail_run_config { + const char *filename; + int elf_fd; + char *const *argv; + char *const *envp; + int use_preload; + int exec_in_child; + int *pstdin_fd; + int *pstdout_fd; + int *pstderr_fd; + pid_t *pchild_pid; +}; + +static int +minijail_run_config_internal(struct minijail *j, + const struct minijail_run_config *config); + +int API minijail_run(struct minijail *j, const char *filename, + char *const argv[]) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = true, + .exec_in_child = true, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_env(struct minijail *j, const char *filename, + char *const argv[], char *const envp[]) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = envp, + .use_preload = true, + .exec_in_child = true, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_pid(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = true, + .exec_in_child = true, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_pipe(struct minijail *j, const char *filename, + char *const argv[], int *pstdin_fd) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = true, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_pid_pipes(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, int *pstderr_fd) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = true, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + .pstdout_fd = pstdout_fd, + .pstderr_fd = pstderr_fd, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_env_pid_pipes(struct minijail *j, const char *filename, + char *const argv[], char *const envp[], + pid_t *pchild_pid, int *pstdin_fd, + int *pstdout_fd, int *pstderr_fd) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = envp, + .use_preload = true, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + .pstdout_fd = pstdout_fd, + .pstderr_fd = pstderr_fd, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_fd_env_pid_pipes(struct minijail *j, int elf_fd, + char *const argv[], char *const envp[], + pid_t *pchild_pid, int *pstdin_fd, + int *pstdout_fd, int *pstderr_fd) +{ + struct minijail_run_config config = { + .filename = NULL, + .elf_fd = elf_fd, + .argv = argv, + .envp = envp, + .use_preload = true, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + .pstdout_fd = pstdout_fd, + .pstderr_fd = pstderr_fd, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_no_preload(struct minijail *j, const char *filename, + char *const argv[]) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = false, + .exec_in_child = true, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_pid_pipes_no_preload(struct minijail *j, + const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, + int *pstderr_fd) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = NULL, + .use_preload = false, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + .pstdout_fd = pstdout_fd, + .pstderr_fd = pstderr_fd, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +int API minijail_run_env_pid_pipes_no_preload(struct minijail *j, + const char *filename, + char *const argv[], + char *const envp[], + pid_t *pchild_pid, int *pstdin_fd, + int *pstdout_fd, int *pstderr_fd) +{ + struct minijail_run_config config = { + .filename = filename, + .elf_fd = -1, + .argv = argv, + .envp = envp, + .use_preload = false, + .exec_in_child = true, + .pstdin_fd = pstdin_fd, + .pstdout_fd = pstdout_fd, + .pstderr_fd = pstderr_fd, + .pchild_pid = pchild_pid, + }; + return minijail_run_config_internal(j, &config); +} + +pid_t API minijail_fork(struct minijail *j) +{ + struct minijail_run_config config = { + .elf_fd = -1, + }; + return minijail_run_config_internal(j, &config); +} + +static int minijail_run_internal(struct minijail *j, + const struct minijail_run_config *config, + struct minijail_run_state *state_out) +{ + int sync_child = 0; + int ret; + /* We need to remember this across the minijail_preexec() call. */ + int pid_namespace = j->flags.pids; + /* + * Create an init process if we are entering a pid namespace, unless the + * user has explicitly opted out by calling minijail_run_as_init(). + */ + int do_init = j->flags.do_init && !j->flags.run_as_init; + int use_preload = config->use_preload; + + if (config->filename != NULL && config->elf_fd != -1) { + die("filename and elf_fd cannot be set at the same time"); + } + + /* + * Only copy the environment if we need to modify it. If this is done + * unconditionally, it triggers odd behavior in the ARC container. + */ + if (use_preload || j->seccomp_policy_path) { + state_out->child_env = + minijail_copy_env(config->envp ? config->envp : environ); + if (!state_out->child_env) + return ENOMEM; + } + + if (j->seccomp_policy_path && + setup_seccomp_policy_path(j, &state_out->child_env)) + return -EFAULT; + + if (use_preload) { + if (j->hooks_head != NULL) + die("Minijail hooks are not supported with LD_PRELOAD"); + if (!config->exec_in_child) + die("minijail_fork is not supported with LD_PRELOAD"); + + /* + * Before we fork(2) and execve(2) the child process, we need + * to open a pipe(2) to send the minijail configuration over. + */ + if (setup_preload(j, &state_out->child_env) || + setup_pipe(&state_out->child_env, state_out->pipe_fds)) + return -EFAULT; + } + + if (!use_preload) { + if (j->flags.use_caps && j->caps != 0 && + !j->flags.set_ambient_caps) { + die("non-empty, non-ambient capabilities are not " + "supported without LD_PRELOAD"); + } + } + + /* Create pipes for stdin/stdout/stderr as requested by caller. */ + struct { + bool requested; + int *pipe_fds; + } pipe_fd_req[] = { + {config->pstdin_fd != NULL, state_out->stdin_fds}, + {config->pstdout_fd != NULL, state_out->stdout_fds}, + {config->pstderr_fd != NULL, state_out->stderr_fds}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(pipe_fd_req); ++i) { + if (pipe_fd_req[i].requested && + pipe(pipe_fd_req[i].pipe_fds) == -1) + return EFAULT; + } + + /* + * If the parent process needs to configure the child's runtime + * environment after forking, create a pipe(2) to block the child until + * configuration is done. + */ + if (j->flags.forward_signals || j->flags.pid_file || j->flags.cgroups || + j->rlimit_count || j->flags.userns) { + sync_child = 1; + if (pipe(state_out->child_sync_pipe_fds)) + return -EFAULT; + } + + /* + * Use sys_clone() if and only if we're creating a pid namespace. + * + * tl;dr: WARNING: do not mix pid namespaces and multithreading. + * + * In multithreaded programs, there are a bunch of locks inside libc, + * some of which may be held by other threads at the time that we call + * minijail_run_pid(). If we call fork(), glibc does its level best to + * ensure that we hold all of these locks before it calls clone() + * internally and drop them after clone() returns, but when we call + * sys_clone(2) directly, all that gets bypassed and we end up with a + * child address space where some of libc's important locks are held by + * other threads (which did not get cloned, and hence will never release + * those locks). This is okay so long as we call exec() immediately + * after, but a bunch of seemingly-innocent libc functions like setenv() + * take locks. + * + * Hence, only call sys_clone() if we need to, in order to get at pid + * namespacing. If we follow this path, the child's address space might + * have broken locks; you may only call functions that do not acquire + * any locks. + * + * Unfortunately, fork() acquires every lock it can get its hands on, as + * previously detailed, so this function is highly likely to deadlock + * later on (see "deadlock here") if we're multithreaded. + * + * We might hack around this by having the clone()d child (init of the + * pid namespace) return directly, rather than leaving the clone()d + * process hanging around to be init for the new namespace (and having + * its fork()ed child return in turn), but that process would be + * crippled with its libc locks potentially broken. We might try + * fork()ing in the parent before we clone() to ensure that we own all + * the locks, but then we have to have the forked child hanging around + * consuming resources (and possibly having file descriptors / shared + * memory regions / etc attached). We'd need to keep the child around to + * avoid having its children get reparented to init. + * + * TODO(ellyjones): figure out if the "forked child hanging around" + * problem is fixable or not. It would be nice if we worked in this + * case. + */ + pid_t child_pid; + if (pid_namespace) { + unsigned long clone_flags = CLONE_NEWPID | SIGCHLD; + if (j->flags.userns) + clone_flags |= CLONE_NEWUSER; + + child_pid = syscall(SYS_clone, clone_flags, NULL, 0L, 0L, 0L); + + if (child_pid < 0) { + if (errno == EPERM) + pdie("clone(CLONE_NEWPID | ...) failed with " + "EPERM; is this process missing " + "CAP_SYS_ADMIN?"); + pdie("clone(CLONE_NEWPID | ...) failed"); + } + } else { + child_pid = fork(); + + if (child_pid < 0) + pdie("fork failed"); + } + + state_out->child_pid = child_pid; + if (child_pid) { + j->initpid = child_pid; + + if (j->flags.forward_signals) { + forward_pid = child_pid; + install_signal_handlers(); + } + + if (j->flags.pid_file) + write_pid_file_or_die(j); + + if (j->flags.cgroups) + add_to_cgroups_or_die(j); + + if (j->rlimit_count) + set_rlimits_or_die(j); + + if (j->flags.userns) + write_ugid_maps_or_die(j); + + if (j->flags.enter_vfs) + close(j->mountns_fd); + + if (j->flags.enter_net) + close(j->netns_fd); + + if (sync_child) + parent_setup_complete(state_out->child_sync_pipe_fds); + + if (use_preload) { + /* + * Add SIGPIPE to the signal mask to avoid getting + * killed if the child process finishes or closes its + * end of the pipe prematurely. + * + * TODO(crbug.com/1022170): Use pthread_sigmask instead + * of sigprocmask if Minijail is used in multithreaded + * programs. + */ + sigset_t to_block, to_restore; + if (sigemptyset(&to_block) < 0) + pdie("sigemptyset failed"); + if (sigaddset(&to_block, SIGPIPE) < 0) + pdie("sigaddset failed"); + if (sigprocmask(SIG_BLOCK, &to_block, &to_restore) < 0) + pdie("sigprocmask failed"); + + /* Send marshalled minijail. */ + close_and_reset(&state_out->pipe_fds[0]); + ret = minijail_to_fd(j, state_out->pipe_fds[1]); + close_and_reset(&state_out->pipe_fds[1]); + + /* Accept any pending SIGPIPE. */ + while (true) { + const struct timespec zero_time = {0, 0}; + const int sig = + sigtimedwait(&to_block, NULL, &zero_time); + if (sig < 0) { + if (errno != EINTR) + break; + } else { + if (sig != SIGPIPE) + die("unexpected signal %d", + sig); + } + } + + /* Restore the signal mask to its original state. */ + if (sigprocmask(SIG_SETMASK, &to_restore, NULL) < 0) + pdie("sigprocmask failed"); + + if (ret) { + warn("failed to send marshalled minijail: %s", + strerror(-ret)); + kill(j->initpid, SIGKILL); + } + } + + return 0; + } + + /* Child process. */ + if (j->flags.reset_signal_mask) { + sigset_t signal_mask; + if (sigemptyset(&signal_mask) != 0) + pdie("sigemptyset failed"); + if (sigprocmask(SIG_SETMASK, &signal_mask, NULL) != 0) + pdie("sigprocmask failed"); + } + + if (j->flags.reset_signal_handlers) { + int signum; + for (signum = 0; signum <= SIGRTMAX; signum++) { + /* + * Ignore EINVAL since some signal numbers in the range + * might not be valid. + */ + if (signal(signum, SIG_DFL) == SIG_ERR && + errno != EINVAL) { + pdie("failed to reset signal %d disposition", + signum); + } + } + } + + if (j->flags.close_open_fds) { + const size_t kMaxInheritableFdsSize = 11 + MAX_PRESERVED_FDS; + int inheritable_fds[kMaxInheritableFdsSize]; + size_t size = 0; + + int *pipe_fds[] = { + state_out->pipe_fds, state_out->child_sync_pipe_fds, + state_out->stdin_fds, state_out->stdout_fds, + state_out->stderr_fds, + }; + + for (size_t i = 0; i < ARRAY_SIZE(pipe_fds); ++i) { + if (pipe_fds[i][0] != -1) { + inheritable_fds[size++] = pipe_fds[i][0]; + } + if (pipe_fds[i][1] != -1) { + inheritable_fds[size++] = pipe_fds[i][1]; + } + } + + /* + * Preserve namespace file descriptors over the close_open_fds() + * call. These are closed in minijail_enter() so they won't leak + * into the child process. + */ + if (j->flags.enter_vfs) + minijail_preserve_fd(j, j->mountns_fd, j->mountns_fd); + if (j->flags.enter_net) + minijail_preserve_fd(j, j->netns_fd, j->netns_fd); + + for (size_t i = 0; i < j->preserved_fd_count; i++) { + /* + * Preserve all parent_fds. They will be dup2(2)-ed in + * the child later. + */ + inheritable_fds[size++] = j->preserved_fds[i].parent_fd; + } + + if (config->elf_fd > -1) { + inheritable_fds[size++] = config->elf_fd; + } + + if (close_open_fds(inheritable_fds, size) < 0) + die("failed to close open file descriptors"); + } + + /* The set of fds will be replaced. */ + fd_set child_fds; + FD_ZERO(&child_fds); + if (get_child_fds(j, &child_fds)) + die("failed to set up fd redirections"); + + if (avoid_pipe_conflicts(state_out, &child_fds)) + die("failed to redirect conflicting pipes"); + + /* The elf_fd needs to be mutable so use a stack copy from now on. */ + int elf_fd = config->elf_fd; + if (elf_fd != -1 && ensure_no_fd_conflict(&child_fds, -1, &elf_fd)) + die("failed to redirect elf_fd"); + + if (redirect_fds(j, &child_fds)) + die("failed to set up fd redirections"); + + if (sync_child) + wait_for_parent_setup(state_out->child_sync_pipe_fds); + + if (j->flags.userns) + enter_user_namespace(j); + + setup_child_std_fds(j, state_out); + + /* If running an init program, let it decide when/how to mount /proc. */ + if (pid_namespace && !do_init) + j->flags.remount_proc_ro = 0; + + if (use_preload) { + /* Strip out flags that cannot be inherited across execve(2). */ + minijail_preexec(j); + } else { + /* + * If not using LD_PRELOAD, do all jailing before execve(2). + * Note that PID namespaces can only be entered on fork(2), + * so that flag is still cleared. + */ + j->flags.pids = 0; + } + + /* + * Jail this process. + * If forking, return. + * If not, execve(2) the target. + */ + minijail_enter(j); + + if (config->exec_in_child && pid_namespace && do_init) { + /* + * pid namespace: this process will become init inside the new + * namespace. We don't want all programs we might exec to have + * to know how to be init. Normally (do_init == 1) we fork off + * a child to actually run the program. If |do_init == 0|, we + * let the program keep pid 1 and be init. + * + * If we're multithreaded, we'll probably deadlock here. See + * WARNING above. + */ + child_pid = fork(); + if (child_pid < 0) { + _exit(child_pid); + } else if (child_pid > 0) { + minijail_free_run_state(state_out); + + /* + * Best effort. Don't bother checking the return value. + */ + prctl(PR_SET_NAME, "minijail-init"); + init(child_pid); /* Never returns. */ + } + state_out->child_pid = child_pid; + } + + run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_EXECVE); + + if (!config->exec_in_child) + return 0; + + /* + * We're going to execve(), so make sure any remaining resources are + * freed. Exceptions are: + * 1. The child environment. No need to worry about freeing it since + * execve reinitializes the heap anyways. + * 2. The read side of the LD_PRELOAD pipe, which we need to hand down + * into the target in which the preloaded code will read from it and + * then close it. + */ + state_out->pipe_fds[0] = -1; + char *const *child_env = state_out->child_env; + state_out->child_env = NULL; + minijail_free_run_state(state_out); + + /* + * If we aren't pid-namespaced, or the jailed program asked to be init: + * calling process + * -> execve()-ing process + * If we are: + * calling process + * -> init()-ing process + * -> execve()-ing process + */ + if (!child_env) + child_env = config->envp ? config->envp : environ; + if (elf_fd > -1) { + fexecve(elf_fd, config->argv, child_env); + pwarn("fexecve(%d) failed", config->elf_fd); + } else { + execve(config->filename, config->argv, child_env); + pwarn("execve(%s) failed", config->filename); + } + + ret = (errno == ENOENT ? MINIJAIL_ERR_NO_COMMAND + : MINIJAIL_ERR_NO_ACCESS); + _exit(ret); +} + +static int +minijail_run_config_internal(struct minijail *j, + const struct minijail_run_config *config) +{ + struct minijail_run_state state = { + .child_pid = -1, + .pipe_fds = {-1, -1}, + .stdin_fds = {-1, -1}, + .stdout_fds = {-1, -1}, + .stderr_fds = {-1, -1}, + .child_sync_pipe_fds = {-1, -1}, + .child_env = NULL, + }; + int ret = minijail_run_internal(j, config, &state); + + if (ret == 0) { + if (config->pchild_pid) + *config->pchild_pid = state.child_pid; + + /* Grab stdin/stdout/stderr descriptors requested by caller. */ + struct { + int *pfd; + int *psrc; + } fd_map[] = { + {config->pstdin_fd, &state.stdin_fds[1]}, + {config->pstdout_fd, &state.stdout_fds[0]}, + {config->pstderr_fd, &state.stderr_fds[0]}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(fd_map); ++i) { + if (fd_map[i].pfd) { + *fd_map[i].pfd = *fd_map[i].psrc; + *fd_map[i].psrc = -1; + } + } + + if (!config->exec_in_child) + ret = state.child_pid; + } + + minijail_free_run_state(&state); + + return ret; +} + +static int minijail_wait_internal(struct minijail *j, int expected_signal) +{ + if (j->initpid <= 0) + return -ECHILD; + + int st; + while (true) { + const int ret = waitpid(j->initpid, &st, 0); + if (ret >= 0) + break; + if (errno != EINTR) + return -errno; + } + + if (!WIFEXITED(st)) { + int error_status = st; + if (!WIFSIGNALED(st)) { + return error_status; + } + + int signum = WTERMSIG(st); + /* + * We return MINIJAIL_ERR_JAIL if the process received + * SIGSYS, which happens when a syscall is blocked by + * seccomp filters. + * If not, we do what bash(1) does: + * $? = 128 + signum + */ + if (signum == SIGSYS) { + warn("child process %d had a policy violation (%s)", + j->initpid, + j->seccomp_policy_path ? j->seccomp_policy_path + : "NO-LABEL"); + error_status = MINIJAIL_ERR_JAIL; + } else { + if (signum != expected_signal) { + warn("child process %d received signal %d", + j->initpid, signum); + } + error_status = MINIJAIL_ERR_SIG_BASE + signum; + } + return error_status; + } + + int exit_status = WEXITSTATUS(st); + if (exit_status != 0) + info("child process %d exited with status %d", j->initpid, + exit_status); + + return exit_status; +} + +int API minijail_kill(struct minijail *j) +{ + if (j->initpid <= 0) + return -ECHILD; + + if (kill(j->initpid, SIGTERM)) + return -errno; + + return minijail_wait_internal(j, SIGTERM); +} + +int API minijail_wait(struct minijail *j) +{ + return minijail_wait_internal(j, 0); +} + +void API minijail_destroy(struct minijail *j) +{ + size_t i; + + if (j->filter_prog) { + free(j->filter_prog->filter); + free(j->filter_prog); + } + free_mounts_list(j); + free_remounts_list(j); + while (j->hooks_head) { + struct hook *c = j->hooks_head; + j->hooks_head = c->next; + free(c); + } + j->hooks_tail = NULL; + while (j->fs_rules_head) { + struct fs_rule *r = j->fs_rules_head; + j->fs_rules_head = r->next; + free(r); + } + j->fs_rules_tail = NULL; + if (j->user) + free(j->user); + if (j->suppl_gid_list) + free(j->suppl_gid_list); + if (j->chrootdir) + free(j->chrootdir); + if (j->pid_file_path) + free(j->pid_file_path); + if (j->uidmap) + free(j->uidmap); + if (j->gidmap) + free(j->gidmap); + if (j->hostname) + free(j->hostname); + if (j->preload_path) + free(j->preload_path); + if (j->alt_syscall_table) + free(j->alt_syscall_table); + for (i = 0; i < j->cgroup_count; ++i) + free(j->cgroups[i]); + if (j->seccomp_policy_path) + free(j->seccomp_policy_path); + free(j); +} + +void API minijail_log_to_fd(int fd, int min_priority) +{ + init_logging(LOG_TO_FD, fd, min_priority); +} diff --git a/aosp/external/selinux/libselinux/include/selinux/selinux.h b/aosp/external/selinux/libselinux/include/selinux/selinux.h new file mode 100644 index 000000000..452ed70d9 --- /dev/null +++ b/aosp/external/selinux/libselinux/include/selinux/selinux.h @@ -0,0 +1,754 @@ +#ifndef _SELINUX_H_ +#define _SELINUX_H_ + +#include +#include + +#ifndef kbox_hack +#define kbox_hack() if (1) return +#endif +#ifndef kbox_hack_p +#define kbox_hack_p(p) if (1) return p +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Return 1 if we are running on a SELinux kernel, or 0 otherwise. */ +extern int is_selinux_enabled(void); +/* Return 1 if we are running on a SELinux MLS kernel, or 0 otherwise. */ +extern int is_selinux_mls_enabled(void); + +/* No longer used; here for compatibility with legacy callers. */ +typedef char *security_context_t +#ifdef __GNUC__ +__attribute__ ((deprecated)) +#endif +; + +/* Free the memory allocated for a context by any of the below get* calls. */ +extern void freecon(char * con); + +/* Free the memory allocated for a context array by security_compute_user. */ +extern void freeconary(char ** con); + +/* Wrappers for the /proc/pid/attr API. */ + +/* Get current context, and set *con to refer to it. + Caller must free via freecon. */ +extern int getcon(char ** con); +extern int getcon_raw(char ** con); + +/* Set the current security context to con. + Note that use of this function requires that the entire application + be trusted to maintain any desired separation between the old and new + security contexts, unlike exec-based transitions performed via setexeccon. + When possible, decompose your application and use setexeccon()+execve() + instead. Note that the application may lose access to its open descriptors + as a result of a setcon() unless policy allows it to use descriptors opened + by the old context. */ +extern int setcon(const char * con); +extern int setcon_raw(const char * con); + +/* Get context of process identified by pid, and + set *con to refer to it. Caller must free via freecon. */ +extern int getpidcon(pid_t pid, char ** con); +extern int getpidcon_raw(pid_t pid, char ** con); + +/* Get previous context (prior to last exec), and set *con to refer to it. + Caller must free via freecon. */ +extern int getprevcon(char ** con); +extern int getprevcon_raw(char ** con); + +/* Get previous context (prior to last exec) of process identified by pid, and + set *con to refer to it. Caller must free via freecon. */ +extern int getpidprevcon(pid_t pid, char ** con); +extern int getpidprevcon_raw(pid_t pid, char ** con); + +/* Get exec context, and set *con to refer to it. + Sets *con to NULL if no exec context has been set, i.e. using default. + If non-NULL, caller must free via freecon. */ +extern int getexeccon(char ** con); +extern int getexeccon_raw(char ** con); + +/* Set exec security context for the next execve. + Call with NULL if you want to reset to the default. */ +extern int setexeccon(const char * con); +extern int setexeccon_raw(const char * con); + +/* Get fscreate context, and set *con to refer to it. + Sets *con to NULL if no fs create context has been set, i.e. using default. + If non-NULL, caller must free via freecon. */ +extern int getfscreatecon(char ** con); +extern int getfscreatecon_raw(char ** con); + +/* Set the fscreate security context for subsequent file creations. + Call with NULL if you want to reset to the default. */ +extern int setfscreatecon(const char * context); +extern int setfscreatecon_raw(const char * context); + +/* Get keycreate context, and set *con to refer to it. + Sets *con to NULL if no key create context has been set, i.e. using default. + If non-NULL, caller must free via freecon. */ +extern int getkeycreatecon(char ** con); +extern int getkeycreatecon_raw(char ** con); + +/* Set the keycreate security context for subsequent key creations. + Call with NULL if you want to reset to the default. */ +extern int setkeycreatecon(const char * context); +extern int setkeycreatecon_raw(const char * context); + +/* Get sockcreate context, and set *con to refer to it. + Sets *con to NULL if no socket create context has been set, i.e. using default. + If non-NULL, caller must free via freecon. */ +extern int getsockcreatecon(char ** con); +extern int getsockcreatecon_raw(char ** con); + +/* Set the sockcreate security context for subsequent socket creations. + Call with NULL if you want to reset to the default. */ +extern int setsockcreatecon(const char * context); +extern int setsockcreatecon_raw(const char * context); + +/* Wrappers for the xattr API. */ + +/* Get file context, and set *con to refer to it. + Caller must free via freecon. */ +extern int getfilecon(const char *path, char ** con); +extern int getfilecon_raw(const char *path, char ** con); +extern int lgetfilecon(const char *path, char ** con); +extern int lgetfilecon_raw(const char *path, char ** con); +extern int fgetfilecon(int fd, char ** con); +extern int fgetfilecon_raw(int fd, char ** con); + +/* Set file context */ +extern int setfilecon(const char *path, const char * con); +extern int setfilecon_raw(const char *path, const char * con); +extern int lsetfilecon(const char *path, const char * con); +extern int lsetfilecon_raw(const char *path, const char * con); +extern int fsetfilecon(int fd, const char * con); +extern int fsetfilecon_raw(int fd, const char * con); + +/* Wrappers for the socket API */ + +/* Get context of peer socket, and set *con to refer to it. + Caller must free via freecon. */ +extern int getpeercon(int fd, char ** con); +extern int getpeercon_raw(int fd, char ** con); + +/* Wrappers for the selinuxfs (policy) API. */ + +typedef unsigned int access_vector_t; +typedef unsigned short security_class_t; + +struct av_decision { + access_vector_t allowed; + access_vector_t decided; + access_vector_t auditallow; + access_vector_t auditdeny; + unsigned int seqno; + unsigned int flags; +}; + +/* Definitions of av_decision.flags */ +#define SELINUX_AVD_FLAGS_PERMISSIVE 0x0001 + +/* Structure for passing options, used by AVC and label subsystems */ +struct selinux_opt { + int type; + const char *value; +}; + +/* Callback facilities */ +union selinux_callback { + /* log the printf-style format and arguments, + with the type code indicating the type of message */ + int +#ifdef __GNUC__ +__attribute__ ((format(printf, 2, 3))) +#endif + (*func_log) (int type, const char *fmt, ...); + /* store a string representation of auditdata (corresponding + to the given security class) into msgbuf. */ + int (*func_audit) (void *auditdata, security_class_t cls, + char *msgbuf, size_t msgbufsize); + /* validate the supplied context, modifying if necessary */ + int (*func_validate) (char **ctx); + /* netlink callback for setenforce message */ + int (*func_setenforce) (int enforcing); + /* netlink callback for policyload message */ + int (*func_policyload) (int seqno); +}; + +#define SELINUX_CB_LOG 0 +#define SELINUX_CB_AUDIT 1 +#define SELINUX_CB_VALIDATE 2 +#define SELINUX_CB_SETENFORCE 3 +#define SELINUX_CB_POLICYLOAD 4 + +extern union selinux_callback selinux_get_callback(int type); +extern void selinux_set_callback(int type, union selinux_callback cb); + + /* Logging type codes, passed to the logging callback */ +#define SELINUX_ERROR 0 +#define SELINUX_WARNING 1 +#define SELINUX_INFO 2 +#define SELINUX_AVC 3 +#define SELINUX_POLICYLOAD 4 +#define SELINUX_SETENFORCE 5 +#define SELINUX_TRANS_DIR "/var/run/setrans" + +/* Compute an access decision. */ +extern int security_compute_av(const char * scon, + const char * tcon, + security_class_t tclass, + access_vector_t requested, + struct av_decision *avd); +extern int security_compute_av_raw(const char * scon, + const char * tcon, + security_class_t tclass, + access_vector_t requested, + struct av_decision *avd); + +extern int security_compute_av_flags(const char * scon, + const char * tcon, + security_class_t tclass, + access_vector_t requested, + struct av_decision *avd); +extern int security_compute_av_flags_raw(const char * scon, + const char * tcon, + security_class_t tclass, + access_vector_t requested, + struct av_decision *avd); + +/* Compute a labeling decision and set *newcon to refer to it. + Caller must free via freecon. */ +extern int security_compute_create(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); +extern int security_compute_create_raw(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); +extern int security_compute_create_name(const char * scon, + const char * tcon, + security_class_t tclass, + const char *objname, + char ** newcon); +extern int security_compute_create_name_raw(const char * scon, + const char * tcon, + security_class_t tclass, + const char *objname, + char ** newcon); + +/* Compute a relabeling decision and set *newcon to refer to it. + Caller must free via freecon. */ +extern int security_compute_relabel(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); +extern int security_compute_relabel_raw(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); + +/* Compute a polyinstantiation member decision and set *newcon to refer to it. + Caller must free via freecon. */ +extern int security_compute_member(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); +extern int security_compute_member_raw(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon); + +/* + * Compute the set of reachable user contexts and set *con to refer to + * the NULL-terminated array of contexts. Caller must free via freeconary. + * These interfaces are deprecated. Use get_ordered_context_list() or + * one of its variant interfaces instead. + */ +extern int security_compute_user(const char * scon, + const char *username, + char *** con); +extern int security_compute_user_raw(const char * scon, + const char *username, + char *** con); + +/* Validate a transition. This determines whether a transition from scon to newcon + using tcon as the target for object class tclass is valid in the loaded policy. + This checks against the mlsvalidatetrans and validatetrans constraints in the loaded policy. + Returns 0 if allowed and -1 if an error occurred with errno set */ +extern int security_validatetrans(const char *scon, + const char *tcon, + security_class_t tclass, + const char *newcon); +extern int security_validatetrans_raw(const char *scon, + const char *tcon, + security_class_t tclass, + const char *newcon); + +/* Load a policy configuration. */ +extern int security_load_policy(const void *data, size_t len); + +/* Get the context of an initial kernel security identifier by name. + Caller must free via freecon */ +extern int security_get_initial_context(const char *name, + char ** con); +extern int security_get_initial_context_raw(const char *name, + char ** con); + +/* + * Make a policy image and load it. + * This function provides a higher level interface for loading policy + * than security_load_policy, internally determining the right policy + * version, locating and opening the policy file, mapping it into memory, + * manipulating it as needed for current boolean settings and/or local + * definitions, and then calling security_load_policy to load it. + * + * 'preservebools' is no longer supported, set to 0. + */ +extern int selinux_mkload_policy(int preservebools); + +/* + * Perform the initial policy load. + * This function determines the desired enforcing mode, sets the + * the *enforce argument accordingly for the caller to use, sets the + * SELinux kernel enforcing status to match it, and loads the policy. + * It also internally handles the initial selinuxfs mount required to + * perform these actions. + * + * The function returns 0 if everything including the policy load succeeds. + * In this case, init is expected to re-exec itself in order to transition + * to the proper security context. + * Otherwise, the function returns -1, and init must check *enforce to + * determine how to proceed. If enforcing (*enforce > 0), then init should + * halt the system. Otherwise, init may proceed normally without a re-exec. + */ +extern int selinux_init_load_policy(int *enforce); + +/* Translate boolean strict to name value pair. */ +typedef struct { + char *name; + int value; +} SELboolean; +/* save a list of booleans in a single transaction. 'permanent' is no + * longer supported, set to 0. + */ +extern int security_set_boolean_list(size_t boolcnt, + SELboolean * boollist, int permanent); + +/* Load policy boolean settings. Deprecated as local policy booleans no + * longer supported. Will always return -1. + */ +extern int security_load_booleans(char *path) +#ifdef __GNUC__ +__attribute__ ((deprecated)) +#endif +; + +/* Check the validity of a security context. */ +extern int security_check_context(const char * con); +extern int security_check_context_raw(const char * con); + +/* Canonicalize a security context. */ +extern int security_canonicalize_context(const char * con, + char ** canoncon); +extern int security_canonicalize_context_raw(const char * con, + char ** canoncon); + +/* Get the enforce flag value. */ +extern int security_getenforce(void); + +/* Set the enforce flag value. */ +extern int security_setenforce(int value); + +/* Get the load-time behavior for undefined classes/permissions */ +extern int security_reject_unknown(void); + +/* Get the runtime behavior for undefined classes/permissions */ +extern int security_deny_unknown(void); + +/* Get the checkreqprot value */ +extern int security_get_checkreqprot(void); + +/* Disable SELinux at runtime (must be done prior to initial policy load). */ +extern int security_disable(void); + +/* Get the policy version number. */ +extern int security_policyvers(void); + +/* Get the boolean names */ +extern int security_get_boolean_names(char ***names, int *len); + +/* Get the pending value for the boolean */ +extern int security_get_boolean_pending(const char *name); + +/* Get the active value for the boolean */ +extern int security_get_boolean_active(const char *name); + +/* Set the pending value for the boolean */ +extern int security_set_boolean(const char *name, int value); + +/* Commit the pending values for the booleans */ +extern int security_commit_booleans(void); + +/* Userspace class mapping support */ +struct security_class_mapping { + const char *name; + const char *perms[sizeof(access_vector_t) * 8 + 1]; +}; + +/** + * selinux_set_mapping - Enable dynamic mapping between integer offsets and security class names + * @map: array of security_class_mapping structures + * + * The core avc_has_perm() API uses integers to represent security + * classes; previous to the introduction of this function, it was + * common for userspace object managers to be compiled using generated + * offsets for a particular policy. However, that strongly ties the build of the userspace components to a particular policy. + * + * By using this function to map between integer offsets and security + * class names, it's possible to replace a system policies that have + * at least the same set of security class names as used by the + * userspace object managers. + * + * To correctly use this function, you should override the generated + * security class defines from the system policy in a local header, + * starting at 1, and have one security_class_mapping structure entry + * per define. + */ +extern int selinux_set_mapping(struct security_class_mapping *map); + +/* Common helpers */ + +/* Convert between mode and security class values */ +extern security_class_t mode_to_security_class(mode_t mode); +/* Convert between security class values and string names */ +extern security_class_t string_to_security_class(const char *name); +extern const char *security_class_to_string(security_class_t cls); + +/* Convert between individual access vector permissions and string names */ +extern const char *security_av_perm_to_string(security_class_t tclass, + access_vector_t perm); +extern access_vector_t string_to_av_perm(security_class_t tclass, + const char *name); + +/* Returns an access vector in a string representation. User must free the + * returned string via free(). */ +extern int security_av_string(security_class_t tclass, + access_vector_t av, char **result); + +/* Display an access vector in a string representation. */ +extern void print_access_vector(security_class_t tclass, access_vector_t av); + +/* Flush the SELinux class cache, e.g. upon a policy reload. */ +extern void selinux_flush_class_cache(void); + +/* Set the function used by matchpathcon_init when displaying + errors about the file_contexts configuration. If not set, + then this defaults to fprintf(stderr, fmt, ...). */ +extern void set_matchpathcon_printf(void (*f) (const char *fmt, ...)); + +/* Set the function used by matchpathcon_init when checking the + validity of a context in the file contexts configuration. If not set, + then this defaults to a test based on security_check_context(). + The function is also responsible for reporting any such error, and + may include the 'path' and 'lineno' in such error messages. */ +extern void set_matchpathcon_invalidcon(int (*f) (const char *path, + unsigned lineno, + char *context)); + +/* Same as above, but also allows canonicalization of the context, + by changing *context to refer to the canonical form. If not set, + and invalidcon is also not set, then this defaults to calling + security_canonicalize_context(). */ +extern void set_matchpathcon_canoncon(int (*f) (const char *path, + unsigned lineno, + char **context)); + +/* Set flags controlling operation of matchpathcon_init or matchpathcon. */ +#define MATCHPATHCON_BASEONLY 1 /* Only process the base file_contexts file. */ +#define MATCHPATHCON_NOTRANS 2 /* Do not perform any context translation. */ +#define MATCHPATHCON_VALIDATE 4 /* Validate/canonicalize contexts at init time. */ +extern void set_matchpathcon_flags(unsigned int flags); + +/* Load the file contexts configuration specified by 'path' + into memory for use by subsequent matchpathcon calls. + If 'path' is NULL, then load the active file contexts configuration, + i.e. the path returned by selinux_file_context_path(). + Unless the MATCHPATHCON_BASEONLY flag has been set, this + function also checks for a 'path'.homedirs file and + a 'path'.local file and loads additional specifications + from them if present. */ +extern int matchpathcon_init(const char *path) +#ifdef __GNUC__ + __attribute__ ((deprecated("Use selabel_open with backend SELABEL_CTX_FILE"))) +#endif +; + +/* Same as matchpathcon_init, but only load entries with + regexes that have stems that are prefixes of 'prefix'. */ +extern int matchpathcon_init_prefix(const char *path, const char *prefix); + +/* Free the memory allocated by matchpathcon_init. */ +extern void matchpathcon_fini(void) +#ifdef __GNUC__ + __attribute__ ((deprecated("Use selabel_close"))) +#endif +; + +/* Resolve all of the symlinks and relative portions of a pathname, but NOT + * the final component (same a realpath() unless the final component is a + * symlink. Resolved path must be a path of size PATH_MAX + 1 */ +extern int realpath_not_final(const char *name, char *resolved_path); + +/* Match the specified pathname and mode against the file contexts + configuration and set *con to refer to the resulting context. + 'mode' can be 0 to disable mode matching. + Caller must free via freecon. + If matchpathcon_init has not already been called, then this function + will call it upon its first invocation with a NULL path. */ +extern int matchpathcon(const char *path, + mode_t mode, char ** con) +#ifdef __GNUC__ + __attribute__ ((deprecated("Use selabel_lookup instead"))) +#endif +; + +/* Same as above, but return a specification index for + later use in a matchpathcon_filespec_add() call - see below. */ +extern int matchpathcon_index(const char *path, + mode_t mode, char ** con); + +/* Maintain an association between an inode and a specification index, + and check whether a conflicting specification is already associated + with the same inode (e.g. due to multiple hard links). If so, then + use the latter of the two specifications based on their order in the + file contexts configuration. Return the used specification index. */ +extern int matchpathcon_filespec_add(ino_t ino, int specind, const char *file); + +/* Destroy any inode associations that have been added, e.g. to restart + for a new filesystem. */ +extern void matchpathcon_filespec_destroy(void); + +/* Display statistics on the hash table usage for the associations. */ +extern void matchpathcon_filespec_eval(void); + +/* Check to see whether any specifications had no matches and report them. + The 'str' is used as a prefix for any warning messages. */ +extern void matchpathcon_checkmatches(char *str); + +/* Match the specified media and against the media contexts + configuration and set *con to refer to the resulting context. + Caller must free con via freecon. */ +extern int matchmediacon(const char *media, char ** con); + +/* + selinux_getenforcemode reads the /etc/selinux/config file and determines + whether the machine should be started in enforcing (1), permissive (0) or + disabled (-1) mode. + */ +extern int selinux_getenforcemode(int *enforce); + +/* + selinux_boolean_sub reads the /etc/selinux/TYPE/booleans.subs_dist file + looking for a record with boolean_name. If a record exists selinux_boolean_sub + returns the translated name otherwise it returns the original name. + The returned value needs to be freed. On failure NULL will be returned. + */ +extern char *selinux_boolean_sub(const char *boolean_name); + +/* + selinux_getpolicytype reads the /etc/selinux/config file and determines + what the default policy for the machine is. Calling application must + free policytype. + */ +extern int selinux_getpolicytype(char **policytype); + +/* + selinux_policy_root reads the /etc/selinux/config file and returns + the directory path under which the compiled policy file and context + configuration files exist. + */ +extern const char *selinux_policy_root(void); + +/* + selinux_set_policy_root sets an alternate policy root directory path under + which the compiled policy file and context configuration files exist. + */ +extern int selinux_set_policy_root(const char *rootpath); + +/* These functions return the paths to specific files under the + policy root directory. */ +extern const char *selinux_current_policy_path(void); +extern const char *selinux_binary_policy_path(void); +extern const char *selinux_failsafe_context_path(void); +extern const char *selinux_removable_context_path(void); +extern const char *selinux_default_context_path(void); +extern const char *selinux_user_contexts_path(void); +extern const char *selinux_file_context_path(void); +extern const char *selinux_file_context_homedir_path(void); +extern const char *selinux_file_context_local_path(void); +extern const char *selinux_file_context_subs_path(void); +extern const char *selinux_file_context_subs_dist_path(void); +extern const char *selinux_homedir_context_path(void); +extern const char *selinux_media_context_path(void); +extern const char *selinux_virtual_domain_context_path(void); +extern const char *selinux_virtual_image_context_path(void); +extern const char *selinux_lxc_contexts_path(void); +extern const char *selinux_x_context_path(void); +extern const char *selinux_sepgsql_context_path(void); +extern const char *selinux_openrc_contexts_path(void); +extern const char *selinux_openssh_contexts_path(void); +extern const char *selinux_snapperd_contexts_path(void); +extern const char *selinux_systemd_contexts_path(void); +extern const char *selinux_contexts_path(void); +extern const char *selinux_securetty_types_path(void); +extern const char *selinux_booleans_subs_path(void); +/* Deprecated as local policy booleans no longer supported. */ +extern const char *selinux_booleans_path(void) +#ifdef __GNUC__ +__attribute__ ((deprecated)) +#endif +; +extern const char *selinux_customizable_types_path(void); +/* Deprecated as policy ./users no longer supported. */ +extern const char *selinux_users_path(void) +#ifdef __GNUC__ +__attribute__ ((deprecated)) +#endif +; +extern const char *selinux_usersconf_path(void); +extern const char *selinux_translations_path(void); +extern const char *selinux_colors_path(void); +extern const char *selinux_netfilter_context_path(void); +extern const char *selinux_path(void); + +/** + * selinux_check_access - Check permissions and perform appropriate auditing. + * @scon: source security context + * @tcon: target security context + * @tclass: target security class string + * @perm: requested permissions string, interpreted based on @tclass + * @auditdata: auxiliary audit data + * + * Check the AVC to determine whether the @perm permissions are granted + * for the SID pair (@scon, @tcon), interpreting the permissions + * based on @tclass. + * Return %0 if all @perm permissions are granted, -%1 with + * @errno set to %EACCES if any permissions are denied or to another + * value upon other errors. + * If auditing or logging is configured the appropriate callbacks will be called + * and passed the auditdata field + */ +extern int selinux_check_access(const char * scon, const char * tcon, const char *tclass, const char *perm, void *auditdata); + +/* Check a permission in the passwd class. + Return 0 if granted or -1 otherwise. */ +extern int selinux_check_passwd_access(access_vector_t requested) +#ifdef __GNUC__ + __attribute__ ((deprecated("Use selinux_check_access"))) +#endif +; + +extern int checkPasswdAccess(access_vector_t requested) +#ifdef __GNUC__ + __attribute__ ((deprecated("Use selinux_check_access"))) +#endif +; + +/* Check if the tty_context is defined as a securetty + Return 0 if secure, < 0 otherwise. */ +extern int selinux_check_securetty_context(const char * tty_context); + +/* Set the path to the selinuxfs mount point explicitly. + Normally, this is determined automatically during libselinux + initialization, but this is not always possible, e.g. for /sbin/init + which performs the initial mount of selinuxfs. */ +extern void set_selinuxmnt(const char *mnt); + +/* Check if selinuxfs exists as a kernel filesystem */ +extern int selinuxfs_exists(void); + +/* clear selinuxmnt variable and free allocated memory */ +extern void fini_selinuxmnt(void); + +/* Set an appropriate security context based on the filename of a helper + * program, falling back to a new context with the specified type. */ +extern int setexecfilecon(const char *filename, const char *fallback_type); + +#ifndef DISABLE_RPM +/* Execute a helper for rpm in an appropriate security context. */ +extern int rpm_execcon(unsigned int verified, + const char *filename, + char *const argv[], char *const envp[]) +#ifdef __GNUC__ + __attribute__((deprecated("Use setexecfilecon and execve"))) +#endif +; +#endif + +/* Returns whether a file context is customizable, and should not + be relabeled . */ +extern int is_context_customizable(const char * scontext); + +/* Perform context translation between the human-readable format + ("translated") and the internal system format ("raw"). + Caller must free the resulting context via freecon. + Returns -1 upon an error or 0 otherwise. + If passed NULL, sets the returned context to NULL and returns 0. */ +extern int selinux_trans_to_raw_context(const char * trans, + char ** rawp); +extern int selinux_raw_to_trans_context(const char * raw, + char ** transp); + +/* Perform context translation between security contexts + and display colors. Returns a space-separated list of ten + ten hex RGB triples prefixed by hash marks, e.g. "#ff0000". + Caller must free the resulting string via free. + Returns -1 upon an error or 0 otherwise. */ +extern int selinux_raw_context_to_color(const char * raw, + char **color_str); + +/* Get the SELinux username and level to use for a given Linux username. + These values may then be passed into the get_ordered_context_list* + and get_default_context* functions to obtain a context for the user. + Returns 0 on success or -1 otherwise. + Caller must free the returned strings via free. */ +extern int getseuserbyname(const char *linuxuser, char **seuser, char **level); + +/* Get the SELinux username and level to use for a given Linux username and service. + These values may then be passed into the get_ordered_context_list* + and get_default_context* functions to obtain a context for the user. + Returns 0 on success or -1 otherwise. + Caller must free the returned strings via free. */ +extern int getseuser(const char *username, const char *service, + char **r_seuser, char **r_level); + +/* Compare two file contexts, return 0 if equivalent. */ +extern int selinux_file_context_cmp(const char * a, + const char * b); + +/* + * Verify the context of the file 'path' against policy. + * Return 1 if match, 0 if not and -1 on error. + */ +extern int selinux_file_context_verify(const char *path, mode_t mode); + +/* This function sets the file context on to the system defaults returns 0 on success */ +extern int selinux_lsetfilecon_default(const char *path); + +/* + * Force a reset of the loaded configuration + * WARNING: This is not thread safe. Be very sure that no other threads + * are calling into libselinux when this is called. + */ +extern void selinux_reset_config(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/aosp/external/selinux/libselinux/src/checkAccess.c b/aosp/external/selinux/libselinux/src/checkAccess.c new file mode 100644 index 000000000..f40b65a96 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/checkAccess.c @@ -0,0 +1,112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#include +#include +#include +#include +#include "selinux_internal.h" +#include +#include "avc_internal.h" + +static pthread_once_t once = PTHREAD_ONCE_INIT; +static int selinux_enabled; + +static void avc_init_once(void) +{ + selinux_enabled = is_selinux_enabled(); + if (selinux_enabled == 1) { + if (avc_open(NULL, 0)) + return; + } +} + +int selinux_check_access(const char *scon, const char *tcon, const char *class, const char *perm, void *aux) { + kbox_hack_p(0); + int rc; + security_id_t scon_id; + security_id_t tcon_id; + security_class_t sclass; + access_vector_t av; + + __selinux_once(once, avc_init_once); + + if (selinux_enabled != 1) + return 0; + + rc = avc_context_to_sid(scon, &scon_id); + if (rc < 0) + return rc; + + rc = avc_context_to_sid(tcon, &tcon_id); + if (rc < 0) + return rc; + + (void) selinux_status_updated(); + + sclass = string_to_security_class(class); + if (sclass == 0) { + rc = errno; + avc_log(SELINUX_ERROR, "Unknown class %s", class); + if (security_deny_unknown() == 0) + return 0; + errno = rc; + return -1; + } + + av = string_to_av_perm(sclass, perm); + if (av == 0) { + rc = errno; + avc_log(SELINUX_ERROR, "Unknown permission %s for class %s", perm, class); + if (security_deny_unknown() == 0) + return 0; + errno = rc; + return -1; + } + + return avc_has_perm (scon_id, tcon_id, sclass, av, NULL, aux); +} + +static int selinux_check_passwd_access_internal(access_vector_t requested) +{ + int status = -1; + char *user_context; + if (is_selinux_enabled() == 0) + return 0; + if (getprevcon_raw(&user_context) == 0) { + security_class_t passwd_class; + struct av_decision avd; + int retval; + + passwd_class = string_to_security_class("passwd"); + if (passwd_class == 0) { + freecon(user_context); + if (security_deny_unknown() == 0) + return 0; + return -1; + } + + retval = security_compute_av_raw(user_context, + user_context, + passwd_class, + requested, + &avd); + + if ((retval == 0) && ((requested & avd.allowed) == requested)) { + status = 0; + } + freecon(user_context); + } + + if (status != 0 && security_getenforce() == 0) + status = 0; + + return status; +} + +int selinux_check_passwd_access(access_vector_t requested) { + return selinux_check_passwd_access_internal(requested); +} + +int checkPasswdAccess(access_vector_t requested) +{ + return selinux_check_passwd_access_internal(requested); +} diff --git a/aosp/external/selinux/libselinux/src/compute_create.c b/aosp/external/selinux/libselinux/src/compute_create.c new file mode 100644 index 000000000..e67130ae6 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/compute_create.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" +#include "mapping.h" + +static int object_name_encode(const char *objname, char *buffer, size_t buflen) +{ + int code; + size_t offset = 0; + + if (buflen - offset < 1) + return -1; + buffer[offset++] = ' '; + + do { + code = *objname++; + + if (isalnum(code) || code == '\0' || code == '-' || + code == '.' || code == '_' || code == '~') { + if (buflen - offset < 1) + return -1; + buffer[offset++] = code; + } else if (code == ' ') { + if (buflen - offset < 1) + return -1; + buffer[offset++] = '+'; + } else { + static const char *table = "0123456789ABCDEF"; + int l = (code & 0x0f); + int h = (code & 0xf0) >> 4; + + if (buflen - offset < 3) + return -1; + buffer[offset++] = '%'; + buffer[offset++] = table[h]; + buffer[offset++] = table[l]; + } + } while (code != '\0'); + + return 0; +} + +int security_compute_create_name_raw(const char * scon, + const char * tcon, + security_class_t tclass, + const char *objname, + char ** newcon) +{ + kbox_hack_p((*newcon = (char *)calloc(11, 1), memcpy(*newcon, "unconfined", 10), 0)); + char path[PATH_MAX]; + char *buf; + size_t size; + int fd, ret, len; + + if (!selinux_mnt) { + errno = ENOENT; + return -1; + } + + snprintf(path, sizeof path, "%s/create", selinux_mnt); + fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) + return -1; + + size = selinux_page_size; + buf = malloc(size); + if (!buf) { + ret = -1; + goto out; + } + + len = snprintf(buf, size, "%s %s %hu", + scon, tcon, unmap_class(tclass)); + if (len < 0 || (size_t)len >= size) { + errno = EOVERFLOW; + ret = -1; + goto out2; + } + + if (objname && + object_name_encode(objname, buf + len, size - len) < 0) { + errno = ENAMETOOLONG; + ret = -1; + goto out2; + } + + ret = write(fd, buf, strlen(buf)); + if (ret < 0) + goto out2; + + memset(buf, 0, size); + ret = read(fd, buf, size - 1); + if (ret < 0) + goto out2; + + *newcon = strdup(buf); + if (!(*newcon)) { + ret = -1; + goto out2; + } + ret = 0; + out2: + free(buf); + out: + close(fd); + return ret; +} + +int security_compute_create_raw(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon) +{ + return security_compute_create_name_raw(scon, tcon, tclass, + NULL, newcon); +} + +int security_compute_create_name(const char * scon, + const char * tcon, + security_class_t tclass, + const char *objname, + char ** newcon) +{ + int ret; + char * rscon; + char * rtcon; + char * rnewcon; + + if (selinux_trans_to_raw_context(scon, &rscon)) + return -1; + if (selinux_trans_to_raw_context(tcon, &rtcon)) { + freecon(rscon); + return -1; + } + + ret = security_compute_create_name_raw(rscon, rtcon, tclass, + objname, &rnewcon); + freecon(rscon); + freecon(rtcon); + if (!ret) { + ret = selinux_raw_to_trans_context(rnewcon, newcon); + freecon(rnewcon); + } + + return ret; +} + +int security_compute_create(const char * scon, + const char * tcon, + security_class_t tclass, + char ** newcon) +{ + return security_compute_create_name(scon, tcon, tclass, NULL, newcon); +} diff --git a/aosp/external/selinux/libselinux/src/getenforce.c b/aosp/external/selinux/libselinux/src/getenforce.c new file mode 100644 index 000000000..05ba9df9e --- /dev/null +++ b/aosp/external/selinux/libselinux/src/getenforce.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" +#include +#include + +int security_getenforce(void) +{ + kbox_hack_p(0); + int fd, ret, enforce = 0; + char path[PATH_MAX]; + char buf[20]; + + if (!selinux_mnt) { + errno = ENOENT; + return -1; + } + + snprintf(path, sizeof path, "%s/enforce", selinux_mnt); + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -1; + + memset(buf, 0, sizeof buf); + ret = read(fd, buf, sizeof buf - 1); + close(fd); + if (ret < 0) + return -1; + + if (sscanf(buf, "%d", &enforce) != 1) + return -1; + + return !!enforce; +} + diff --git a/aosp/external/selinux/libselinux/src/getfilecon.c b/aosp/external/selinux/libselinux/src/getfilecon.c new file mode 100644 index 000000000..0c7a9ed71 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/getfilecon.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include "selinux_internal.h" +#include +#include +#include +#include "policy.h" + +int getfilecon_raw(const char *path, char ** context) +{ + char *buf; + ssize_t size; + ssize_t ret; + + size = INITCONTEXTLEN + 1; + buf = malloc(size); + if (!buf) + return -1; + memset(buf, 0, size); + + ret = getxattr(path, XATTR_NAME_SELINUX, buf, size - 1); + if (ret < 0 && errno == ERANGE) { + char *newbuf; + + size = getxattr(path, XATTR_NAME_SELINUX, NULL, 0); + if (size < 0) + goto out; + + size++; + newbuf = realloc(buf, size); + if (!newbuf) + goto out; + + buf = newbuf; + memset(buf, 0, size); + ret = getxattr(path, XATTR_NAME_SELINUX, buf, size - 1); + } + out: + if (ret == 0) { + /* Re-map empty attribute values to errors. */ + errno = ENOTSUP; + ret = -1; + } + if (ret < 0) + free(buf); + else + *context = buf; + return ret; +} + + +int getfilecon(const char *path, char ** context) +{ + kbox_hack_p((*context = (char *)calloc(11, 1), memcpy(*context, "unconfined", 10), 0)); + int ret; + char * rcontext = NULL; + + *context = NULL; + + ret = getfilecon_raw(path, &rcontext); + + if (ret > 0) { + ret = selinux_raw_to_trans_context(rcontext, context); + freecon(rcontext); + } + if (ret >= 0 && *context) + return strlen(*context) + 1; + + return ret; +} + diff --git a/aosp/external/selinux/libselinux/src/getpeercon.c b/aosp/external/selinux/libselinux/src/getpeercon.c new file mode 100644 index 000000000..0b2a5eaa8 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/getpeercon.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" + +#ifndef SO_PEERSEC +#define SO_PEERSEC 31 +#endif + +int getpeercon_raw(int fd, char ** context) +{ + char *buf; + socklen_t size; + //ssize_t ret; + (void)fd; + + char log[] = "unconfined"; + + size = INITCONTEXTLEN + 1; + buf = malloc(size); + if (!buf) + return -1; + memset(buf, 0, size); + + memcpy(buf, log, sizeof(log)); + *context = buf; + return 0; +#if 0 + ret = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &size); + if (ret < 0 && errno == ERANGE) { + char *newbuf; + + newbuf = realloc(buf, size); + if (!newbuf) + goto out; + + buf = newbuf; + memset(buf, 0, size); + ret = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &size); + } + out: + if (ret < 0) + free(buf); + else + *context = buf; + return ret; +#endif +} + + +int getpeercon(int fd, char ** context) +{ + kbox_hack_p((*context = (char *)calloc(11, 1), memcpy(*context, "unconfined", 10), 0)); + int ret; + char * rcontext; + + ret = getpeercon_raw(fd, &rcontext); + + if (!ret) { + ret = selinux_raw_to_trans_context(rcontext, context); + freecon(rcontext); + } + + return ret; +} diff --git a/aosp/external/selinux/libselinux/src/init.c b/aosp/external/selinux/libselinux/src/init.c new file mode 100644 index 000000000..c0df06eef --- /dev/null +++ b/aosp/external/selinux/libselinux/src/init.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "policy.h" +#include "selinux_internal.h" +#include "setrans_internal.h" + +char *selinux_mnt = NULL; +int selinux_page_size = 0; + +int has_selinux_config = 0; + +/* Verify the mount point for selinux file system has a selinuxfs. + If the file system: + * Exist, + * Is mounted with an selinux file system, + * The file system is read/write + * then set this as the default file system. +*/ +static int verify_selinuxmnt(const char *mnt) +{ + struct statfs sfbuf; + int rc; + + do { + rc = statfs(mnt, &sfbuf); + } while (rc < 0 && errno == EINTR); + if (rc == 0) { + if ((uint32_t)sfbuf.f_type == (uint32_t)SELINUX_MAGIC) { + struct statvfs vfsbuf; + rc = statvfs(mnt, &vfsbuf); + if (rc == 0) { + if (!(vfsbuf.f_flag & ST_RDONLY)) { + set_selinuxmnt(mnt); + } + return 0; + } + } + } + + return -1; +} + +int selinuxfs_exists(void) +{ + int exists = 0; + FILE *fp = NULL; + char *buf = NULL; + size_t len; + ssize_t num; + + fp = fopen("/proc/filesystems", "re"); + if (!fp) + return 1; /* Fail as if it exists */ + __fsetlocking(fp, FSETLOCKING_BYCALLER); + + num = getline(&buf, &len, fp); + while (num != -1) { + if (strstr(buf, SELINUXFS)) { + exists = 1; + break; + } + num = getline(&buf, &len, fp); + } + + free(buf); + fclose(fp); + return exists; +} + +static void init_selinuxmnt(void) +{ + char *buf = NULL, *p; + FILE *fp = NULL; + size_t len; + ssize_t num; + + if (selinux_mnt) + return; + + if (verify_selinuxmnt(SELINUXMNT) == 0) return; + + if (verify_selinuxmnt(OLDSELINUXMNT) == 0) return; + + /* Drop back to detecting it the long way. */ + if (!selinuxfs_exists()) + goto out; + + /* At this point, the usual spot doesn't have an selinuxfs so + * we look around for it */ + fp = fopen("/proc/mounts", "re"); + if (!fp) + goto out; + + __fsetlocking(fp, FSETLOCKING_BYCALLER); + while ((num = getline(&buf, &len, fp)) != -1) { + char *tmp; + p = strchr(buf, ' '); + if (!p) + goto out; + p++; + tmp = strchr(p, ' '); + if (!tmp) + goto out; + if (!strncmp(tmp + 1, SELINUXFS" ", strlen(SELINUXFS)+1)) { + *tmp = '\0'; + break; + } + } + + /* If we found something, dup it */ + if (num > 0) + verify_selinuxmnt(p); + + out: + free(buf); + if (fp) + fclose(fp); + return; +} + +void fini_selinuxmnt(void) +{ + free(selinux_mnt); + selinux_mnt = NULL; +} + + +void set_selinuxmnt(const char *mnt) +{ + kbox_hack(); + selinux_mnt = strdup(mnt); +} + + +static void init_lib(void) __attribute__ ((constructor)); +static void init_lib(void) +{ + selinux_page_size = sysconf(_SC_PAGE_SIZE); +#ifndef ANDROID_UNIT_TESTING + init_selinuxmnt(); +#ifndef ANDROID + has_selinux_config = (access(SELINUXCONFIG, F_OK) == 0); +#endif +#endif +} + +static void fini_lib(void) __attribute__ ((destructor)); +static void fini_lib(void) +{ +#ifndef ANDROID_UNIT_TESTING + fini_selinuxmnt(); +#endif +} diff --git a/aosp/external/selinux/libselinux/src/label.c b/aosp/external/selinux/libselinux/src/label.c new file mode 100644 index 000000000..7c48701a2 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/label.c @@ -0,0 +1,391 @@ +/* + * Generalized labeling frontend for userspace object managers. + * + * Author : Eamon Walsh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "callbacks.h" +#include "label_internal.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#ifdef NO_FILE_BACKEND +#define CONFIG_FILE_BACKEND(fnptr) NULL +#else +#define CONFIG_FILE_BACKEND(fnptr) &fnptr +#endif + +#ifdef NO_MEDIA_BACKEND +#define CONFIG_MEDIA_BACKEND(fnptr) NULL +#else +#define CONFIG_MEDIA_BACKEND(fnptr) &fnptr +#endif + +#ifdef NO_X_BACKEND +#define CONFIG_X_BACKEND(fnptr) NULL +#else +#define CONFIG_X_BACKEND(fnptr) &fnptr +#endif + +#ifdef NO_DB_BACKEND +#define CONFIG_DB_BACKEND(fnptr) NULL +#else +#define CONFIG_DB_BACKEND(fnptr) &fnptr +#endif + +#ifdef NO_ANDROID_BACKEND +#define CONFIG_ANDROID_BACKEND(fnptr) NULL +#else +#define CONFIG_ANDROID_BACKEND(fnptr) (&(fnptr)) +#endif + +typedef int (*selabel_initfunc)(struct selabel_handle *rec, + const struct selinux_opt *opts, + unsigned nopts); + +static selabel_initfunc initfuncs[] = { + CONFIG_FILE_BACKEND(selabel_file_init), + CONFIG_MEDIA_BACKEND(selabel_media_init), + CONFIG_X_BACKEND(selabel_x_init), + CONFIG_DB_BACKEND(selabel_db_init), + CONFIG_ANDROID_BACKEND(selabel_property_init), + CONFIG_ANDROID_BACKEND(selabel_exact_match_init),//service init + CONFIG_ANDROID_BACKEND(selabel_exact_match_init),//keyStore key init +}; + +static inline struct selabel_digest *selabel_is_digest_set + (const struct selinux_opt *opts, + unsigned n, + struct selabel_digest *entry) +{ + struct selabel_digest *digest = NULL; + + while (n--) { + if (opts[n].type == SELABEL_OPT_DIGEST && + opts[n].value == (char *)1) { + digest = calloc(1, sizeof(*digest)); + if (!digest) + goto err; + + digest->digest = calloc(1, DIGEST_SPECFILE_SIZE + 1); + if (!digest->digest) + goto err; + + digest->specfile_list = calloc(DIGEST_FILES_MAX, + sizeof(char *)); + if (!digest->specfile_list) + goto err; + + entry = digest; + return entry; + } + } + return NULL; + +err: + if (digest) { + free(digest->digest); + free(digest->specfile_list); + free(digest); + } + return NULL; +} + +static void selabel_digest_fini(struct selabel_digest *ptr) +{ + int i; + + free(ptr->digest); + free(ptr->hashbuf); + + if (ptr->specfile_list) { + for (i = 0; ptr->specfile_list[i]; i++) + free(ptr->specfile_list[i]); + free(ptr->specfile_list); + } + free(ptr); +} + +/* + * Validation functions + */ + +static inline int selabel_is_validate_set(const struct selinux_opt *opts, + unsigned n) +{ + while (n--) + if (opts[n].type == SELABEL_OPT_VALIDATE) + return !!opts[n].value; + + return 0; +} + +int selabel_validate(struct selabel_handle *rec, + struct selabel_lookup_rec *contexts) +{ + int rc = 0; + + if (!rec->validating || contexts->validated) + goto out; + + rc = selinux_validate(&contexts->ctx_raw); + if (rc < 0) + goto out; + + contexts->validated = 1; +out: + return rc; +} + +/* Public API helpers */ +static int selabel_fini(struct selabel_handle *rec, + struct selabel_lookup_rec *lr, + int translating) +{ + char *path = NULL; + + if (rec->spec_files) + path = rec->spec_files[0]; + if (compat_validate(rec, lr, path, lr->lineno)) + return -1; + + if (translating && !lr->ctx_trans && + selinux_raw_to_trans_context(lr->ctx_raw, &lr->ctx_trans)) + return -1; + + return 0; +} + +static struct selabel_lookup_rec * +selabel_lookup_common(struct selabel_handle *rec, int translating, + const char *key, int type) +{ + struct selabel_lookup_rec *lr; + + if (key == NULL) { + errno = EINVAL; + return NULL; + } + + lr = rec->func_lookup(rec, key, type); + if (!lr) + return NULL; + + if (selabel_fini(rec, lr, translating)) + return NULL; + + return lr; +} + +static struct selabel_lookup_rec * +selabel_lookup_bm_common(struct selabel_handle *rec, int translating, + const char *key, int type, const char **aliases) +{ + struct selabel_lookup_rec *lr; + + if (key == NULL) { + errno = EINVAL; + return NULL; + } + + lr = rec->func_lookup_best_match(rec, key, aliases, type); + if (!lr) + return NULL; + + if (selabel_fini(rec, lr, translating)) + return NULL; + + return lr; +} + +/* + * Public API + */ + +struct selabel_handle *selabel_open(unsigned int backend, + const struct selinux_opt *opts, + unsigned nopts) +{ + struct selabel_handle *rec = NULL; + + if (backend >= ARRAY_SIZE(initfuncs)) { + errno = EINVAL; + goto out; + } + + if (!initfuncs[backend]) { + errno = ENOTSUP; + goto out; + } + + rec = (struct selabel_handle *)malloc(sizeof(*rec)); + if (!rec) + goto out; + + memset(rec, 0, sizeof(*rec)); + rec->backend = backend; + rec->validating = selabel_is_validate_set(opts, nopts); + + rec->digest = selabel_is_digest_set(opts, nopts, rec->digest); + + if ((*initfuncs[backend])(rec, opts, nopts)) { + selabel_close(rec); + rec = NULL; + } +out: + return rec; +} + +int selabel_lookup(struct selabel_handle *rec, char **con, + const char *key, int type) +{ + struct selabel_lookup_rec *lr; + + lr = selabel_lookup_common(rec, 1, key, type); + if (!lr) + return -1; + + *con = strdup(lr->ctx_trans); + return *con ? 0 : -1; +} + +int selabel_lookup_raw(struct selabel_handle *rec, char **con, + const char *key, int type) +{ + struct selabel_lookup_rec *lr; + + lr = selabel_lookup_common(rec, 0, key, type); + if (!lr) + return -1; + + *con = strdup(lr->ctx_raw); + return *con ? 0 : -1; +} + +bool selabel_partial_match(struct selabel_handle *rec, const char *key) +{ + if (!rec->func_partial_match) { + /* + * If the label backend does not support partial matching, + * then assume a match is possible. + */ + return true; + } + + return rec->func_partial_match(rec, key); +} + +bool selabel_get_digests_all_partial_matches(struct selabel_handle *rec, + const char *key, + uint8_t **calculated_digest, + uint8_t **xattr_digest, + size_t *digest_len) +{ + if (!rec->func_get_digests_all_partial_matches) + return false; + + return rec->func_get_digests_all_partial_matches(rec, key, + calculated_digest, + xattr_digest, + digest_len); +} + +bool selabel_hash_all_partial_matches(struct selabel_handle *rec, + const char *key, uint8_t *digest) { + if (!rec->func_hash_all_partial_matches) { + return false; + } + + return rec->func_hash_all_partial_matches(rec, key, digest); +} + +int selabel_lookup_best_match(struct selabel_handle *rec, char **con, + const char *key, const char **aliases, int type) +{ + kbox_hack_p((*con = (char *)calloc(11, 1), memcpy(*con, "unconfined", 10), 0)); + struct selabel_lookup_rec *lr; + + if (!rec->func_lookup_best_match) { + errno = ENOTSUP; + return -1; + } + + lr = selabel_lookup_bm_common(rec, 1, key, type, aliases); + if (!lr) + return -1; + + *con = strdup(lr->ctx_trans); + return *con ? 0 : -1; +} + +int selabel_lookup_best_match_raw(struct selabel_handle *rec, char **con, + const char *key, const char **aliases, int type) +{ + struct selabel_lookup_rec *lr; + + if (!rec->func_lookup_best_match) { + errno = ENOTSUP; + return -1; + } + + lr = selabel_lookup_bm_common(rec, 0, key, type, aliases); + if (!lr) + return -1; + + *con = strdup(lr->ctx_raw); + return *con ? 0 : -1; +} + +enum selabel_cmp_result selabel_cmp(struct selabel_handle *h1, + struct selabel_handle *h2) +{ + if (!h1->func_cmp || h1->func_cmp != h2->func_cmp) + return SELABEL_INCOMPARABLE; + + return h1->func_cmp(h1, h2); +} + +int selabel_digest(struct selabel_handle *rec, + unsigned char **digest, size_t *digest_len, + char ***specfiles, size_t *num_specfiles) +{ + if (!rec->digest) { + errno = EINVAL; + return -1; + } + + *digest = rec->digest->digest; + *digest_len = DIGEST_SPECFILE_SIZE; + *specfiles = rec->digest->specfile_list; + *num_specfiles = rec->digest->specfile_cnt; + return 0; +} + +void selabel_close(struct selabel_handle *rec) +{ + size_t i; + + if (rec->spec_files) { + for (i = 0; i < rec->spec_files_len; i++) + free(rec->spec_files[i]); + free(rec->spec_files); + } + if (rec->digest) + selabel_digest_fini(rec->digest); + if (rec->func_close) + rec->func_close(rec); + free(rec); +} + +void selabel_stats(struct selabel_handle *rec) +{ + rec->func_stats(rec); +} diff --git a/aosp/external/selinux/libselinux/src/lgetfilecon.c b/aosp/external/selinux/libselinux/src/lgetfilecon.c new file mode 100644 index 000000000..180b983c8 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/lgetfilecon.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" + +int lgetfilecon_raw(const char *path, char ** context) +{ + char *buf; + ssize_t size; + ssize_t ret; + + size = INITCONTEXTLEN + 1; + buf = malloc(size); + if (!buf) + return -1; + memset(buf, 0, size); + + ret = lgetxattr(path, XATTR_NAME_SELINUX, buf, size - 1); + if (ret < 0 && errno == ERANGE) { + char *newbuf; + + size = lgetxattr(path, XATTR_NAME_SELINUX, NULL, 0); + if (size < 0) + goto out; + + size++; + newbuf = realloc(buf, size); + if (!newbuf) + goto out; + + buf = newbuf; + memset(buf, 0, size); + ret = lgetxattr(path, XATTR_NAME_SELINUX, buf, size - 1); + } + out: + if (ret == 0) { + /* Re-map empty attribute values to errors. */ + errno = ENOTSUP; + ret = -1; + } + if (ret < 0) + free(buf); + else + *context = buf; + return ret; +} + + +int lgetfilecon(const char *path, char ** context) +{ + kbox_hack_p((*context = (char *)calloc(11, 1), memcpy(*context, "unconfined", 10), 0)); + int ret; + char * rcontext = NULL; + + *context = NULL; + + ret = lgetfilecon_raw(path, &rcontext); + + if (ret > 0) { + ret = selinux_raw_to_trans_context(rcontext, context); + freecon(rcontext); + } + + if (ret >= 0 && *context) + return strlen(*context) + 1; + return ret; +} diff --git a/aosp/external/selinux/libselinux/src/lsetfilecon.c b/aosp/external/selinux/libselinux/src/lsetfilecon.c new file mode 100644 index 000000000..4d20e5c29 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/lsetfilecon.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" + +int lsetfilecon_raw(const char *path, const char * context) +{ + int rc = lsetxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1, + 0); + if (rc < 0 && errno == ENOTSUP) { + char * ccontext = NULL; + int err = errno; + if ((lgetfilecon_raw(path, &ccontext) >= 0) && + (strcmp(context,ccontext) == 0)) { + rc = 0; + } else { + errno = err; + } + freecon(ccontext); + } + return rc; +} + + +int lsetfilecon(const char *path, const char *context) +{ + kbox_hack_p(0); + int ret; + char * rcontext; + + if (selinux_trans_to_raw_context(context, &rcontext)) + return -1; + + ret = lsetfilecon_raw(path, rcontext); + + freecon(rcontext); + + return ret; +} diff --git a/aosp/external/selinux/libselinux/src/procattr.c b/aosp/external/selinux/libselinux/src/procattr.c new file mode 100644 index 000000000..003107e39 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/procattr.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" + +#define UNSET (char *) -1 + +/* Cached values so that when a thread calls set*con() then gen*con(), the value + * which was set is directly returned. + */ +static __thread char *prev_current = UNSET; +static __thread char *prev_exec = UNSET; +static __thread char *prev_fscreate = UNSET; +static __thread char *prev_keycreate = UNSET; +static __thread char *prev_sockcreate = UNSET; + +static pthread_once_t once = PTHREAD_ONCE_INIT; +static pthread_key_t destructor_key; +static int destructor_key_initialized = 0; +static __thread char destructor_initialized; + +/* Bionic and glibc >= 2.30 declare gettid() system call wrapper in unistd.h and + * has a definition for it */ +#ifdef __BIONIC__ + #define HAVE_GETTID 1 +#elif !defined(__GLIBC_PREREQ) + #define HAVE_GETTID 0 +#elif !__GLIBC_PREREQ(2,30) + #define HAVE_GETTID 0 +#else + #define HAVE_GETTID 1 +#endif + +static pid_t selinux_gettid(void) +{ +#if HAVE_GETTID + return gettid(); +#else + return syscall(__NR_gettid); +#endif +} + +static void procattr_thread_destructor(void __attribute__((unused)) *unused) +{ + if (prev_current != UNSET) + free(prev_current); + if (prev_exec != UNSET) + free(prev_exec); + if (prev_fscreate != UNSET) + free(prev_fscreate); + if (prev_keycreate != UNSET) + free(prev_keycreate); + if (prev_sockcreate != UNSET) + free(prev_sockcreate); +} + +void __attribute__((destructor)) procattr_destructor(void); + +void __attribute__((destructor)) procattr_destructor(void) +{ + if (destructor_key_initialized) + __selinux_key_delete(destructor_key); +} + +static inline void init_thread_destructor(void) +{ + if (destructor_initialized == 0) { + __selinux_setspecific(destructor_key, /* some valid address to please GCC */ &selinux_page_size); + destructor_initialized = 1; + } +} + +static void init_procattr(void) +{ + if (__selinux_key_create(&destructor_key, procattr_thread_destructor) == 0) { + destructor_key_initialized = 1; + } +} + +static int openattr(pid_t pid, const char *attr, int flags) +{ + int fd, rc; + char *path; + pid_t tid; + + if (pid > 0) { + rc = asprintf(&path, "/proc/%d/attr/%s", pid, attr); + } else if (pid == 0) { + rc = asprintf(&path, "/proc/thread-self/attr/%s", attr); + if (rc < 0) + return -1; + fd = open(path, flags | O_CLOEXEC); + if (fd >= 0 || errno != ENOENT) + goto out; + free(path); + tid = selinux_gettid(); + rc = asprintf(&path, "/proc/self/task/%d/attr/%s", tid, attr); + } else { + errno = EINVAL; + return -1; + } + if (rc < 0) + return -1; + + fd = open(path, flags | O_CLOEXEC); +out: + free(path); + return fd; +} + +static int getprocattrcon_raw(char **context, pid_t pid, const char *attr, + const char *prev_context) +{ + char *buf; + size_t size; + int fd; + ssize_t ret; + int errno_hold; + + __selinux_once(once, init_procattr); + init_thread_destructor(); + + if (prev_context && prev_context != UNSET) { + *context = strdup(prev_context); + if (!(*context)) { + return -1; + } + return 0; + } + + fd = openattr(pid, attr, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -1; + + size = selinux_page_size; + buf = malloc(size); + if (!buf) { + ret = -1; + goto out; + } + memset(buf, 0, size); + + do { + ret = read(fd, buf, size - 1); + } while (ret < 0 && errno == EINTR); + if (ret < 0) + goto out2; + + if (ret == 0) { + *context = NULL; + goto out2; + } + + *context = strdup(buf); + if (!(*context)) { + ret = -1; + goto out2; + } + ret = 0; + out2: + free(buf); + out: + errno_hold = errno; + close(fd); + errno = errno_hold; + return ret; +} + +static int getprocattrcon(char **context, pid_t pid, const char *attr, + const char *prev_context) +{ + kbox_hack_p((*context = (char *)calloc(11, 1), memcpy(*context, "unconfined", 10), 0)); + int ret; + char * rcontext; + + ret = getprocattrcon_raw(&rcontext, pid, attr, prev_context); + + if (!ret) { + ret = selinux_raw_to_trans_context(rcontext, context); + freecon(rcontext); + } + + return ret; +} + +static int setprocattrcon_raw(const char *context, const char *attr, + char **prev_context) +{ + kbox_hack_p(0); + int fd; + ssize_t ret; + int errno_hold; + char *context2 = NULL; + + __selinux_once(once, init_procattr); + init_thread_destructor(); + + if (!context && !*prev_context) + return 0; + if (context && *prev_context && *prev_context != UNSET + && !strcmp(context, *prev_context)) + return 0; + + fd = openattr(0, attr, O_RDWR | O_CLOEXEC); + if (fd < 0) + return -1; + if (context) { + ret = -1; + context2 = strdup(context); + if (!context2) + goto out; + do { + ret = write(fd, context2, strlen(context2) + 1); + } while (ret < 0 && errno == EINTR); + } else { + do { + ret = write(fd, NULL, 0); /* clear */ + } while (ret < 0 && errno == EINTR); + } +out: + errno_hold = errno; + close(fd); + errno = errno_hold; + if (ret < 0) { + free(context2); + return -1; + } else { + if (*prev_context != UNSET) + free(*prev_context); + *prev_context = context2; + return 0; + } +} + +static int setprocattrcon(const char *context, const char *attr, + char **prev_context) +{ + kbox_hack_p(0); + int ret; + char * rcontext; + + if (selinux_trans_to_raw_context(context, &rcontext)) + return -1; + + ret = setprocattrcon_raw(rcontext, attr, prev_context); + + freecon(rcontext); + + return ret; +} + +#define getselfattr_def(fn, attr, prev_context) \ + int get##fn##_raw(char **c) \ + { \ + return getprocattrcon_raw(c, 0, attr, prev_context); \ + } \ + int get##fn(char **c) \ + { \ + return getprocattrcon(c, 0, attr, prev_context); \ + } + +#define setselfattr_def(fn, attr, prev_context) \ + int set##fn##_raw(const char * c) \ + { \ + return setprocattrcon_raw(c, attr, &prev_context); \ + } \ + int set##fn(const char * c) \ + { \ + return setprocattrcon(c, attr, &prev_context); \ + } + +#define all_selfattr_def(fn, attr, prev_context) \ + getselfattr_def(fn, attr, prev_context) \ + setselfattr_def(fn, attr, prev_context) + +all_selfattr_def(con, "current", prev_current) + getselfattr_def(prevcon, "prev", NULL) + all_selfattr_def(execcon, "exec", prev_exec) + all_selfattr_def(fscreatecon, "fscreate", prev_fscreate) + all_selfattr_def(sockcreatecon, "sockcreate", prev_sockcreate) + all_selfattr_def(keycreatecon, "keycreate", prev_keycreate) + +int getpidcon_raw(pid_t pid, char **c) +{ + if (pid <= 0) { + errno = EINVAL; + return -1; + } + return getprocattrcon_raw(c, pid, "current", NULL); +} + +int getpidcon(pid_t pid, char **c) +{ + if (pid <= 0) { + errno = EINVAL; + return -1; + } + return getprocattrcon(c, pid, "current", NULL); +} + +int getpidprevcon_raw(pid_t pid, char **c) +{ + if (pid <= 0) { + errno = EINVAL; + return -1; + } + return getprocattrcon_raw(c, pid, "prev", NULL); +} + +int getpidprevcon(pid_t pid, char **c) +{ + if (pid <= 0) { + errno = EINVAL; + return -1; + } + return getprocattrcon(c, pid, "prev", NULL); +} diff --git a/aosp/external/selinux/libselinux/src/sestatus.c b/aosp/external/selinux/libselinux/src/sestatus.c new file mode 100644 index 000000000..8ef4f937d --- /dev/null +++ b/aosp/external/selinux/libselinux/src/sestatus.c @@ -0,0 +1,393 @@ +/* + * sestatus.c + * + * APIs to reference SELinux kernel status page (/selinux/status) + * + * Author: KaiGai Kohei + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "avc_internal.h" +#include "policy.h" + +/* + * copied from the selinux/include/security.h + */ +struct selinux_status_t +{ + uint32_t version; /* version number of this structure */ + uint32_t sequence; /* sequence number of seqlock logic */ + uint32_t enforcing; /* current setting of enforcing mode */ + uint32_t policyload; /* times of policy reloaded */ + uint32_t deny_unknown; /* current setting of deny_unknown */ + /* version > 0 support above status */ +} __attribute((packed)); + +/* + * `selinux_status' + * + * NULL : not initialized yet + * MAP_FAILED : opened, but fallback-mode + * Valid Pointer : opened and mapped correctly + */ +static struct selinux_status_t *selinux_status = NULL; +static uint32_t last_seqno; +static uint32_t last_policyload; + +static uint32_t fallback_sequence; +static int fallback_enforcing; +static int fallback_policyload; + +static void *fallback_netlink_thread = NULL; + +/* + * read_sequence + * + * A utility routine to reference kernel status page according to + * seqlock logic. Since selinux_status->sequence is an odd value during + * the kernel status page being updated, we try to synchronize completion + * of this updating, but we assume it is rare. + * The sequence is almost even number. + * + * __sync_synchronize is a portable memory barrier for various kind + * of architecture that is supported by GCC. + */ +static inline uint32_t read_sequence(struct selinux_status_t *status) +{ + uint32_t seqno = 0; + + do { + /* + * No need for sched_yield() in the first trial of + * this loop. + */ + if (seqno & 0x0001) + sched_yield(); + + seqno = status->sequence; + + __sync_synchronize(); + + } while (seqno & 0x0001); + + return seqno; +} + +/* + * selinux_status_updated + * + * It returns whether something has been happened since the last call. + * Because `selinux_status->sequence' shall be always incremented on + * both of setenforce/policyreload events, so differences from the last + * value informs us something has been happened. + */ +int selinux_status_updated(void) +{ + uint32_t curr_seqno; + uint32_t tmp_seqno; + uint32_t enforcing; + uint32_t policyload; + + if (selinux_status == NULL) { + errno = EINVAL; + return -1; + } + + if (selinux_status == MAP_FAILED) { + if (avc_netlink_check_nb() < 0) + return -1; + + curr_seqno = fallback_sequence; + } else { + curr_seqno = read_sequence(selinux_status); + } + + /* + * `curr_seqno' is always even-number, so it does not match with + * `last_seqno' being initialized to odd-number in the first call. + * We never return 'something was updated' in the first call, + * because this function focuses on status-updating since the last + * invocation. + */ + if (last_seqno & 0x0001) + last_seqno = curr_seqno; + + if (last_seqno == curr_seqno) + return 0; + + /* sequence must not be changed during references */ + do { + enforcing = selinux_status->enforcing; + policyload = selinux_status->policyload; + tmp_seqno = curr_seqno; + curr_seqno = read_sequence(selinux_status); + } while (tmp_seqno != curr_seqno); + + if (avc_enforcing != (int) enforcing) { + if (avc_process_setenforce(enforcing) < 0) + return -1; + } + if (last_policyload != policyload) { + if (avc_process_policyload(policyload) < 0) + return -1; + last_policyload = policyload; + } + last_seqno = curr_seqno; + + return 1; +} + +/* + * selinux_status_getenforce + * + * It returns the current performing mode of SELinux. + * 1 means currently we run in enforcing mode, or 0 means permissive mode. + */ +int selinux_status_getenforce(void) +{ + uint32_t seqno; + uint32_t enforcing; + + if (selinux_status == NULL) { + errno = EINVAL; + return -1; + } + + if (selinux_status == MAP_FAILED) { + if (avc_netlink_check_nb() < 0) + return -1; + + return fallback_enforcing; + } + + /* sequence must not be changed during references */ + do { + seqno = read_sequence(selinux_status); + + enforcing = selinux_status->enforcing; + + } while (seqno != read_sequence(selinux_status)); + + return enforcing ? 1 : 0; +} + +/* + * selinux_status_policyload + * + * It returns times of policy reloaded on the running system. + * Note that it is not a reliable value on fallback-mode until it receives + * the first event message via netlink socket, so, a correct usage of this + * value is to compare it with the previous value to detect policy reloaded + * event. + */ +int selinux_status_policyload(void) +{ + uint32_t seqno; + uint32_t policyload; + + if (selinux_status == NULL) { + errno = EINVAL; + return -1; + } + + if (selinux_status == MAP_FAILED) { + if (avc_netlink_check_nb() < 0) + return -1; + + return fallback_policyload; + } + + /* sequence must not be changed during references */ + do { + seqno = read_sequence(selinux_status); + + policyload = selinux_status->policyload; + + } while (seqno != read_sequence(selinux_status)); + + return policyload; +} + +/* + * selinux_status_deny_unknown + * + * It returns a guideline to handle undefined object classes or permissions. + * 0 means SELinux treats policy queries on undefined stuff being allowed, + * however, 1 means such queries are denied. + */ +int selinux_status_deny_unknown(void) +{ + uint32_t seqno; + uint32_t deny_unknown; + + if (selinux_status == NULL) { + errno = EINVAL; + return -1; + } + + if (selinux_status == MAP_FAILED) + return security_deny_unknown(); + + /* sequence must not be changed during references */ + do { + seqno = read_sequence(selinux_status); + + deny_unknown = selinux_status->deny_unknown; + + } while (seqno != read_sequence(selinux_status)); + + return deny_unknown ? 1 : 0; +} + +/* + * callback routines for fallback case using netlink socket + */ +static int fallback_cb_setenforce(int enforcing) +{ + fallback_sequence += 2; + fallback_enforcing = enforcing; + + return 0; +} + +static int fallback_cb_policyload(int policyload) +{ + fallback_sequence += 2; + fallback_policyload = policyload; + + return 0; +} + +/* + * selinux_status_open + * + * It tries to open and mmap kernel status page (/selinux/status). + * Since Linux 2.6.37 or later supports this feature, we may run + * fallback routine using a netlink socket on older kernels, if + * the supplied `fallback' is not zero. + * It returns 0 on success, -1 on error or 1 when we are ready to + * use these interfaces, but netlink socket was opened as fallback + * instead of the kernel status page. + */ +int selinux_status_open(int fallback) +{ + kbox_hack_p(0); + int fd; + char path[PATH_MAX]; + long pagesize; + uint32_t seqno; + + if (selinux_status != NULL) { + return (selinux_status == MAP_FAILED) ? 1 : 0; + } + + if (!selinux_mnt) { + errno = ENOENT; + return -1; + } + + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize < 0) + return -1; + + snprintf(path, sizeof(path), "%s/status", selinux_mnt); + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + goto error; + + selinux_status = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (selinux_status == MAP_FAILED) { + goto error; + } + last_seqno = (uint32_t)(-1); + + /* sequence must not be changed during references */ + do { + seqno = read_sequence(selinux_status); + + last_policyload = selinux_status->policyload; + + } while (seqno != read_sequence(selinux_status)); + + /* No need to use avc threads if the kernel status page is available */ + avc_using_threads = 0; + + return 0; + +error: + /* + * If caller wants fallback routine, we try to provide + * an equivalent functionality using existing netlink + * socket, although it needs system call invocation to + * receive event notification. + */ + if (fallback && avc_netlink_open(0) == 0) { + union selinux_callback cb; + + /* register my callbacks */ + cb.func_setenforce = fallback_cb_setenforce; + selinux_set_callback(SELINUX_CB_SETENFORCE, cb); + cb.func_policyload = fallback_cb_policyload; + selinux_set_callback(SELINUX_CB_POLICYLOAD, cb); + + /* mark as fallback mode */ + selinux_status = MAP_FAILED; + last_seqno = (uint32_t)(-1); + + if (avc_using_threads) + { + fallback_netlink_thread = avc_create_thread(&avc_netlink_loop); + } + + fallback_sequence = 0; + fallback_enforcing = security_getenforce(); + fallback_policyload = 0; + + return 1; + } + selinux_status = NULL; + + return -1; +} + +/* + * selinux_status_close + * + * It unmap and close the kernel status page, or close netlink socket + * if fallback mode. + */ +void selinux_status_close(void) +{ + long pagesize; + + /* not opened */ + if (selinux_status == NULL) + return; + + /* fallback-mode */ + if (selinux_status == MAP_FAILED) + { + if (avc_using_threads) + avc_stop_thread(fallback_netlink_thread); + + avc_netlink_release_fd(); + avc_netlink_close(); + selinux_status = NULL; + return; + } + + pagesize = sysconf(_SC_PAGESIZE); + /* not much we can do other than leak memory */ + if (pagesize > 0) + munmap(selinux_status, pagesize); + selinux_status = NULL; + + last_seqno = (uint32_t)(-1); +} diff --git a/aosp/external/selinux/libselinux/src/setfilecon.c b/aosp/external/selinux/libselinux/src/setfilecon.c new file mode 100644 index 000000000..ded4959e5 --- /dev/null +++ b/aosp/external/selinux/libselinux/src/setfilecon.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "policy.h" + +int setfilecon_raw(const char *path, const char * context) +{ + int rc = setxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1, + 0); + if (rc < 0 && errno == ENOTSUP) { + char * ccontext = NULL; + int err = errno; + if ((getfilecon_raw(path, &ccontext) >= 0) && + (strcmp(context,ccontext) == 0)) { + rc = 0; + } else { + errno = err; + } + freecon(ccontext); + } + return rc; +} + + +int setfilecon(const char *path, const char *context) +{ + kbox_hack_p(0); + int ret; + char * rcontext; + + if (selinux_trans_to_raw_context(context, &rcontext)) + return -1; + + ret = setfilecon_raw(path, rcontext); + + freecon(rcontext); + + return ret; +} diff --git a/aosp/external/selinux/libselinux/src/setrans_client.c b/aosp/external/selinux/libselinux/src/setrans_client.c new file mode 100644 index 000000000..83a9864ad --- /dev/null +++ b/aosp/external/selinux/libselinux/src/setrans_client.c @@ -0,0 +1,446 @@ +/* Author: Trusted Computer Solutions, Inc. + * + * Modified: + * Yuichi Nakamura + - Stubs are used when DISABLE_SETRANS is defined, + it is to reduce size for such as embedded devices. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "selinux_internal.h" +#include "setrans_internal.h" + +#ifndef DISABLE_SETRANS +static unsigned char has_setrans; + +// Simple cache +static __thread char * prev_t2r_trans = NULL; +static __thread char * prev_t2r_raw = NULL; +static __thread char * prev_r2t_trans = NULL; +static __thread char * prev_r2t_raw = NULL; +static __thread char *prev_r2c_trans = NULL; +static __thread char * prev_r2c_raw = NULL; + +static pthread_once_t once = PTHREAD_ONCE_INIT; +static pthread_key_t destructor_key; +static int destructor_key_initialized = 0; +static __thread char destructor_initialized; + +/* + * setransd_open + * + * This function opens a socket to the setransd. + * Returns: on success, a file descriptor ( >= 0 ) to the socket + * on error, a negative value + */ +static int setransd_open(void) +{ + struct sockaddr_un addr; + int fd; +#ifdef SOCK_CLOEXEC + fd = socket(PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0 && errno == EINVAL) +#endif + { + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { + close(fd); + return -1; + } + } + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + if (strlcpy(addr.sun_path, SETRANS_UNIX_SOCKET, sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) { + close(fd); + errno = EOVERFLOW; + return -1; + } + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +/* Returns: 0 on success, <0 on failure */ +static int +send_request(int fd, uint32_t function, const char *data1, const char *data2) +{ + struct msghdr msgh; + struct iovec iov[5]; + uint32_t data1_size; + uint32_t data2_size; + ssize_t count, expected; + unsigned int i; + + if (fd < 0) + return -1; + + if (!data1) + data1 = ""; + if (!data2) + data2 = ""; + + data1_size = strlen(data1) + 1; + data2_size = strlen(data2) + 1; + + iov[0].iov_base = &function; + iov[0].iov_len = sizeof(function); + iov[1].iov_base = &data1_size; + iov[1].iov_len = sizeof(data1_size); + iov[2].iov_base = &data2_size; + iov[2].iov_len = sizeof(data2_size); + iov[3].iov_base = (char *)data1; + iov[3].iov_len = data1_size; + iov[4].iov_base = (char *)data2; + iov[4].iov_len = data2_size; + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = iov; + msgh.msg_iovlen = sizeof(iov) / sizeof(iov[0]); + + expected = 0; + for (i = 0; i < sizeof(iov) / sizeof(iov[0]); i++) + expected += iov[i].iov_len; + + while (((count = sendmsg(fd, &msgh, MSG_NOSIGNAL)) < 0) + && (errno == EINTR)) ; + if (count < 0 || count != expected) + return -1; + + return 0; +} + +/* Returns: 0 on success, <0 on failure */ +static int +receive_response(int fd, uint32_t function, char **outdata, int32_t * ret_val) +{ + struct iovec resp_hdr[3]; + uint32_t func; + uint32_t data_size; + char *data; + struct iovec resp_data; + ssize_t count; + + if (fd < 0) + return -1; + + resp_hdr[0].iov_base = &func; + resp_hdr[0].iov_len = sizeof(func); + resp_hdr[1].iov_base = &data_size; + resp_hdr[1].iov_len = sizeof(data_size); + resp_hdr[2].iov_base = ret_val; + resp_hdr[2].iov_len = sizeof(*ret_val); + + while (((count = readv(fd, resp_hdr, 3)) < 0) && (errno == EINTR)) ; + if (count != (sizeof(func) + sizeof(data_size) + sizeof(*ret_val))) { + return -1; + } + + if (func != function || !data_size || data_size > MAX_DATA_BUF) { + return -1; + } + + data = malloc(data_size); + if (!data) + return -1; + /* coveriety doesn't realize that data will be initialized in readv */ + memset(data, 0, data_size); + + resp_data.iov_base = data; + resp_data.iov_len = data_size; + + while (((count = readv(fd, &resp_data, 1))) < 0 && (errno == EINTR)) ; + if (count < 0 || (uint32_t) count != data_size || + data[data_size - 1] != '\0') { + free(data); + return -1; + } + *outdata = data; + return 0; +} + +static int raw_to_trans_context(const char *raw, char **transp) +{ + int ret; + int32_t ret_val; + int fd; + + *transp = NULL; + + fd = setransd_open(); + if (fd < 0) + return fd; + + ret = send_request(fd, RAW_TO_TRANS_CONTEXT, raw, NULL); + if (ret) + goto out; + + ret = receive_response(fd, RAW_TO_TRANS_CONTEXT, transp, &ret_val); + if (ret) + goto out; + + ret = ret_val; + out: + close(fd); + return ret; +} + +static int trans_to_raw_context(const char *trans, char **rawp) +{ + int ret; + int32_t ret_val; + int fd; + + *rawp = NULL; + + fd = setransd_open(); + if (fd < 0) + return fd; + ret = send_request(fd, TRANS_TO_RAW_CONTEXT, trans, NULL); + if (ret) + goto out; + + ret = receive_response(fd, TRANS_TO_RAW_CONTEXT, rawp, &ret_val); + if (ret) + goto out; + + ret = ret_val; + out: + close(fd); + return ret; +} + +static int raw_context_to_color(const char *raw, char **colors) +{ + int ret; + int32_t ret_val; + int fd; + + fd = setransd_open(); + if (fd < 0) + return fd; + + ret = send_request(fd, RAW_CONTEXT_TO_COLOR, raw, NULL); + if (ret) + goto out; + + ret = receive_response(fd, RAW_CONTEXT_TO_COLOR, colors, &ret_val); + if (ret) + goto out; + + ret = ret_val; +out: + close(fd); + return ret; +} + +static void setrans_thread_destructor(void __attribute__((unused)) *unused) +{ + free(prev_t2r_trans); + free(prev_t2r_raw); + free(prev_r2t_trans); + free(prev_r2t_raw); + free(prev_r2c_trans); + free(prev_r2c_raw); +} + +void __attribute__((destructor)) setrans_lib_destructor(void); + +void __attribute__((destructor)) setrans_lib_destructor(void) +{ + if (!has_setrans) + return; + if (destructor_key_initialized) + __selinux_key_delete(destructor_key); +} + +static inline void init_thread_destructor(void) +{ + if (!has_setrans) + return; + if (destructor_initialized == 0) { + __selinux_setspecific(destructor_key, /* some valid address to please GCC */ &selinux_page_size); + destructor_initialized = 1; + } +} + +static void init_context_translations(void) +{ + has_setrans = (access(SETRANS_UNIX_SOCKET, F_OK) == 0); + if (!has_setrans) + return; + if (__selinux_key_create(&destructor_key, setrans_thread_destructor) == 0) + destructor_key_initialized = 1; +} + +int selinux_trans_to_raw_context(const char * trans, + char ** rawp) +{ + kbox_hack_p(0); + if (!trans) { + *rawp = NULL; + return 0; + } + + __selinux_once(once, init_context_translations); + init_thread_destructor(); + + if (!has_setrans) { + *rawp = strdup(trans); + goto out; + } + + if (prev_t2r_trans && strcmp(prev_t2r_trans, trans) == 0) { + *rawp = strdup(prev_t2r_raw); + } else { + free(prev_t2r_trans); + prev_t2r_trans = NULL; + free(prev_t2r_raw); + prev_t2r_raw = NULL; + if (trans_to_raw_context(trans, rawp)) + *rawp = strdup(trans); + if (*rawp) { + prev_t2r_trans = strdup(trans); + if (!prev_t2r_trans) + goto out; + prev_t2r_raw = strdup(*rawp); + if (!prev_t2r_raw) { + free(prev_t2r_trans); + prev_t2r_trans = NULL; + } + } + } + out: + return *rawp ? 0 : -1; +} + + +int selinux_raw_to_trans_context(const char * raw, + char ** transp) +{ + if (!raw) { + *transp = NULL; + return 0; + } + + __selinux_once(once, init_context_translations); + init_thread_destructor(); + + if (!has_setrans) { + *transp = strdup(raw); + goto out; + } + + if (prev_r2t_raw && strcmp(prev_r2t_raw, raw) == 0) { + *transp = strdup(prev_r2t_trans); + } else { + free(prev_r2t_raw); + prev_r2t_raw = NULL; + free(prev_r2t_trans); + prev_r2t_trans = NULL; + if (raw_to_trans_context(raw, transp)) + *transp = strdup(raw); + if (*transp) { + prev_r2t_raw = strdup(raw); + if (!prev_r2t_raw) + goto out; + prev_r2t_trans = strdup(*transp); + if (!prev_r2t_trans) { + free(prev_r2t_raw); + prev_r2t_raw = NULL; + } + } + } + out: + return *transp ? 0 : -1; +} + + +int selinux_raw_context_to_color(const char * raw, char **transp) +{ + if (!raw) { + *transp = NULL; + return -1; + } + + __selinux_once(once, init_context_translations); + init_thread_destructor(); + + if (!has_setrans) { + *transp = strdup(raw); + goto out; + } + + if (prev_r2c_raw && strcmp(prev_r2c_raw, raw) == 0) { + *transp = strdup(prev_r2c_trans); + } else { + free(prev_r2c_raw); + prev_r2c_raw = NULL; + free(prev_r2c_trans); + prev_r2c_trans = NULL; + if (raw_context_to_color(raw, transp)) + return -1; + if (*transp) { + prev_r2c_raw = strdup(raw); + if (!prev_r2c_raw) + goto out; + prev_r2c_trans = strdup(*transp); + if (!prev_r2c_trans) { + free(prev_r2c_raw); + prev_r2c_raw = NULL; + } + } + } + out: + return *transp ? 0 : -1; +} + +#else /*DISABLE_SETRANS*/ + +int selinux_trans_to_raw_context(const char * trans, + char ** rawp) +{ + kbox_hack_p(0); + if (!trans) { + *rawp = NULL; + return 0; + } + + *rawp = strdup(trans); + + return *rawp ? 0 : -1; +} + + +int selinux_raw_to_trans_context(const char * raw, + char ** transp) +{ + if (!raw) { + *transp = NULL; + return 0; + } + *transp = strdup(raw); + + return *transp ? 0 : -1; +} + +#endif /*DISABLE_SETRANS*/ diff --git a/aosp/external/virglrenderer/Android.bp b/aosp/external/virglrenderer/Android.bp new file mode 100644 index 000000000..631e3aa51 --- /dev/null +++ b/aosp/external/virglrenderer/Android.bp @@ -0,0 +1,176 @@ +// +// Copyright (C) 2018 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. +// + +package { + default_applicable_licenses: ["external_virglrenderer_license"], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// +// large-scale-change included anything that looked like it might be a license +// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc. +// +// Please consider removing redundant or irrelevant files from 'license_text:'. +// See: http://go/android-license-faq +license { + name: "external_virglrenderer_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-BSD", + "SPDX-license-identifier-BSL-1.0", + "SPDX-license-identifier-MIT", + ], + license_text: [ + "COPYING", + "LICENSE", + "NOTICE", + ], +} + +cc_library_headers { + name: "virgl_headers", + host_supported: true, + export_include_dirs: ["src"], +} + +cc_library { + name: "libvirglrenderer", + host_supported: true, + cflags: [ + "-DHAVE_CONFIG_H", + "-include prebuilt-intermediates/config.h", + "-Wno-unused-parameter", + ], + local_include_dirs: [ + "prebuilt-intermediates", + "src", + "src/drm", + "src/gallium/auxiliary", + "src/gallium/auxiliary/util", + "src/gallium/include", + "src/mesa", + "src/mesa/compat", + "src/mesa/pipe", + "src/mesa/util", + "src/venus", + ], + srcs: [ + "prebuilt-intermediates/src/u_format_table.c", + "src/gallium/auxiliary/cso_cache/cso_cache.c", + "src/gallium/auxiliary/cso_cache/cso_hash.c", + "src/gallium/auxiliary/tgsi/tgsi_build.c", + "src/gallium/auxiliary/tgsi/tgsi_dump.c", + "src/gallium/auxiliary/tgsi/tgsi_info.c", + "src/gallium/auxiliary/tgsi/tgsi_iterate.c", + "src/gallium/auxiliary/tgsi/tgsi_parse.c", + "src/gallium/auxiliary/tgsi/tgsi_sanity.c", + "src/gallium/auxiliary/tgsi/tgsi_scan.c", + "src/gallium/auxiliary/tgsi/tgsi_strings.c", + "src/gallium/auxiliary/tgsi/tgsi_text.c", + "src/gallium/auxiliary/tgsi/tgsi_util.c", + "src/gallium/auxiliary/util/u_debug_describe.c", + "src/gallium/auxiliary/util/u_format.c", + "src/gallium/auxiliary/util/u_hash_table.c", + "src/gallium/auxiliary/util/u_texture.c", + "src/mesa/util/anon_file.c", + "src/mesa/util/bitscan.c", + "src/mesa/util/hash_table.c", + "src/mesa/util/os_file.c", + "src/mesa/util/os_misc.c", + "src/mesa/util/ralloc.c", + "src/mesa/util/u_cpu_detect.c", + "src/mesa/util/u_debug.c", + "src/mesa/util/u_math.c", + "src/iov.c", + "src/virgl_context.c", + "src/virglrenderer.c", + "src/virgl_resource.c", + "src/virgl_util.c", + "src/vrend_blitter.c", + "src/vrend_debug.c", + "src/vrend_decode.c", + "src/vrend_formats.c", + "src/vrend_object.c", + "src/vrend_renderer.c", + "src/vrend_shader.c", + "src/vrend_tweaks.c", + "src/vrend_winsys.c", + "src/vrend_winsys_egl.c", + "src/vrend_winsys_gbm.c", + ], + target: { + host_linux: { + shared_libs: [ + "libdrm", + "libepoxy", + ], + }, + linux_glibc: { + cflags: [ + "-Wno-#warnings", + // FIXME: Figure out how to get C protos for asprintf, + // pthread_setname_np and sched_getcpu from glibc sysroot + "-Wno-error=implicit-function-declaration", + ], + // Avoid linking to another host copy of libdrm; this library will cause + // binary GPU drivers to be loaded from the host, which might be linked + // to a system copy of libdrm, which conflicts with the AOSP one + allow_undefined_symbols: true, + header_libs: ["libdrm_headers"], + exclude_shared_libs: [ + "libdrm", + ], + }, + linux_bionic: { + cflags: [ + // Provide a C proto for memfd_create + "-D__USE_GNU", + "-DHAVE_MEMFD_CREATE=1", + ], + }, + android: { + cflags: [ + // Provide a C proto for memfd_create + "-D__USE_GNU", + "-DHAVE_MEMFD_CREATE=1", + ], + shared_libs: [ + "libcutils", + "libdrm", + "liblog", + ], + static_libs: [ + "libepoxy", + ], + }, + }, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], +} diff --git a/aosp/system/bpf/bpfloader/BpfLoader.cpp b/aosp/system/bpf/bpfloader/BpfLoader.cpp new file mode 100644 index 000000000..dbb24d3dc --- /dev/null +++ b/aosp/system/bpf/bpfloader/BpfLoader.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef LOG_TAG +#define LOG_TAG "bpfloader" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "BpfSyscallWrappers.h" +#include "bpf/BpfUtils.h" + +using android::base::EndsWith; +using android::bpf::domain; +using std::string; + +bool exists(const char* const path) { + int v = access(path, F_OK); + if (!v) { + ALOGI("%s exists.", path); + return true; + } + if (errno == ENOENT) return false; + ALOGE("FATAL: access(%s, F_OK) -> %d [%d:%s]", path, v, errno, strerror(errno)); + abort(); // can only hit this if permissions (likely selinux) are screwed up +} + +// Networking-related program types are limited to the Tethering Apex +// to prevent things from breaking due to conflicts on mainline updates +// (exception made for socket filters, ie. xt_bpf for potential use in iptables, +// or for attaching to sockets directly) +constexpr bpf_prog_type kPlatformAllowedProgTypes[] = { + BPF_PROG_TYPE_KPROBE, + BPF_PROG_TYPE_PERF_EVENT, + BPF_PROG_TYPE_SOCKET_FILTER, + BPF_PROG_TYPE_TRACEPOINT, + BPF_PROG_TYPE_UNSPEC, // Will be replaced with fuse bpf program type +}; + +constexpr bpf_prog_type kUprobestatsAllowedProgTypes[] = { + BPF_PROG_TYPE_KPROBE, +}; + +// see b/162057235. For arbitrary program types, the concern is that due to the lack of +// SELinux access controls over BPF program attachpoints, we have no way to control the +// attachment of programs to shared resources (or to detect when a shared resource +// has one BPF program replace another that is attached there) +constexpr bpf_prog_type kVendorAllowedProgTypes[] = { + BPF_PROG_TYPE_SOCKET_FILTER, +}; + +const android::bpf::Location locations[] = { + // Core operating system + { + .dir = "/system/etc/bpf/", + .prefix = "", + .allowedDomainBitmask = domainToBitmask(domain::platform), + .allowedProgTypes = kPlatformAllowedProgTypes, + .allowedProgTypesLength = arraysize(kPlatformAllowedProgTypes), + }, + // uprobestats + { + .dir = "/system/etc/bpf/uprobestats/", + .prefix = "uprobestats/", + .allowedDomainBitmask = domainToBitmask(domain::platform), + .allowedProgTypes = kUprobestatsAllowedProgTypes, + .allowedProgTypesLength = arraysize(kUprobestatsAllowedProgTypes), + }, + // Vendor operating system + { + .dir = "/vendor/etc/bpf/", + .prefix = "vendor/", + .allowedDomainBitmask = domainToBitmask(domain::vendor), + .allowedProgTypes = kVendorAllowedProgTypes, + .allowedProgTypesLength = arraysize(kVendorAllowedProgTypes), + }, +}; + +int loadAllElfObjects(const android::bpf::Location& location) { + int retVal = 0; + DIR* dir; + struct dirent* ent; + + if ((dir = opendir(location.dir)) != NULL) { + while ((ent = readdir(dir)) != NULL) { + string s = ent->d_name; + if (!EndsWith(s, ".o")) continue; + + string progPath(location.dir); + progPath += s; + + bool critical; + int ret = android::bpf::loadProg(progPath.c_str(), &critical, location); + if (ret) { + if (critical) retVal = ret; + ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret)); + } else { + ALOGI("Loaded object: %s", progPath.c_str()); + } + } + closedir(dir); + } + return retVal; +} + +int createSysFsBpfSubDir(const char* const prefix) { + if (*prefix) { + mode_t prevUmask = umask(0); + + string s = "/sys/fs/bpf/"; + s += prefix; + + errno = 0; + int ret = mkdir(s.c_str(), S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); + if (ret && errno != EEXIST) { + const int err = errno; + ALOGE("Failed to create directory: %s, ret: %s", s.c_str(), std::strerror(err)); + return -err; + } + + umask(prevUmask); + } + return 0; +} + +// Technically 'value' doesn't need to be newline terminated, but it's best +// to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour, +// which is usually how kernel devs test the actual sysctl interfaces. +int writeProcSysFile(const char *filename, const char *value) { + android::base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC)); + if (fd < 0) { + const int err = errno; + ALOGE("open('%s', O_WRONLY | O_CLOEXEC) -> %s", filename, strerror(err)); + return -err; + } + int len = strlen(value); + int v = write(fd, value, len); + if (v < 0) { + const int err = errno; + ALOGE("write('%s', '%s', %d) -> %s", filename, value, len, strerror(err)); + return -err; + } + if (v != len) { + // In practice, due to us only using this for /proc/sys/... files, this can't happen. + ALOGE("write('%s', '%s', %d) -> short write [%d]", filename, value, len, v); + return -EINVAL; + } + return 0; +} + +int main(int argc, char** argv) { + (void)argc; + android::base::InitLogging(argv, &android::base::KernelLogger); + + // Create all the pin subdirectories + // (this must be done first to allow selinux_context and pin_subdir functionality, + // which could otherwise fail with ENOENT during object pinning or renaming, + // due to ordering issues) + for (const auto& location : locations) { + if (createSysFsBpfSubDir(location.prefix)) return 1; + } + + // Load all ELF objects, create programs and maps, and pin them + for (const auto& location : locations) { + if (loadAllElfObjects(location) != 0) { + ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS FROM %s ===", location.dir); + ALOGE("If this triggers reliably, you're probably missing kernel options or patches."); + ALOGE("If this triggers randomly, you might be hitting some memory allocation " + "problems or startup script race."); + ALOGE("--- DO NOT EXPECT SYSTEM TO BOOT SUCCESSFULLY ---"); +#if 0 + sleep(20); + return 2; +#endif + } + } + + if (android::base::SetProperty("bpf.progs_loaded", "1") == false) { + ALOGE("Failed to set bpf.progs_loaded property"); + return 1; + } + + return 0; +} diff --git a/aosp/system/core/fs_mgr/Android.bp b/aosp/system/core/fs_mgr/Android.bp new file mode 100644 index 000000000..3f1e9a9c8 --- /dev/null +++ b/aosp/system/core/fs_mgr/Android.bp @@ -0,0 +1,242 @@ +// +// Copyright (C) 2017 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. +// + +package { + default_applicable_licenses: [ + "Android-Apache-2.0", + "system_core_fs_mgr_license", + ], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// See: http://go/android-license-faq +license { + name: "system_core_fs_mgr_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-MIT", + ], + license_text: ["NOTICE"], +} + +cc_defaults { + name: "fs_mgr_defaults", + sanitize: { + misc_undefined: ["integer"], + }, + cflags: [ + "-Wall", + "-Werror", + ], +} + +cc_defaults { + name: "libfs_mgr_defaults", + defaults: ["fs_mgr_defaults"], + export_include_dirs: ["include"], + local_include_dirs: ["include/"], + cflags: [ + "-D_FILE_OFFSET_BITS=64", + ], + srcs: [ + "blockdev.cpp", + "file_wait.cpp", + "fs_mgr.cpp", + "fs_mgr_format.cpp", + "fs_mgr_dm_linear.cpp", + "fs_mgr_roots.cpp", + "fs_mgr_overlayfs_control.cpp", + "fs_mgr_overlayfs_mount.cpp", + "fs_mgr_vendor_overlay.cpp", + ":libfiemap_srcs", + ], + shared_libs: [ + "libbase", + "libcrypto", + "libcrypto_utils", + "libcutils", + "libext4_utils", + "libfec", + "liblog", + "liblp", + "libselinux", + ], + static_libs: [ + "libavb", + "libfs_avb", + "libgsi", + ], + export_static_lib_headers: [ + "libfs_avb", + "libfstab", + "libdm", + ], + export_shared_lib_headers: [ + "liblp", + ], + whole_static_libs: [ + "liblogwrap", + "libdm", + "libext2_uuid", + "libfscrypt", + "libfstab", + ], + cppflags: [ + "-UALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_DISABLE_VERITY=1", + ], + product_variables: { + debuggable: { + cppflags: [ + "-UALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_DISABLE_VERITY=1", + ], + }, + }, + header_libs: [ + "libfiemap_headers", + "libstorage_literals_headers", + ], + export_header_lib_headers: [ + "libfiemap_headers", + ], + target: { + platform: { + required: [ + "e2freefrag", + "e2fsdroid", + ], + }, + recovery: { + required: [ + "e2fsdroid.recovery", + ], + }, + }, +} + +// Two variants of libfs_mgr are provided: libfs_mgr and libfs_mgr_binder. +// Use libfs_mgr in recovery, first-stage-init, or when libfiemap or overlayfs +// is not used. +// +// Use libfs_mgr_binder when not in recovery/first-stage init, or when overlayfs +// or libfiemap is needed. In this case, libfiemap will proxy over binder to +// gsid. +cc_library { + // Do not ever allow this library to be vendor_available as a shared library. + // It does not have a stable interface. + name: "libfs_mgr", + ramdisk_available: true, + vendor_ramdisk_available: true, + recovery_available: true, + defaults: [ + "libfs_mgr_defaults", + ], + srcs: [ + ":libfiemap_passthrough_srcs", + ], +} + +cc_library { + // Do not ever allow this library to be vendor_available as a shared library. + // It does not have a stable interface. + name: "libfs_mgr_binder", + defaults: [ + "libfs_mgr_defaults", + "libfiemap_binder_defaults", + ], +} + +cc_library_static { + name: "libfs_mgr_file_wait", + defaults: ["fs_mgr_defaults"], + export_include_dirs: ["include"], + cflags: [ + "-D_FILE_OFFSET_BITS=64", + ], + srcs: [ + "file_wait.cpp", + ], + shared_libs: [ + "libbase", + ], + host_supported: true, + ramdisk_available: true, + vendor_ramdisk_available: true, + recovery_available: true, +} + +cc_binary { + name: "remount", + defaults: ["fs_mgr_defaults"], + static_libs: [ + "libavb_user", + "libgsid", + "libvold_binder", + ], + shared_libs: [ + "libbootloader_message", + "libbase", + "libbinder", + "libcutils", + "libcrypto", + "libext4_utils", + "libfs_mgr_binder", + "liblog", + "liblp", + "libselinux", + "libutils", + ], + header_libs: [ + "libcutils_headers", + ], + srcs: [ + "fs_mgr_remount.cpp", + ], + cppflags: [ + "-UALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_DISABLE_VERITY=1", + ], + product_variables: { + debuggable: { + cppflags: [ + "-UALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_DISABLE_VERITY=1", + ], + init_rc: [ + "clean_scratch_files.rc", + ], + }, + }, + symlinks: [ + "clean_scratch_files", + "disable-verity", + "enable-verity", + "set-verity-state", + ], +} diff --git a/aosp/system/core/init/Android.bp b/aosp/system/core/init/Android.bp new file mode 100644 index 000000000..71d1f76f9 --- /dev/null +++ b/aosp/system/core/init/Android.bp @@ -0,0 +1,643 @@ +// +// Copyright (C) 2017 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. +// + +package { + default_applicable_licenses: ["system_core_init_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "system_core_init_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + +init_common_sources = [ + "action.cpp", + "action_manager.cpp", + "action_parser.cpp", + "capabilities.cpp", + "epoll.cpp", + "import_parser.cpp", + "interface_utils.cpp", + "interprocess_fifo.cpp", + "keychords.cpp", + "parser.cpp", + "property_type.cpp", + "rlimit_parser.cpp", + "service.cpp", + "service_list.cpp", + "service_parser.cpp", + "service_utils.cpp", + "subcontext.cpp", + "subcontext.proto", + "tokenizer.cpp", + "util.cpp", +] +init_device_sources = [ + "apex_init_util.cpp", + "block_dev_initializer.cpp", + "bootchart.cpp", + "builtins.cpp", + "devices.cpp", + "firmware_handler.cpp", + "first_stage_console.cpp", + "first_stage_init.cpp", + "first_stage_mount.cpp", + "fscrypt_init_extensions.cpp", + "init.cpp", + "lmkd_service.cpp", + "modalias_handler.cpp", + "mount_handler.cpp", + "mount_namespace.cpp", + "persistent_properties.cpp", + "persistent_properties.proto", + "property_service.cpp", + "property_service.proto", + "reboot.cpp", + "reboot_utils.cpp", + "security.cpp", + "selabel.cpp", + "selinux.cpp", + "sigchld_handler.cpp", + "snapuserd_transition.cpp", + "switch_root.cpp", + "uevent_listener.cpp", + "ueventd.cpp", + "ueventd_parser.cpp", +] +init_host_sources = [ + "check_builtins.cpp", + "host_import_parser.cpp", +] + +soong_config_module_type { + name: "libinit_cc_defaults", + module_type: "cc_defaults", + config_namespace: "ANDROID", + bool_variables: [ + "PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT", + ], + properties: [ + "cflags", + ], +} + +libinit_cc_defaults { + name: "init_defaults", + sanitize: { + misc_undefined: ["signed-integer-overflow"], + }, + cflags: [ + "-DALLOW_FIRST_STAGE_CONSOLE=0", + "-DALLOW_LOCAL_PROP_OVERRIDE=0", + "-DALLOW_PERMISSIVE_SELINUX=0", + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", + "-DDUMP_ON_UMOUNT_FAILURE=0", + "-DINIT_FULL_SOURCES", + "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=0", + "-DLOG_UEVENTS=0", + "-DREBOOT_BOOTLOADER_ON_PANIC=0", + "-DSHUTDOWN_ZERO_TIMEOUT=0", + "-DWORLD_WRITABLE_KMSG=0", + "-Wall", + "-Werror", + "-Wextra", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + product_variables: { + debuggable: { + cppflags: [ + "-UALLOW_FIRST_STAGE_CONSOLE", + "-DALLOW_FIRST_STAGE_CONSOLE=1", + "-UALLOW_LOCAL_PROP_OVERRIDE", + "-DALLOW_LOCAL_PROP_OVERRIDE=1", + "-UALLOW_PERMISSIVE_SELINUX", + "-DALLOW_PERMISSIVE_SELINUX=1", + "-UREBOOT_BOOTLOADER_ON_PANIC", + "-DREBOOT_BOOTLOADER_ON_PANIC=1", + "-UWORLD_WRITABLE_KMSG", + "-DWORLD_WRITABLE_KMSG=1", + "-UDUMP_ON_UMOUNT_FAILURE", + "-DDUMP_ON_UMOUNT_FAILURE=1", + ], + }, + eng: { + cppflags: [ + "-USHUTDOWN_ZERO_TIMEOUT", + "-DSHUTDOWN_ZERO_TIMEOUT=1", + ], + }, + uml: { + cppflags: ["-DUSER_MODE_LINUX"], + }, + }, + soong_config_variables: { + PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT: { + cflags: [ + "-UINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT", + "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=1", + ], + }, + }, + static_libs: [ + "libavb", + "libbootloader_message", + "libc++fs", + "libcgrouprc_format", + "liblmkd_utils", + "liblz4", + "libzstd", + "libmodprobe", + "libprocinfo", + "libprotobuf-cpp-lite", + "libpropertyinfoserializer", + "libpropertyinfoparser", + "libsnapshot_cow", + "libsnapshot_init", + "libxml2", + "lib_apex_manifest_proto_lite", + "update_metadata-protos", + ], + shared_libs: [ + "libbase", + "libcutils", + "libdl", + "libext4_utils", + "libfs_mgr", + "libgsi", + "libhidl-gen-utils", + "liblog", + "liblogwrap", + "liblp", + "libprocessgroup", + "libprocessgroup_setup", + "libselinux", + "libunwindstack", + "libutils", + "libvendorsupport", + ], + header_libs: ["bionic_libc_platform_headers"], + bootstrap: true, + visibility: [":__subpackages__"], +} + +cc_library_headers { + name: "libinit_headers", + export_include_dirs: ["."], + visibility: [":__subpackages__"], +} + +cc_defaults { + name: "libinit_defaults", + recovery_available: true, + defaults: [ + "init_defaults", + "selinux_policy_version", + ], + srcs: init_common_sources + init_device_sources, + export_include_dirs: ["."], + generated_sources: [ + "apex-info-list", + ], + whole_static_libs: [ + "libcap", + "libcom.android.sysprop.init", + ], + header_libs: ["bootimg_headers"], + proto: { + type: "lite", + export_proto_headers: true, + }, + + cflags: [ + "-Wno-unused-argument", + "-Wno-unused-function", + ], + + target: { + recovery: { + cflags: ["-DRECOVERY"], + exclude_static_libs: [ + "libxml2", + ], + exclude_generated_sources: [ + "apex-info-list", + ], + exclude_shared_libs: [ + "libbinder", + "libutils", + ], + }, + }, +} + +cc_library_static { + name: "libinit", + defaults: ["libinit_defaults"], +} + +cc_library_static { + name: "libinit.microdroid", + defaults: ["libinit_defaults"], + cflags: ["-DMICRODROID=1"], +} + +phony { + name: "init", + required: [ + "init_second_stage", + ], +} + +cc_defaults { + name: "init_second_stage_defaults", + recovery_available: true, + stem: "init", + defaults: ["init_defaults"], + srcs: ["main.cpp"], + symlinks: ["ueventd"], + target: { + platform: { + required: [ + "init.rc", + "ueventd.rc", + "e2fsdroid", + "extra_free_kbytes", + "make_f2fs", + "mke2fs", + "sload_f2fs", + ], + }, + recovery: { + cflags: ["-DRECOVERY"], + exclude_static_libs: [ + "libxml2", + ], + exclude_shared_libs: [ + "libbinder", + "libutils", + ], + required: [ + "init_recovery.rc", + "ueventd.rc.recovery", + "e2fsdroid.recovery", + "make_f2fs.recovery", + "mke2fs.recovery", + "sload_f2fs.recovery", + ], + }, + }, +} + +cc_binary { + name: "init_second_stage", + defaults: ["init_second_stage_defaults"], + static_libs: ["libinit"], +} + +cc_binary { + name: "init_second_stage.microdroid", + defaults: ["init_second_stage_defaults"], + static_libs: ["libinit.microdroid"], + cflags: ["-DMICRODROID=1"], + installable: false, + visibility: ["//packages/modules/Virtualization/microdroid"], +} + +soong_config_module_type { + name: "init_first_stage_cc_defaults", + module_type: "cc_defaults", + config_namespace: "ANDROID", + bool_variables: ["BOARD_USES_RECOVERY_AS_BOOT"], + properties: ["installable"], +} + +// Do not install init_first_stage even with mma if we're system-as-root. +// Otherwise, it will overwrite the symlink. +init_first_stage_cc_defaults { + name: "init_first_stage_defaults", + soong_config_variables: { + BOARD_USES_RECOVERY_AS_BOOT: { + installable: false, + }, + }, + + stem: "init", + + srcs: [ + "block_dev_initializer.cpp", + "devices.cpp", + "first_stage_console.cpp", + "first_stage_init.cpp", + "first_stage_main.cpp", + "first_stage_mount.cpp", + "reboot_utils.cpp", + "selabel.cpp", + "service_utils.cpp", + "snapuserd_transition.cpp", + "switch_root.cpp", + "uevent_listener.cpp", + "util.cpp", + ], + + static_libs: [ + "libc++fs", + "libfs_avb", + "libfs_mgr", + "libfec", + "libfec_rs", + "libsquashfs_utils", + "libcrypto_utils", + "libavb", + "liblp", + "libcutils", + "libbase", + "liblog", + "libcrypto_static", + "libselinux", + "libcap", + "libgsi", + "liblzma", + "libunwindstack_no_dex", + "libmodprobe", + "libext2_uuid", + "libprotobuf-cpp-lite", + "libsnapshot_cow", + "liblz4", + "libzstd", + "libsnapshot_init", + "update_metadata-protos", + "libprocinfo", + ], + + static_executable: true, + system_shared_libs: [], + + cflags: [ + "-Wall", + "-Wextra", + "-Wno-unused-parameter", + "-Werror", + "-DALLOW_FIRST_STAGE_CONSOLE=0", + "-DALLOW_LOCAL_PROP_OVERRIDE=0", + "-DALLOW_PERMISSIVE_SELINUX=0", + "-DREBOOT_BOOTLOADER_ON_PANIC=0", + "-DWORLD_WRITABLE_KMSG=0", + "-DDUMP_ON_UMOUNT_FAILURE=0", + "-DSHUTDOWN_ZERO_TIMEOUT=0", + "-DLOG_UEVENTS=0", + "-DSEPOLICY_VERSION=30", // TODO(jiyong): externalize the version number + ], + + product_variables: { + debuggable: { + cflags: [ + "-UALLOW_FIRST_STAGE_CONSOLE", + "-DALLOW_FIRST_STAGE_CONSOLE=1", + + "-UALLOW_LOCAL_PROP_OVERRIDE", + "-DALLOW_LOCAL_PROP_OVERRIDE=1", + + "-UALLOW_PERMISSIVE_SELINUX", + "-DALLOW_PERMISSIVE_SELINUX=1", + + "-UREBOOT_BOOTLOADER_ON_PANIC", + "-DREBOOT_BOOTLOADER_ON_PANIC=1", + + "-UWORLD_WRITABLE_KMSG", + "-DWORLD_WRITABLE_KMSG=1", + + "-UDUMP_ON_UMOUNT_FAILURE", + "-DDUMP_ON_UMOUNT_FAILURE=1", + ], + }, + + eng: { + cflags: [ + "-USHUTDOWN_ZERO_TIMEOUT", + "-DSHUTDOWN_ZERO_TIMEOUT=1", + ], + }, + }, + + sanitize: { + misc_undefined: ["signed-integer-overflow"], + + // First stage init is weird: it may start without stdout/stderr, and no /proc. + hwaddress: false, + }, + + // Install adb_debug.prop into debug ramdisk. + // This allows adb root on a user build, when debug ramdisk is used. + required: ["adb_debug.prop"], + + ramdisk: true, + + install_in_root: true, +} + +cc_binary { + name: "init_first_stage", + defaults: ["init_first_stage_defaults"], +} + +cc_binary { + name: "init_first_stage.microdroid", + defaults: ["init_first_stage_defaults"], + cflags: ["-DMICRODROID=1"], + installable: false, +} + +phony { + name: "init_system", + required: ["init_second_stage"], +} + +// Tests +// ------------------------------------------------------------------------------ + +cc_test { + // Note: This is NOT a CTS test. See b/320800872 + name: "CtsInitTestCases", + defaults: ["init_defaults"], + require_root: true, + + compile_multilib: "first", + + srcs: [ + "devices_test.cpp", + "epoll_test.cpp", + "firmware_handler_test.cpp", + "init_test.cpp", + "interprocess_fifo_test.cpp", + "keychords_test.cpp", + "oneshot_on_test.cpp", + "persistent_properties_test.cpp", + "property_service_test.cpp", + "property_type_test.cpp", + "reboot_test.cpp", + "rlimit_parser_test.cpp", + "service_test.cpp", + "subcontext_test.cpp", + "tokenizer_test.cpp", + "ueventd_parser_test.cpp", + "ueventd_test.cpp", + "util_test.cpp", + ], + static_libs: [ + "libgmock", + "libinit", + ], + + test_suites: [ + "device-tests", + ], +} + +cc_benchmark { + name: "init_benchmarks", + defaults: ["init_defaults"], + srcs: [ + "subcontext_benchmark.cpp", + ], + static_libs: ["libinit"], +} + +cc_defaults { + name: "libinit_test_utils_libraries_defaults", + shared_libs: [ + "libbase", + "libcutils", + "libselinux", + "liblog", + "libprocessgroup", + "libprotobuf-cpp-lite", + ], + static_libs: [ + "libfs_mgr", + "libhidl-gen-utils", + ], +} + +cc_library_static { + name: "libinit_test_utils", + defaults: ["libinit_test_utils_libraries_defaults"], + cflags: [ + "-Wall", + "-Wextra", + "-Wno-unused-parameter", + "-Werror", + ], + srcs: init_common_sources + [ + "test_utils/service_utils.cpp", + ], + whole_static_libs: [ + "libcap", + ], + export_include_dirs: ["test_utils/include"], // for tests + header_libs: ["bionic_libc_platform_headers"], +} + +// Host Verifier +// ------------------------------------------------------------------------------ + +genrule { + name: "generated_stub_builtin_function_map", + tool_files: ["host_builtin_map.py"], + out: ["generated_stub_builtin_function_map.h"], + srcs: [ + "builtins.cpp", + "check_builtins.cpp", + ], + cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)", +} + +cc_defaults { + name: "init_host_defaults", + host_supported: true, + cflags: [ + "-Wall", + "-Wextra", + "-Wno-unused-parameter", + "-Werror", + ], + static_libs: [ + "libbase", + "libselinux", + "libpropertyinfoserializer", + "libpropertyinfoparser", + ], + whole_static_libs: ["libcap"], + shared_libs: [ + "libcutils", + "libhidl-gen-utils", + "libhidlmetadata", + "liblog", + "libprocessgroup", + "libprotobuf-cpp-lite", + ], + proto: { + type: "lite", + }, + generated_headers: [ + "generated_stub_builtin_function_map", + ], + target: { + android: { + enabled: false, + }, + darwin: { + enabled: false, + }, + }, +} + +cc_binary { + name: "host_init_verifier", + defaults: ["init_host_defaults"], + srcs: ["host_init_verifier.cpp"] + init_common_sources + init_host_sources, + generated_headers: [ + "generated_android_ids", + ], +} + +cc_library_host_static { + name: "libinit_host", + defaults: ["init_host_defaults"], + srcs: init_common_sources + init_host_sources, + export_include_dirs: ["."], + proto: { + export_proto_headers: true, + }, + visibility: [ + // host_apex_verifier performs a subset of init.rc validation + "//system/apex/tools", + ], +} + +sh_binary { + name: "extra_free_kbytes", + src: "extra_free_kbytes.sh", + filename_from_src: true, +} diff --git a/aosp/system/core/init/devices.cpp b/aosp/system/core/init/devices.cpp new file mode 100644 index 000000000..a58a393d1 --- /dev/null +++ b/aosp/system/core/init/devices.cpp @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2007 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. + */ + +#include "devices.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "selabel.h" +#include "util.h" + +using namespace std::chrono_literals; + +using android::base::Basename; +using android::base::Dirname; +using android::base::ReadFileToString; +using android::base::Readlink; +using android::base::Realpath; +using android::base::Split; +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::Trim; + +namespace android { +namespace init { + +/* Given a path that may start with a PCI device, populate the supplied buffer + * with the PCI domain/bus number and the peripheral ID and return 0. + * If it doesn't start with a PCI device, or there is some error, return -1 */ +static bool FindPciDevicePrefix(const std::string& path, std::string* result) { + result->clear(); + + if (!StartsWith(path, "/devices/pci")) return false; + + /* Beginning of the prefix is the initial "pci" after "/devices/" */ + std::string::size_type start = 9; + + /* End of the prefix is two path '/' later, capturing the domain/bus number + * and the peripheral ID. Example: pci0000:00/0000:00:1f.2 */ + auto end = path.find('/', start); + if (end == std::string::npos) return false; + + end = path.find('/', end + 1); + if (end == std::string::npos) return false; + + auto length = end - start; + if (length <= 4) { + // The minimum string that will get to this check is 'pci/', which is malformed, + // so return false + return false; + } + + *result = path.substr(start, length); + return true; +} + +/* Given a path that may start with a virtual block device, populate + * the supplied buffer with the virtual block device ID and return 0. + * If it doesn't start with a virtual block device, or there is some + * error, return -1 */ +static bool FindVbdDevicePrefix(const std::string& path, std::string* result) { + result->clear(); + + if (!StartsWith(path, "/devices/vbd-")) return false; + + /* Beginning of the prefix is the initial "vbd-" after "/devices/" */ + std::string::size_type start = 13; + + /* End of the prefix is one path '/' later, capturing the + virtual block device ID. Example: 768 */ + auto end = path.find('/', start); + if (end == std::string::npos) return false; + + auto length = end - start; + if (length == 0) return false; + + *result = path.substr(start, length); + return true; +} + +// Given a path that may start with a virtual dm block device, populate +// the supplied buffer with the dm module's instantiated name. +// If it doesn't start with a virtual block device, or there is some +// error, return false. +static bool FindDmDevice(const Uevent& uevent, std::string* name, std::string* uuid) { + if (!StartsWith(uevent.path, "/devices/virtual/block/dm-")) return false; + if (uevent.action == "remove") return false; // Avoid error spam from ioctl + + dev_t dev = makedev(uevent.major, uevent.minor); + + auto& dm = android::dm::DeviceMapper::Instance(); + return dm.GetDeviceNameAndUuid(dev, name, uuid); +} + +Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid, + bool no_fnm_pathname) + : name_(name), + perm_(perm), + uid_(uid), + gid_(gid), + prefix_(false), + wildcard_(false), + no_fnm_pathname_(no_fnm_pathname) { + // Set 'prefix_' or 'wildcard_' based on the below cases: + // + // 1) No '*' in 'name' -> Neither are set and Match() checks a given path for strict + // equality with 'name' + // + // 2) '*' only appears as the last character in 'name' -> 'prefix'_ is set to true and + // Match() checks if 'name' is a prefix of a given path. + // + // 3) '*' appears elsewhere -> 'wildcard_' is set to true and Match() uses fnmatch() + // with FNM_PATHNAME to compare 'name' to a given path. + auto wildcard_position = name_.find('*'); + if (wildcard_position != std::string::npos) { + if (wildcard_position == name_.length() - 1) { + prefix_ = true; + name_.pop_back(); + } else { + wildcard_ = true; + } + } +} + +bool Permissions::Match(const std::string& path) const { + if (prefix_) return StartsWith(path, name_); + if (wildcard_) + return fnmatch(name_.c_str(), path.c_str(), no_fnm_pathname_ ? 0 : FNM_PATHNAME) == 0; + return path == name_; +} + +bool SysfsPermissions::MatchWithSubsystem(const std::string& path, + const std::string& subsystem) const { + std::string path_basename = Basename(path); + if (name().find(subsystem) != std::string::npos) { + if (Match("/sys/class/" + subsystem + "/" + path_basename)) return true; + if (Match("/sys/bus/" + subsystem + "/devices/" + path_basename)) return true; + } + return Match(path); +} + +void SysfsPermissions::SetPermissions(const std::string& path) const { + std::string attribute_file = path + "/" + attribute_; + LOG(VERBOSE) << "fixup " << attribute_file << " " << uid() << " " << gid() << " " << std::oct + << perm(); + + if (access(attribute_file.c_str(), F_OK) == 0) { + if (chown(attribute_file.c_str(), uid(), gid()) != 0) { + PLOG(ERROR) << "chown(" << attribute_file << ", " << uid() << ", " << gid() + << ") failed"; + } + if (chmod(attribute_file.c_str(), perm()) != 0) { + PLOG(ERROR) << "chmod(" << attribute_file << ", " << perm() << ") failed"; + } + } +} + +std::string DeviceHandler::GetPartitionNameForDevice(const std::string& query_device) { + static const auto partition_map = [] { + std::vector> partition_map; + auto parser = [&partition_map](const std::string& key, const std::string& value) { + if (key != "androidboot.partition_map") { + return; + } + for (const auto& map : Split(value, ";")) { + auto map_pieces = Split(map, ","); + if (map_pieces.size() != 2) { + LOG(ERROR) << "Expected a comma separated device,partition mapping, but found '" + << map << "'"; + continue; + } + partition_map.emplace_back(map_pieces[0], map_pieces[1]); + } + }; + android::fs_mgr::ImportKernelCmdline(parser); + android::fs_mgr::ImportBootconfig(parser); + return partition_map; + }(); + + for (const auto& [device, partition] : partition_map) { + if (query_device == device) { + return partition; + } + } + return {}; +} + +// Given a path that may start with a platform device, find the parent platform device by finding a +// parent directory with a 'subsystem' symlink that points to the platform bus. +// If it doesn't start with a platform device, return false +bool DeviceHandler::FindPlatformDevice(std::string path, std::string* platform_device_path) const { + platform_device_path->clear(); + + // Uevents don't contain the mount point, so we need to add it here. + path.insert(0, sysfs_mount_point_); + + std::string directory = Dirname(path); + + while (directory != "/" && directory != ".") { + std::string subsystem_link_path; + if (Realpath(directory + "/subsystem", &subsystem_link_path) && + (subsystem_link_path == sysfs_mount_point_ + "/bus/platform" || + subsystem_link_path == sysfs_mount_point_ + "/bus/amba")) { + // We need to remove the mount point that we added above before returning. + directory.erase(0, sysfs_mount_point_.size()); + *platform_device_path = directory; + return true; + } + + auto last_slash = path.rfind('/'); + if (last_slash == std::string::npos) return false; + + path.erase(last_slash); + directory = Dirname(path); + } + + return false; +} + +void DeviceHandler::FixupSysPermissions(const std::string& upath, + const std::string& subsystem) const { + // upaths omit the "/sys" that paths in this list + // contain, so we prepend it... + std::string path = "/sys" + upath; + + for (const auto& s : sysfs_permissions_) { + if (s.MatchWithSubsystem(path, subsystem)) s.SetPermissions(path); + } + + if (!skip_restorecon_ && access(path.c_str(), F_OK) == 0) { + LOG(VERBOSE) << "restorecon_recursive: " << path; + if (selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) { + PLOG(ERROR) << "selinux_android_restorecon(" << path << ") failed"; + } + } +} + +std::tuple DeviceHandler::GetDevicePermissions( + const std::string& path, const std::vector& links) const { + // Search the perms list in reverse so that ueventd.$hardware can override ueventd.rc. + for (auto it = dev_permissions_.crbegin(); it != dev_permissions_.crend(); ++it) { + if (it->Match(path) || std::any_of(links.cbegin(), links.cend(), + [it](const auto& link) { return it->Match(link); })) { + return {it->perm(), it->uid(), it->gid()}; + } + } + /* Default if nothing found. */ + return {0600, 0, 0}; +} + +void DeviceHandler::MakeDevice(const std::string& path, bool block, int major, int minor, + const std::vector& links) const { + auto[mode, uid, gid] = GetDevicePermissions(path, links); + mode |= (block ? S_IFBLK : S_IFCHR); + + std::string secontext; + if (!SelabelLookupFileContextBestMatch(path, links, mode, &secontext)) { + PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; + return; + } + if (!secontext.empty()) { + setfscreatecon(secontext.c_str()); + } + + gid_t new_group = -1; + + dev_t dev = makedev(major, minor); + /* Temporarily change egid to avoid race condition setting the gid of the + * device node. Unforunately changing the euid would prevent creation of + * some device nodes, so the uid has to be set with chown() and is still + * racy. Fixing the gid race at least fixed the issue with system_server + * opening dynamic input devices under the AID_INPUT gid. */ + if (setegid(gid)) { + PLOG(ERROR) << "setegid(" << gid << ") for " << path << " device failed"; + goto out; + } + /* If the node already exists update its SELinux label and the file mode to handle cases when + * it was created with the wrong context and file mode during coldboot procedure. */ + if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && !secontext.empty()) { + char* fcon = nullptr; + int rc = lgetfilecon(path.c_str(), &fcon); + if (rc < 0) { + PLOG(ERROR) << "Cannot get SELinux label on '" << path << "' device"; + goto out; + } + + bool different = fcon != secontext; + freecon(fcon); + + if (different && lsetfilecon(path.c_str(), secontext.c_str())) { + PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path + << "' device"; + } + + struct stat s; + if (stat(path.c_str(), &s) == 0) { + if (gid != s.st_gid) { + new_group = gid; + } + if (mode != s.st_mode) { + if (chmod(path.c_str(), mode) != 0) { + PLOG(ERROR) << "Cannot chmod " << path << " to " << mode; + } + } + } else { + PLOG(ERROR) << "Cannot stat " << path; + } + } + +out: + if (chown(path.c_str(), uid, new_group) < 0) { + PLOG(ERROR) << "Cannot chown " << path << " " << uid << " " << new_group; + } + if (setegid(AID_ROOT)) { + PLOG(FATAL) << "setegid(AID_ROOT) failed"; + } + + if (!secontext.empty()) { + setfscreatecon(nullptr); + } +} + +// replaces any unacceptable characters with '_', the +// length of the resulting string is equal to the input string +void SanitizePartitionName(std::string* string) { + const char* accept = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "_-."; + + if (!string) return; + + std::string::size_type pos = 0; + while ((pos = string->find_first_not_of(accept, pos)) != std::string::npos) { + (*string)[pos] = '_'; + } +} + +std::vector DeviceHandler::GetBlockDeviceSymlinks(const Uevent& uevent) const { + std::string device; + std::string type; + std::string partition; + std::string uuid; + + if (FindPlatformDevice(uevent.path, &device)) { + // Skip /devices/platform or /devices/ if present + static const std::string devices_platform_prefix = "/devices/platform/"; + static const std::string devices_prefix = "/devices/"; + + if (StartsWith(device, devices_platform_prefix)) { + device = device.substr(devices_platform_prefix.length()); + } else if (StartsWith(device, devices_prefix)) { + device = device.substr(devices_prefix.length()); + } + + type = "platform"; + } else if (FindPciDevicePrefix(uevent.path, &device)) { + type = "pci"; + } else if (FindVbdDevicePrefix(uevent.path, &device)) { + type = "vbd"; + } else if (FindDmDevice(uevent, &partition, &uuid)) { + std::vector symlinks = {"/dev/block/mapper/" + partition}; + if (!uuid.empty()) { + symlinks.emplace_back("/dev/block/mapper/by-uuid/" + uuid); + } + return symlinks; + } else { + return {}; + } + + std::vector links; + + LOG(VERBOSE) << "found " << type << " device " << device; + + auto link_path = "/dev/block/" + type + "/" + device; + + bool is_boot_device = boot_devices_.find(device) != boot_devices_.end(); + if (!uevent.partition_name.empty()) { + std::string partition_name_sanitized(uevent.partition_name); + SanitizePartitionName(&partition_name_sanitized); + if (partition_name_sanitized != uevent.partition_name) { + LOG(VERBOSE) << "Linking partition '" << uevent.partition_name << "' as '" + << partition_name_sanitized << "'"; + } + links.emplace_back(link_path + "/by-name/" + partition_name_sanitized); + // Adds symlink: /dev/block/by-name/. + if (is_boot_device) { + links.emplace_back("/dev/block/by-name/" + partition_name_sanitized); + } + } else if (is_boot_device) { + // If we don't have a partition name but we are a partition on a boot device, create a + // symlink of /dev/block/by-name/ for symmetry. + links.emplace_back("/dev/block/by-name/" + uevent.device_name); + auto partition_name = GetPartitionNameForDevice(uevent.device_name); + if (!partition_name.empty()) { + links.emplace_back("/dev/block/by-name/" + partition_name); + } + } + + std::string model; + if (ReadFileToString("/sys/class/block/" + uevent.device_name + "/queue/zoned", &model) && + !StartsWith(model, "none")) { + links.emplace_back("/dev/block/by-name/zoned_device"); + } + + auto last_slash = uevent.path.rfind('/'); + links.emplace_back(link_path + "/" + uevent.path.substr(last_slash + 1)); + + return links; +} + +void DeviceHandler::chinit_dev(const char *path) +{ + std::vector links; + auto[mode, uid, gid] = GetDevicePermissions(path, links); + // NOTICE("chinit_dev %s %d %d 0%o\n", path, uid, gid, mode); + chown(path, uid, gid); + chmod(path, mode); +} + +static void RemoveDeviceMapperLinks(const std::string& devpath) { + std::vector dirs = { + "/dev/block/mapper", + "/dev/block/mapper/by-uuid", + }; + for (const auto& dir : dirs) { + if (access(dir.c_str(), F_OK) != 0) continue; + + std::unique_ptr dh(opendir(dir.c_str()), closedir); + if (!dh) { + PLOG(ERROR) << "Failed to open directory " << dir; + continue; + } + + struct dirent* dp; + std::string link_path; + while ((dp = readdir(dh.get())) != nullptr) { + if (dp->d_type != DT_LNK) continue; + + auto path = dir + "/" + dp->d_name; + if (Readlink(path, &link_path) && link_path == devpath) { + unlink(path.c_str()); + } + } + } +} + +void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, bool block, + int major, int minor, const std::vector& links) const { + std::string temp = devpath.substr(0, 11); + if ((temp.compare("/dev/input/")) == 0) { + return; + } + + if (action == "add") { + MakeDevice(devpath, block, major, minor, links); + } + + // Handle device-mapper nodes. + // On kernels <= 5.10, the "add" event is fired on DM_DEV_CREATE, but does not contain name + // information until DM_TABLE_LOAD - thus, we wait for a "change" event. + // On kernels >= 5.15, the "add" event is fired on DM_TABLE_LOAD, followed by a "change" + // event. + if (action == "add" || (action == "change" && StartsWith(devpath, "/dev/block/dm-"))) { + for (const auto& link : links) { + if (!mkdir_recursive(Dirname(link), 0755)) { + PLOG(ERROR) << "Failed to create directory " << Dirname(link); + } + + if (symlink(devpath.c_str(), link.c_str())) { + if (errno != EEXIST) { + PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link; + } else if (std::string link_path; + Readlink(link, &link_path) && link_path != devpath) { + PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link + << ", which already links to: " << link_path; + } + } + } + } + + if (action == "remove") { + if (StartsWith(devpath, "/dev/block/dm-")) { + RemoveDeviceMapperLinks(devpath); + } + for (const auto& link : links) { + std::string link_path; + if (Readlink(link, &link_path) && link_path == devpath) { + unlink(link.c_str()); + } + } + unlink(devpath.c_str()); + } +} + +void DeviceHandler::HandleAshmemUevent(const Uevent& uevent) { + if (uevent.device_name == "ashmem") { + static const std::string boot_id_path = "/proc/sys/kernel/random/boot_id"; + std::string boot_id; + if (!ReadFileToString(boot_id_path, &boot_id)) { + PLOG(ERROR) << "Cannot duplicate ashmem device node. Failed to read " << boot_id_path; + return; + }; + boot_id = Trim(boot_id); + + Uevent dup_ashmem_uevent = uevent; + dup_ashmem_uevent.device_name += boot_id; + dup_ashmem_uevent.path += boot_id; + HandleUevent(dup_ashmem_uevent); + } +} + +void DeviceHandler::HandleUevent(const Uevent& uevent) { + if (uevent.action == "add" || uevent.action == "change" || + uevent.action == "bind" || uevent.action == "online") { + FixupSysPermissions(uevent.path, uevent.subsystem); + } + + // if it's not a /dev device, nothing to do + if (uevent.major < 0 || uevent.minor < 0) return; + + std::string devpath; + std::vector links; + bool block = false; + + if (uevent.subsystem == "block") { + block = true; + devpath = "/dev/block/" + Basename(uevent.path); + + if (StartsWith(uevent.path, "/devices")) { + links = GetBlockDeviceSymlinks(uevent); + } + } else if (const auto subsystem = + std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem); + subsystem != subsystems_.cend()) { + devpath = subsystem->ParseDevPath(uevent); + } else if (uevent.subsystem == "usb") { + if (!uevent.device_name.empty()) { + devpath = "/dev/" + uevent.device_name; + } else { + // This imitates the file system that would be created + // if we were using devfs instead. + // Minors are broken up into groups of 128, starting at "001" + int bus_id = uevent.minor / 128 + 1; + int device_id = uevent.minor % 128 + 1; + devpath = StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id); + } + } else if (StartsWith(uevent.subsystem, "usb")) { + // ignore other USB events + return; + } else if (uevent.subsystem == "misc" && StartsWith(uevent.device_name, "dm-user/")) { + devpath = "/dev/dm-user/" + uevent.device_name.substr(8); + } else if (uevent.subsystem == "misc" && uevent.device_name == "vfio/vfio") { + devpath = "/dev/" + uevent.device_name; + } else { + devpath = "/dev/" + Basename(uevent.path); + } + + mkdir_recursive(Dirname(devpath), 0755); + + HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links); + + // Duplicate /dev/ashmem device and name it /dev/ashmem. + // TODO(b/111903542): remove once all users of /dev/ashmem are migrated to libcutils API. + HandleAshmemUevent(uevent); +} + +void DeviceHandler::ColdbootDone() { + skip_restorecon_ = false; +} + +DeviceHandler::DeviceHandler(std::vector dev_permissions, + std::vector sysfs_permissions, + std::vector subsystems, std::set boot_devices, + bool skip_restorecon) + : dev_permissions_(std::move(dev_permissions)), + sysfs_permissions_(std::move(sysfs_permissions)), + subsystems_(std::move(subsystems)), + boot_devices_(std::move(boot_devices)), + skip_restorecon_(skip_restorecon), + sysfs_mount_point_("/sys") { + chinit_dev("/dev/binder"); + chinit_dev("/dev/hwbinder"); + chinit_dev("/dev/vndbinder"); + chinit_dev("/dev/ashmem"); + chinit_dev("/dev/uinput"); + chinit_dev("/dev/dri/renderD128"); +} + +DeviceHandler::DeviceHandler() + : DeviceHandler(std::vector{}, std::vector{}, + std::vector{}, std::set{}, false) {} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/devices.h b/aosp/system/core/init/devices.h new file mode 100644 index 000000000..2efbff76a --- /dev/null +++ b/aosp/system/core/init/devices.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef _INIT_DEVICES_H +#define _INIT_DEVICES_H + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "uevent.h" +#include "uevent_handler.h" + +namespace android { +namespace init { + +class Permissions { + public: + friend void TestPermissions(const Permissions& expected, const Permissions& test); + + Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid, bool no_fnm_pathname); + + bool Match(const std::string& path) const; + + mode_t perm() const { return perm_; } + uid_t uid() const { return uid_; } + gid_t gid() const { return gid_; } + + protected: + const std::string& name() const { return name_; } + + private: + std::string name_; + mode_t perm_; + uid_t uid_; + gid_t gid_; + bool prefix_; + bool wildcard_; + bool no_fnm_pathname_; +}; + +class SysfsPermissions : public Permissions { + public: + friend void TestSysfsPermissions(const SysfsPermissions& expected, const SysfsPermissions& test); + + SysfsPermissions(const std::string& name, const std::string& attribute, mode_t perm, uid_t uid, + gid_t gid, bool no_fnm_pathname) + : Permissions(name, perm, uid, gid, no_fnm_pathname), attribute_(attribute) {} + + bool MatchWithSubsystem(const std::string& path, const std::string& subsystem) const; + void SetPermissions(const std::string& path) const; + + private: + const std::string attribute_; +}; + +class Subsystem { + public: + friend class SubsystemParser; + friend void TestSubsystems(const Subsystem& expected, const Subsystem& test); + + enum DevnameSource { + DEVNAME_UEVENT_DEVNAME, + DEVNAME_UEVENT_DEVPATH, + }; + + Subsystem() {} + Subsystem(std::string name) : name_(std::move(name)) {} + Subsystem(std::string name, DevnameSource source, std::string dir_name) + : name_(std::move(name)), devname_source_(source), dir_name_(std::move(dir_name)) {} + + // Returns the full path for a uevent of a device that is a member of this subsystem, + // according to the rules parsed from ueventd.rc + std::string ParseDevPath(const Uevent& uevent) const { + std::string devname = devname_source_ == DEVNAME_UEVENT_DEVNAME + ? uevent.device_name + : android::base::Basename(uevent.path); + + return dir_name_ + "/" + devname; + } + + bool operator==(const std::string& string_name) const { return name_ == string_name; } + + private: + std::string name_; + DevnameSource devname_source_ = DEVNAME_UEVENT_DEVNAME; + std::string dir_name_ = "/dev"; +}; + +class DeviceHandler : public UeventHandler { + public: + friend class DeviceHandlerTester; + + DeviceHandler(); + DeviceHandler(std::vector dev_permissions, + std::vector sysfs_permissions, std::vector subsystems, + std::set boot_devices, bool skip_restorecon); + virtual ~DeviceHandler() = default; + + void HandleUevent(const Uevent& uevent) override; + void ColdbootDone() override; + + std::vector GetBlockDeviceSymlinks(const Uevent& uevent) const; + void chinit_dev(const char *path); + + // `androidboot.partition_map` allows associating a partition name for a raw block device + // through a comma separated and semicolon deliminated list. For example, + // `androidboot.partition_map=vdb,metadata;vdc,userdata` maps `vdb` to `metadata` and `vdc` to + // `userdata`. + static std::string GetPartitionNameForDevice(const std::string& device); + + private: + bool FindPlatformDevice(std::string path, std::string* platform_device_path) const; + std::tuple GetDevicePermissions( + const std::string& path, const std::vector& links) const; + void MakeDevice(const std::string& path, bool block, int major, int minor, + const std::vector& links) const; + void HandleDevice(const std::string& action, const std::string& devpath, bool block, int major, + int minor, const std::vector& links) const; + void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const; + void HandleAshmemUevent(const Uevent& uevent); + + std::vector dev_permissions_; + std::vector sysfs_permissions_; + std::vector subsystems_; + std::set boot_devices_; + bool skip_restorecon_; + std::string sysfs_mount_point_; +}; + +// Exposed for testing +void SanitizePartitionName(std::string* string); + +} // namespace init +} // namespace android + +#endif diff --git a/aosp/system/core/init/first_stage_init.cpp b/aosp/system/core/init/first_stage_init.cpp new file mode 100644 index 000000000..22a448bc5 --- /dev/null +++ b/aosp/system/core/init/first_stage_init.cpp @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "first_stage_init.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "debug_ramdisk.h" +#include "first_stage_console.h" +#include "first_stage_mount.h" +#include "reboot_utils.h" +#include "second_stage_resources.h" +#include "snapuserd_transition.h" +#include "switch_root.h" +#include "util.h" + +using android::base::boot_clock; + +using namespace std::literals; + +namespace fs = std::filesystem; + +namespace android { +namespace init { + +namespace { + +enum class BootMode { + NORMAL_MODE, + RECOVERY_MODE, + CHARGER_MODE, +}; + +void FreeRamdisk(DIR* dir, dev_t dev) { + int dfd = dirfd(dir); + + dirent* de = nullptr; + while ((de = readdir(dir)) != nullptr) { + if (de->d_name == "."s || de->d_name == ".."s) { + continue; + } + + bool is_dir = false; + + if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) { + struct stat info {}; + if (fstatat(dfd, de->d_name, &info, AT_SYMLINK_NOFOLLOW) != 0) { + continue; + } + + if (info.st_dev != dev) { + continue; + } + + if (S_ISDIR(info.st_mode)) { + is_dir = true; + auto fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fd >= 0) { + auto subdir = + std::unique_ptr{fdopendir(fd), closedir}; + if (subdir) { + FreeRamdisk(subdir.get(), dev); + } else { + close(fd); + } + } + } + } else if (de->d_type == DT_REG) { + // Do not free snapuserd if we will need the ramdisk copy during the + // selinux transition. + if (de->d_name == "snapuserd"s && IsFirstStageSnapuserdRunning()) { + continue; + } + } + unlinkat(dfd, de->d_name, is_dir ? AT_REMOVEDIR : 0); + } +} + +bool ForceNormalBoot(const std::string& cmdline, const std::string& bootconfig) { + return bootconfig.find("androidboot.force_normal_boot = \"1\"") != std::string::npos || + cmdline.find("androidboot.force_normal_boot=1") != std::string::npos; +} + +static void Copy(const char* src, const char* dst) { + if (link(src, dst) == 0) { + LOG(INFO) << "hard linking " << src << " to " << dst << " succeeded"; + return; + } + PLOG(FATAL) << "hard linking " << src << " to " << dst << " failed"; +} + +// Move snapuserd before switching root, so that it is available at the same path +// after switching root. +void PrepareSwitchRoot() { + static constexpr const auto& snapuserd = "/system/bin/snapuserd"; + static constexpr const auto& snapuserd_ramdisk = "/system/bin/snapuserd_ramdisk"; + static constexpr const auto& dst = "/first_stage_ramdisk/system/bin/snapuserd"; + + if (access(dst, X_OK) == 0) { + LOG(INFO) << dst << " already exists and it can be executed"; + return; + } + auto dst_dir = android::base::Dirname(dst); + std::error_code ec; + if (access(dst_dir.c_str(), F_OK) != 0) { + if (!fs::create_directories(dst_dir, ec)) { + LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message(); + } + } + + // prefer the generic ramdisk copy of snapuserd, because that's on system side of treble + // boundary, and therefore is more likely to be updated along with the Android platform. + // The vendor ramdisk copy might be under vendor freeze, or vendor might choose not to update + // it. + if (access(snapuserd_ramdisk, F_OK) == 0) { + LOG(INFO) << "Using generic ramdisk copy of snapuserd " << snapuserd_ramdisk; + Copy(snapuserd_ramdisk, dst); + } else if (access(snapuserd, F_OK) == 0) { + LOG(INFO) << "Using vendor ramdisk copy of snapuserd " << snapuserd; + Copy(snapuserd, dst); + } +} + +std::string GetPageSizeSuffix() { + static const size_t page_size = sysconf(_SC_PAGE_SIZE); + if (page_size <= 4096) { + return ""; + } + return android::base::StringPrintf("_%zuk", page_size / 1024); +} + +constexpr bool EndsWith(const std::string_view str, const std::string_view suffix) { + return str.size() >= suffix.size() && + 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); +} + +constexpr std::string_view GetPageSizeSuffix(std::string_view dirname) { + if (EndsWith(dirname, "_16k")) { + return "_16k"; + } + if (EndsWith(dirname, "_64k")) { + return "_64k"; + } + return ""; +} + +} // namespace + +std::string GetModuleLoadList(BootMode boot_mode, const std::string& dir_path) { + std::string module_load_file; + + switch (boot_mode) { + case BootMode::NORMAL_MODE: + module_load_file = "modules.load"; + break; + case BootMode::RECOVERY_MODE: + module_load_file = "modules.load.recovery"; + break; + case BootMode::CHARGER_MODE: + module_load_file = "modules.load.charger"; + break; + } + + if (module_load_file != "modules.load") { + struct stat fileStat {}; + std::string load_path = dir_path + "/" + module_load_file; + // Fall back to modules.load if the other files aren't accessible + if (stat(load_path.c_str(), &fileStat)) { + module_load_file = "modules.load"; + } + } + + return module_load_file; +} + +#define MODULE_BASE_DIR "/lib/modules" +bool LoadKernelModules(BootMode boot_mode, bool want_console, bool want_parallel, + int& modules_loaded) { + struct utsname uts {}; + if (uname(&uts)) { + LOG(FATAL) << "Failed to get kernel version."; + } + int major = 0, minor = 0; + if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) { + LOG(FATAL) << "Failed to parse kernel version " << uts.release; + } + + std::unique_ptr base_dir(opendir(MODULE_BASE_DIR), closedir); + if (!base_dir) { + LOG(INFO) << "Unable to open /lib/modules, skipping module loading."; + return true; + } + dirent* entry = nullptr; + std::vector module_dirs; + const auto page_size_suffix = GetPageSizeSuffix(); + const std::string release_specific_module_dir = uts.release + page_size_suffix; + while ((entry = readdir(base_dir.get()))) { + if (entry->d_type != DT_DIR) { + continue; + } + if (entry->d_name == release_specific_module_dir) { + LOG(INFO) << "Release specific kernel module dir " << release_specific_module_dir + << " found, loading modules from here with no fallbacks."; + module_dirs.clear(); + module_dirs.emplace_back(entry->d_name); + break; + } + // Is a directory does not have page size suffix, it does not mean this directory is for 4K + // kernels. Certain 16K kernel builds put all modules in /lib/modules/`uname -r` without any + // suffix. Therefore, only ignore a directory if it has _16k/_64k suffix and the suffix does + // not match system page size. + const auto dir_page_size_suffix = GetPageSizeSuffix(entry->d_name); + if (!dir_page_size_suffix.empty() && dir_page_size_suffix != page_size_suffix) { + continue; + } + int dir_major = 0, dir_minor = 0; + if (sscanf(entry->d_name, "%d.%d", &dir_major, &dir_minor) != 2 || dir_major != major || + dir_minor != minor) { + continue; + } + module_dirs.emplace_back(entry->d_name); + } + + // Sort the directories so they are iterated over during module loading + // in a consistent order. Alphabetical sorting is fine here because the + // kernel version at the beginning of the directory name must match the + // current kernel version, so the sort only applies to a label that + // follows the kernel version, for example /lib/modules/5.4 vs. + // /lib/modules/5.4-gki. + std::sort(module_dirs.begin(), module_dirs.end()); + + for (const auto& module_dir : module_dirs) { + std::string dir_path = MODULE_BASE_DIR "/"; + dir_path.append(module_dir); + Modprobe m({dir_path}, GetModuleLoadList(boot_mode, dir_path)); + bool retval = m.LoadListedModules(!want_console); + modules_loaded = m.GetModuleCount(); + if (modules_loaded > 0) { + LOG(INFO) << "Loaded " << modules_loaded << " modules from " << dir_path; + return retval; + } + } + + Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(boot_mode, MODULE_BASE_DIR)); + bool retval = (want_parallel) ? m.LoadModulesParallel(std::thread::hardware_concurrency()) + : m.LoadListedModules(!want_console); + modules_loaded = m.GetModuleCount(); + if (modules_loaded > 0) { + LOG(INFO) << "Loaded " << modules_loaded << " modules from " << MODULE_BASE_DIR; + return retval; + } + return true; +} + +static bool IsChargerMode(const std::string& cmdline, const std::string& bootconfig) { + return bootconfig.find("androidboot.mode = \"charger\"") != std::string::npos || + cmdline.find("androidboot.mode=charger") != std::string::npos; +} + +static BootMode GetBootMode(const std::string& cmdline, const std::string& bootconfig) +{ + if (IsChargerMode(cmdline, bootconfig)) + return BootMode::CHARGER_MODE; + else if (IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig)) + return BootMode::RECOVERY_MODE; + + return BootMode::NORMAL_MODE; +} + +static std::unique_ptr CreateFirstStageMount(const std::string& cmdline) { + auto ret = FirstStageMount::Create(cmdline); + if (ret.ok()) { + return std::move(*ret); + } else { + LOG(ERROR) << "Failed to create FirstStageMount : " << ret.error(); + return nullptr; + } +} + +int FirstStageMain(int argc, char** argv) { + if (REBOOT_BOOTLOADER_ON_PANIC) { + InstallRebootSignalHandlers(); + } + + boot_clock::time_point start_time = boot_clock::now(); + + std::vector> errors; +#define CHECKCALL(x) \ + if ((x) != 0) LOG(ERROR) << #x " failed" << " " << strerror(errno); + + // Clear the umask. + umask(0); + + CHECKCALL(clearenv()); + CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); + // Get the basic filesystem setup we need put together in the initramdisk + // on / and then we'll let the rc file figure out the rest. + //CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); + CHECKCALL(mkdir("/dev/pts", 0755)); + CHECKCALL(mkdir("/dev/socket", 0755)); + CHECKCALL(mkdir("/dev/dm-user", 0755)); + //CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL)); +//#define MAKE_STR(x) __STRING(x) + //CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC))); +//#undef MAKE_STR + // Don't expose the raw commandline to unprivileged processes. + CHECKCALL(chmod("/proc/cmdline", 0440)); + std::string cmdline; + android::base::ReadFileToString("/proc/cmdline", &cmdline); + // Don't expose the raw bootconfig to unprivileged processes. + chmod("/proc/bootconfig", 0440); + std::string bootconfig; + android::base::ReadFileToString("/proc/bootconfig", &bootconfig); + gid_t groups[] = {AID_READPROC}; + CHECKCALL(setgroups(arraysize(groups), groups)); + CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL)); + CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)); + + CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11))); + + if constexpr (WORLD_WRITABLE_KMSG) { + CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11))); + } + + CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8))); + CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))); + + // This is needed for log wrapper, which gets called before ueventd runs. + CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2))); + CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); + + // These below mounts are done in first stage init so that first stage mount can mount + // subdirectories of /mnt/{vendor,product}/. Other mounts, not required by first stage mount, + // should be done in rc files. + // Mount staging areas for devices managed by vold + // See storage config details at http://source.android.com/devices/storage/ + CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=1000")); + // /mnt/vendor is used to mount vendor-specific partitions that can not be + // part of the vendor partition, e.g. because they are mounted read-write. + CHECKCALL(mkdir("/mnt/vendor", 0755)); + // /mnt/product is used to mount product-specific partitions that can not be + // part of the product partition, e.g. because they are mounted read-write. + CHECKCALL(mkdir("/mnt/product", 0755)); + + // /debug_ramdisk is used to preserve additional files from the debug ramdisk + CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")); + + // /second_stage_resources is used to preserve files from first to second + // stage init + CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")) +#undef CHECKCALL + + SetStdioToDevNull(argv); + // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually + // talk to the outside world... + InitKernelLogging(argv); + + if (!errors.empty()) { + for (const auto& [error_string, error_errno] : errors) { + LOG(ERROR) << error_string << " " << strerror(error_errno); + } + LOG(FATAL) << "Init encountered errors starting first stage, aborting"; + } + + LOG(INFO) << "init first stage started!"; + + // We only allow /vendor partition in debuggable Microdrod until it is verified during boot. + // TODO(b/285855436): remove this check. + if (IsMicrodroid()) { + bool mount_vendor = + cmdline.find("androidboot.microdroid.mount_vendor=1") != std::string::npos; + bool debuggable = + bootconfig.find("androidboot.microdroid.debuggable = \"1\"") != std::string::npos; + if (mount_vendor && !debuggable) { + LOG(FATAL) << "Attempted to mount /vendor partition for non-debuggable Microdroid VM"; + } + } + + auto old_root_dir = std::unique_ptr{opendir("/"), closedir}; + if (!old_root_dir) { + PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk"; + } + + struct stat old_root_info {}; + if (stat("/", &old_root_info) != 0) { + PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk"; + old_root_dir.reset(); + } + + auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0; + auto want_parallel = + bootconfig.find("androidboot.load_modules_parallel = \"true\"") != std::string::npos; + + boot_clock::time_point module_start_time = boot_clock::now(); + int module_count = 0; + BootMode boot_mode = GetBootMode(cmdline, bootconfig); + if (!LoadKernelModules(boot_mode, want_console, + want_parallel, module_count)) { + if (want_console != FirstStageConsoleParam::DISABLED) { + LOG(ERROR) << "Failed to load kernel modules, starting console"; + } else { + LOG(FATAL) << "Failed to load kernel modules"; + } + } + if (module_count > 0) { + auto module_elapse_time = std::chrono::duration_cast( + boot_clock::now() - module_start_time); + setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1); + LOG(INFO) << "Loaded " << module_count << " kernel modules took " + << module_elapse_time.count() << " ms"; + } + + std::unique_ptr fsm; + + bool created_devices = false; + if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) { + if (!IsRecoveryMode()) { + fsm = CreateFirstStageMount(cmdline); + if (fsm) { + created_devices = fsm->DoCreateDevices(); + if (!created_devices) { + LOG(ERROR) << "Failed to create device nodes early"; + } + } + } + StartConsole(cmdline); + } + + if (access(kBootImageRamdiskProp, F_OK) == 0) { + std::string dest = GetRamdiskPropForSecondStage(); + std::string dir = android::base::Dirname(dest); + std::error_code ec; + if (!fs::create_directories(dir, ec) && !!ec) { + LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message(); + } + if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) { + LOG(FATAL) << "Can't copy " << kBootImageRamdiskProp << " to " << dest << ": " + << ec.message(); + } + LOG(INFO) << "Copied ramdisk prop to " << dest; + } + + // If "/force_debuggable" is present, the second-stage init will use a userdebug + // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked. + if (access("/force_debuggable", F_OK) == 0) { + constexpr const char adb_debug_prop_src[] = "/adb_debug.prop"; + constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil"; + std::error_code ec; // to invoke the overloaded copy_file() that won't throw. + if (access(adb_debug_prop_src, F_OK) == 0 && + !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec)) { + LOG(WARNING) << "Can't copy " << adb_debug_prop_src << " to " << kDebugRamdiskProp + << ": " << ec.message(); + } + if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 && + !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec)) { + LOG(WARNING) << "Can't copy " << userdebug_plat_sepolicy_cil_src << " to " + << kDebugRamdiskSEPolicy << ": " << ec.message(); + } + // setenv for second-stage init to read above kDebugRamdisk* files. + setenv("INIT_FORCE_DEBUGGABLE", "true", 1); + } + + if (ForceNormalBoot(cmdline, bootconfig)) { + mkdir("/first_stage_ramdisk", 0755); + PrepareSwitchRoot(); + // SwitchRoot() must be called with a mount point as the target, so we bind mount the + // target directory to itself here. + if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) { + PLOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself"; + } + SwitchRoot("/first_stage_ramdisk"); + } + + if (IsRecoveryMode()) { + LOG(INFO) << "First stage mount skipped (recovery mode)"; + } else { + if (!fsm) { + fsm = CreateFirstStageMount(cmdline); + } +#if 0 + if (!fsm) { + LOG(FATAL) << "FirstStageMount not available"; + } + + if (!created_devices && !fsm->DoCreateDevices()) { + LOG(FATAL) << "Failed to create devices required for first stage mount"; + } + + if (!fsm->DoFirstStageMount()) { + LOG(FATAL) << "Failed to mount required partitions early ..."; + } +#endif + } + + struct stat new_root_info {}; + if (stat("/", &new_root_info) != 0) { + PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk"; + old_root_dir.reset(); + } + + if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) { + FreeRamdisk(old_root_dir.get(), old_root_info.st_dev); + } + + SetInitAvbVersionInRecovery(); + + setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), + 1); + + const char* path = "/system/bin/init"; + const char* args[] = {path, "selinux_setup", nullptr}; + auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + execv(path, const_cast(args)); + + // execv() only returns if an error happened, in which case we + // panic and never fall through this conditional. + PLOG(FATAL) << "execv(\"" << path << "\") failed"; + + return 1; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/init.cpp b/aosp/system/core/init/init.cpp new file mode 100644 index 000000000..6270d19ab --- /dev/null +++ b/aosp/system/core/init/init.cpp @@ -0,0 +1,1158 @@ +/* + * Copyright (C) 2008 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. + */ + +#include "init.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "action.h" +#include "action_manager.h" +#include "action_parser.h" +#include "apex_init_util.h" +#include "epoll.h" +#include "first_stage_init.h" +#include "first_stage_mount.h" +#include "import_parser.h" +#include "keychords.h" +#include "lmkd_service.h" +#include "mount_handler.h" +#include "mount_namespace.h" +#include "property_service.h" +#include "proto_utils.h" +#include "reboot.h" +#include "reboot_utils.h" +#include "second_stage_resources.h" +#include "security.h" +#include "selabel.h" +#include "selinux.h" +#include "service.h" +#include "service_list.h" +#include "service_parser.h" +#include "sigchld_handler.h" +#include "snapuserd_transition.h" +#include "subcontext.h" +#include "system/core/init/property_service.pb.h" +#include "util.h" + +#ifndef RECOVERY +#include "com_android_apex.h" +#endif // RECOVERY + +using namespace std::chrono_literals; +using namespace std::string_literals; + +using android::base::boot_clock; +using android::base::ConsumePrefix; +using android::base::GetProperty; +using android::base::ReadFileToString; +using android::base::SetProperty; +using android::base::StringPrintf; +using android::base::Timer; +using android::base::Trim; +using android::base::unique_fd; +using android::fs_mgr::AvbHandle; +using android::snapshot::SnapshotManager; + +namespace android { +namespace init { + +static int property_triggers_enabled = 0; + +static int sigterm_fd = -1; +static int property_fd = -1; + +struct PendingControlMessage { + std::string message; + std::string name; + pid_t pid; + int fd; +}; +static std::mutex pending_control_messages_lock; +static std::queue pending_control_messages; + +// Init epolls various FDs to wait for various inputs. It previously waited on property changes +// with a blocking socket that contained the information related to the change, however, it was easy +// to fill that socket and deadlock the system. Now we use locks to handle the property changes +// directly in the property thread, however we still must wake the epoll to inform init that there +// is a change to process, so we use this FD. It is non-blocking, since we do not care how many +// times WakeMainInitThread() is called, only that the epoll will wake. +static int wake_main_thread_fd = -1; +static void InstallInitNotifier(Epoll* epoll) { + wake_main_thread_fd = eventfd(0, EFD_CLOEXEC); + if (wake_main_thread_fd == -1) { + PLOG(FATAL) << "Failed to create eventfd for waking init"; + } + auto clear_eventfd = [] { + uint64_t counter; + TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter))); + }; + + if (auto result = epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd); !result.ok()) { + LOG(FATAL) << result.error(); + } +} + +static void WakeMainInitThread() { + uint64_t counter = 1; + TEMP_FAILURE_RETRY(write(wake_main_thread_fd, &counter, sizeof(counter))); +} + +static class PropWaiterState { + public: + bool StartWaiting(const char* name, const char* value) { + auto lock = std::lock_guard{lock_}; + if (waiting_for_prop_) { + return false; + } + if (GetProperty(name, "") != value) { + // Current property value is not equal to expected value + wait_prop_name_ = name; + wait_prop_value_ = value; + waiting_for_prop_.reset(new Timer()); + } else { + LOG(INFO) << "start_waiting_for_property(\"" << name << "\", \"" << value + << "\"): already set"; + } + return true; + } + + void ResetWaitForProp() { + auto lock = std::lock_guard{lock_}; + ResetWaitForPropLocked(); + } + + void CheckAndResetWait(const std::string& name, const std::string& value) { + auto lock = std::lock_guard{lock_}; + // We always record how long init waited for ueventd to tell us cold boot finished. + // If we aren't waiting on this property, it means that ueventd finished before we even + // started to wait. + if (name == kColdBootDoneProp) { + auto time_waited = waiting_for_prop_ ? waiting_for_prop_->duration().count() : 0; + std::thread([time_waited] { + SetProperty("ro.boottime.init.cold_boot_wait", std::to_string(time_waited)); + }).detach(); + } + + if (waiting_for_prop_) { + if (wait_prop_name_ == name && wait_prop_value_ == value) { + LOG(INFO) << "Wait for property '" << wait_prop_name_ << "=" << wait_prop_value_ + << "' took " << *waiting_for_prop_; + ResetWaitForPropLocked(); + WakeMainInitThread(); + } + } + } + + // This is not thread safe because it releases the lock when it returns, so the waiting state + // may change. However, we only use this function to prevent running commands in the main + // thread loop when we are waiting, so we do not care about false positives; only false + // negatives. StartWaiting() and this function are always called from the same thread, so false + // negatives are not possible and therefore we're okay. + bool MightBeWaiting() { + auto lock = std::lock_guard{lock_}; + return static_cast(waiting_for_prop_); + } + + private: + void ResetWaitForPropLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) { + wait_prop_name_.clear(); + wait_prop_value_.clear(); + waiting_for_prop_.reset(); + } + + std::mutex lock_; + GUARDED_BY(lock_) std::unique_ptr waiting_for_prop_{nullptr}; + GUARDED_BY(lock_) std::string wait_prop_name_; + GUARDED_BY(lock_) std::string wait_prop_value_; + +} prop_waiter_state; + +bool start_waiting_for_property(const char* name, const char* value) { + return prop_waiter_state.StartWaiting(name, value); +} + +void ResetWaitForProp() { + prop_waiter_state.ResetWaitForProp(); +} + +static class ShutdownState { + public: + void TriggerShutdown(const std::string& command) { + // We can't call HandlePowerctlMessage() directly in this function, + // because it modifies the contents of the action queue, which can cause the action queue + // to get into a bad state if this function is called from a command being executed by the + // action queue. Instead we set this flag and ensure that shutdown happens before the next + // command is run in the main init loop. + auto lock = std::lock_guard{shutdown_command_lock_}; + shutdown_command_ = command; + do_shutdown_ = true; + WakeMainInitThread(); + } + + std::optional CheckShutdown() __attribute__((warn_unused_result)) { + auto lock = std::lock_guard{shutdown_command_lock_}; + if (do_shutdown_ && !IsShuttingDown()) { + do_shutdown_ = false; + return shutdown_command_; + } + return {}; + } + + private: + std::mutex shutdown_command_lock_; + std::string shutdown_command_ GUARDED_BY(shutdown_command_lock_); + bool do_shutdown_ = false; +} shutdown_state; + +void DumpState() { + ServiceList::GetInstance().DumpState(); + ActionManager::GetInstance().DumpState(); +} + +Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) { + Parser parser; + + parser.AddSectionParser("service", std::make_unique( + &service_list, GetSubcontext(), std::nullopt)); + parser.AddSectionParser("on", std::make_unique(&action_manager, GetSubcontext())); + parser.AddSectionParser("import", std::make_unique(&parser)); + + return parser; +} + +#ifndef RECOVERY +template +struct LibXmlErrorHandler { + T handler_; + template + LibXmlErrorHandler(Handler&& handler) : handler_(std::move(handler)) { + xmlSetGenericErrorFunc(nullptr, &ErrorHandler); + } + ~LibXmlErrorHandler() { xmlSetGenericErrorFunc(nullptr, nullptr); } + static void ErrorHandler(void*, const char* msg, ...) { + va_list args; + va_start(args, msg); + char* formatted; + if (vasprintf(&formatted, msg, args) >= 0) { + LOG(ERROR) << formatted; + } + free(formatted); + va_end(args); + } +}; + +template +LibXmlErrorHandler(Handler&&) -> LibXmlErrorHandler; +#endif // RECOVERY + +// Returns a Parser that accepts scripts from APEX modules. It supports `service` and `on`. +Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list) { + Parser parser; + auto subcontext = GetSubcontext(); +#ifndef RECOVERY + if (subcontext) { + const auto apex_info_list_file = "/apex/apex-info-list.xml"; + auto error_handler = LibXmlErrorHandler([&](const auto& error_message) { + LOG(ERROR) << "Failed to read " << apex_info_list_file << ":" << error_message; + }); + const auto apex_info_list = com::android::apex::readApexInfoList(apex_info_list_file); + if (apex_info_list.has_value()) { + std::vector subcontext_apexes; + for (const auto& info : apex_info_list->getApexInfo()) { + if (info.hasPreinstalledModulePath() && + subcontext->PathMatchesSubcontext(info.getPreinstalledModulePath())) { + subcontext_apexes.push_back(info.getModuleName()); + } + } + subcontext->SetApexList(std::move(subcontext_apexes)); + } + } +#endif // RECOVERY + parser.AddSectionParser("service", + std::make_unique(&service_list, subcontext, + std::nullopt)); + parser.AddSectionParser("on", std::make_unique(&action_manager, subcontext)); + + return parser; +} + +static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { + Parser parser = CreateParser(action_manager, service_list); + + std::string bootscript = GetProperty("ro.boot.init_rc", ""); + if (bootscript.empty()) { + parser.ParseConfig("/system/etc/init/hw/init.rc"); + if (!parser.ParseConfig("/system/etc/init")) { + late_import_paths.emplace_back("/system/etc/init"); + } + // late_import is available only in Q and earlier release. As we don't + // have system_ext in those versions, skip late_import for system_ext. + parser.ParseConfig("/system_ext/etc/init"); + if (!parser.ParseConfig("/vendor/etc/init")) { + late_import_paths.emplace_back("/vendor/etc/init"); + } + if (!parser.ParseConfig("/odm/etc/init")) { + late_import_paths.emplace_back("/odm/etc/init"); + } + if (!parser.ParseConfig("/product/etc/init")) { + late_import_paths.emplace_back("/product/etc/init"); + } + } else { + parser.ParseConfig(bootscript); + } +} + +void PropertyChanged(const std::string& name, const std::string& value) { + // If the property is sys.powerctl, we bypass the event queue and immediately handle it. + // This is to ensure that init will always and immediately shutdown/reboot, regardless of + // if there are other pending events to process or if init is waiting on an exec service or + // waiting on a property. + // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific + // commands to be executed. + if (name == "sys.powerctl") { + trigger_shutdown(value); + } + + if (property_triggers_enabled) { + ActionManager::GetInstance().QueuePropertyChange(name, value); + WakeMainInitThread(); + } + + prop_waiter_state.CheckAndResetWait(name, value); +} + +static std::optional HandleProcessActions() { + std::optional next_process_action_time; + for (const auto& s : ServiceList::GetInstance()) { + if ((s->flags() & SVC_RUNNING) && s->timeout_period()) { + auto timeout_time = s->time_started() + *s->timeout_period(); + if (boot_clock::now() > timeout_time) { + s->Timeout(); + } else { + if (!next_process_action_time || timeout_time < *next_process_action_time) { + next_process_action_time = timeout_time; + } + } + } + + if (!(s->flags() & SVC_RESTARTING)) continue; + + auto restart_time = s->time_started() + s->restart_period(); + if (boot_clock::now() > restart_time) { + if (auto result = s->Start(); !result.ok()) { + LOG(ERROR) << "Could not restart process '" << s->name() << "': " << result.error(); + } + } else { + if (!next_process_action_time || restart_time < *next_process_action_time) { + next_process_action_time = restart_time; + } + } + } + return next_process_action_time; +} + +static Result DoControlStart(Service* service) { + return service->Start(); +} + +static Result DoControlStop(Service* service) { + service->Stop(); + return {}; +} + +static Result DoControlRestart(Service* service) { + service->Restart(); + return {}; +} + +int StopServicesFromApex(const std::string& apex_name) { + auto services = ServiceList::GetInstance().FindServicesByApexName(apex_name); + if (services.empty()) { + LOG(INFO) << "No service found for APEX: " << apex_name; + return 0; + } + std::set service_names; + for (const auto& service : services) { + service_names.emplace(service->name()); + } + constexpr std::chrono::milliseconds kServiceStopTimeout = 10s; + int still_running = StopServicesAndLogViolations(service_names, kServiceStopTimeout, + true /*SIGTERM*/); + // Send SIGKILL to ones that didn't terminate cleanly. + if (still_running > 0) { + still_running = StopServicesAndLogViolations(service_names, 0ms, false /*SIGKILL*/); + } + return still_running; +} + +void RemoveServiceAndActionFromApex(const std::string& apex_name) { + // Remove services and actions that match apex name + ActionManager::GetInstance().RemoveActionIf([&](const std::unique_ptr& action) -> bool { + if (GetApexNameFromFileName(action->filename()) == apex_name) { + return true; + } + return false; + }); + ServiceList::GetInstance().RemoveServiceIf([&](const std::unique_ptr& s) -> bool { + if (GetApexNameFromFileName(s->filename()) == apex_name) { + return true; + } + return false; + }); +} + +static Result DoUnloadApex(const std::string& apex_name) { + if (StopServicesFromApex(apex_name) > 0) { + return Error() << "Unable to stop all service from " << apex_name; + } + RemoveServiceAndActionFromApex(apex_name); + return {}; +} + +static Result UpdateApexLinkerConfig(const std::string& apex_name) { + // Do not invoke linkerconfig when there's no bin/ in the apex. + const std::string bin_path = "/apex/" + apex_name + "/bin"; + if (access(bin_path.c_str(), R_OK) != 0) { + return {}; + } + const char* linkerconfig_binary = "/apex/com.android.runtime/bin/linkerconfig"; + const char* linkerconfig_target = "/linkerconfig"; + const char* arguments[] = {linkerconfig_binary, "--target", linkerconfig_target, "--apex", + apex_name.c_str(), "--strict"}; + + if (logwrap_fork_execvp(arraysize(arguments), arguments, nullptr, false, LOG_KLOG, false, + nullptr) != 0) { + return ErrnoError() << "failed to execute linkerconfig"; + } + LOG(INFO) << "Generated linker configuration for " << apex_name; + return {}; +} + +static Result DoLoadApex(const std::string& apex_name) { + if (auto result = ParseRcScriptsFromApex(apex_name); !result.ok()) { + return result.error(); + } + + if (auto result = UpdateApexLinkerConfig(apex_name); !result.ok()) { + return result.error(); + } + + return {}; +} + +enum class ControlTarget { + SERVICE, // function gets called for the named service + INTERFACE, // action gets called for every service that holds this interface +}; + +using ControlMessageFunction = std::function(Service*)>; + +static const std::map>& GetControlMessageMap() { + // clang-format off + static const std::map> control_message_functions = { + {"sigstop_on", [](auto* service) { service->set_sigstop(true); return Result{}; }}, + {"sigstop_off", [](auto* service) { service->set_sigstop(false); return Result{}; }}, + {"oneshot_on", [](auto* service) { service->set_oneshot(true); return Result{}; }}, + {"oneshot_off", [](auto* service) { service->set_oneshot(false); return Result{}; }}, + {"start", DoControlStart}, + {"stop", DoControlStop}, + {"restart", DoControlRestart}, + }; + // clang-format on + + return control_message_functions; +} + +static Result HandleApexControlMessage(std::string_view action, const std::string& name, + std::string_view message) { + if (action == "load") { + return DoLoadApex(name); + } else if (action == "unload") { + return DoUnloadApex(name); + } else { + return Error() << "Unknown control msg '" << message << "'"; + } +} + +static bool HandleControlMessage(std::string_view message, const std::string& name, + pid_t from_pid) { + std::string cmdline_path = StringPrintf("proc/%d/cmdline", from_pid); + std::string process_cmdline; + if (ReadFileToString(cmdline_path, &process_cmdline)) { + std::replace(process_cmdline.begin(), process_cmdline.end(), '\0', ' '); + process_cmdline = Trim(process_cmdline); + } else { + process_cmdline = "unknown process"; + } + + auto action = message; + if (ConsumePrefix(&action, "apex_")) { + if (auto result = HandleApexControlMessage(action, name, message); !result.ok()) { + LOG(ERROR) << "Control message: Could not ctl." << message << " for '" << name + << "' from pid: " << from_pid << " (" << process_cmdline + << "): " << result.error(); + return false; + } + LOG(INFO) << "Control message: Processed ctl." << message << " for '" << name + << "' from pid: " << from_pid << " (" << process_cmdline << ")"; + return true; + } + + Service* service = nullptr; + if (ConsumePrefix(&action, "interface_")) { + service = ServiceList::GetInstance().FindInterface(name); + } else { + service = ServiceList::GetInstance().FindService(name); + } + + if (service == nullptr) { + LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << message + << " from pid: " << from_pid << " (" << process_cmdline << ")"; + return false; + } + + const auto& map = GetControlMessageMap(); + const auto it = map.find(action); + if (it == map.end()) { + LOG(ERROR) << "Unknown control msg '" << message << "'"; + return false; + } + const auto& function = it->second; + + if (auto result = function(service); !result.ok()) { + LOG(ERROR) << "Control message: Could not ctl." << message << " for '" << name + << "' from pid: " << from_pid << " (" << process_cmdline + << "): " << result.error(); + return false; + } + + LOG(INFO) << "Control message: Processed ctl." << message << " for '" << name + << "' from pid: " << from_pid << " (" << process_cmdline << ")"; + return true; +} + +bool QueueControlMessage(const std::string& message, const std::string& name, pid_t pid, int fd) { + auto lock = std::lock_guard{pending_control_messages_lock}; + if (pending_control_messages.size() > 100) { + LOG(ERROR) << "Too many pending control messages, dropped '" << message << "' for '" << name + << "' from pid: " << pid; + return false; + } + pending_control_messages.push({message, name, pid, fd}); + WakeMainInitThread(); + return true; +} + +static void HandleControlMessages() { + auto lock = std::unique_lock{pending_control_messages_lock}; + // Init historically would only execute handle one property message, including control messages + // in each iteration of its main loop. We retain this behavior here to prevent starvation of + // other actions in the main loop. + if (!pending_control_messages.empty()) { + auto control_message = pending_control_messages.front(); + pending_control_messages.pop(); + lock.unlock(); + + bool success = HandleControlMessage(control_message.message, control_message.name, + control_message.pid); + + uint32_t response = success ? PROP_SUCCESS : PROP_ERROR_HANDLE_CONTROL_MESSAGE; + if (control_message.fd != -1) { + TEMP_FAILURE_RETRY(send(control_message.fd, &response, sizeof(response), 0)); + close(control_message.fd); + } + lock.lock(); + } + // If we still have items to process, make sure we wake back up to do so. + if (!pending_control_messages.empty()) { + WakeMainInitThread(); + } +} + +static Result wait_for_coldboot_done_action(const BuiltinArguments& args) { + if (!prop_waiter_state.StartWaiting(kColdBootDoneProp, "true")) { + LOG(FATAL) << "Could not wait for '" << kColdBootDoneProp << "'"; + } + + return {}; +} + +static Result SetupCgroupsAction(const BuiltinArguments&) { + if (!CgroupsAvailable()) { + LOG(INFO) << "Cgroups support in kernel is not enabled"; + return {}; + } + // Have to create using make_dir function + // for appropriate sepolicy to be set for it + make_dir(android::base::Dirname(CGROUPS_RC_PATH), 0711); + if (!CgroupSetup()) { + return ErrnoError() << "Failed to setup cgroups"; + } + + return {}; +} + +static void export_oem_lock_status() { + if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) { + return; + } + SetProperty( + "ro.boot.flash.locked", + android::base::GetProperty("ro.boot.verifiedbootstate", "") == "orange" ? "0" : "1"); +} + +static Result property_enable_triggers_action(const BuiltinArguments& args) { + /* Enable property triggers. */ + property_triggers_enabled = 1; + return {}; +} + +static Result queue_property_triggers_action(const BuiltinArguments& args) { + ActionManager::GetInstance().QueueBuiltinAction(property_enable_triggers_action, "enable_property_trigger"); + ActionManager::GetInstance().QueueAllPropertyActions(); + return {}; +} + +// Set the UDC controller for the ConfigFS USB Gadgets. +// Read the UDC controller in use from "/sys/class/udc". +// In case of multiple UDC controllers select the first one. +static void SetUsbController() { + static auto controller_set = false; + if (controller_set) return; + std::unique_ptrdir(opendir("/sys/class/udc"), closedir); + if (!dir) return; + + dirent* dp; + while ((dp = readdir(dir.get())) != nullptr) { + if (dp->d_name[0] == '.') continue; + + SetProperty("sys.usb.controller", dp->d_name); + controller_set = true; + break; + } +} + +/// Set ro.kernel.version property to contain the major.minor pair as returned +/// by uname(2). +static void SetKernelVersion() { + struct utsname uts; + unsigned int major, minor; + + if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) { + LOG(ERROR) << "Could not parse the kernel version from uname"; + return; + } + SetProperty("ro.kernel.version", android::base::StringPrintf("%u.%u", major, minor)); +} + +static void HandleSigtermSignal(const signalfd_siginfo& siginfo) { + if (siginfo.ssi_pid != 0) { + // Drop any userspace SIGTERM requests. + LOG(DEBUG) << "Ignoring SIGTERM from pid " << siginfo.ssi_pid; + return; + } + + HandlePowerctlMessage("shutdown,container"); +} + +static void HandleSignalFd(int signal) { + signalfd_siginfo siginfo; + const int signal_fd = signal == SIGCHLD ? Service::GetSigchldFd() : sigterm_fd; + ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo))); + if (bytes_read != sizeof(siginfo)) { + PLOG(ERROR) << "Failed to read siginfo from signal_fd"; + return; + } + + switch (siginfo.ssi_signo) { + case SIGCHLD: + ReapAnyOutstandingChildren(); + break; + case SIGTERM: + HandleSigtermSignal(siginfo); + break; + default: + LOG(ERROR) << "signal_fd: received unexpected signal " << siginfo.ssi_signo; + break; + } +} + +static void UnblockSignals() { + const struct sigaction act { .sa_handler = SIG_DFL }; + sigaction(SIGCHLD, &act, nullptr); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_UNBLOCK, &mask, nullptr) == -1) { + PLOG(FATAL) << "failed to unblock signals for PID " << getpid(); + } +} + +static Result RegisterSignalFd(Epoll* epoll, int signal, int fd) { + return epoll->RegisterHandler( + fd, [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI); +} + +static Result CreateAndRegisterSignalFd(Epoll* epoll, int signal) { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, signal); + if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { + return ErrnoError() << "failed to block signal " << signal; + } + + unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC)); + if (signal_fd.get() < 0) { + return ErrnoError() << "failed to create signalfd for signal " << signal; + } + OR_RETURN(RegisterSignalFd(epoll, signal, signal_fd.get())); + + return signal_fd.release(); +} + +static void InstallSignalFdHandler(Epoll* epoll) { + // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving + // SIGCHLD when a child process stops or continues (b/77867680#comment9). + const struct sigaction act { .sa_flags = SA_NOCLDSTOP, .sa_handler = SIG_DFL }; + sigaction(SIGCHLD, &act, nullptr); + + // Register a handler to unblock signals in the child processes. + const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals); + if (result != 0) { + LOG(FATAL) << "Failed to register a fork handler: " << strerror(result); + } + + Result cs_result = RegisterSignalFd(epoll, SIGCHLD, Service::GetSigchldFd()); + if (!cs_result.ok()) { + PLOG(FATAL) << cs_result.error(); + } + + if (!IsRebootCapable()) { + Result cs_result = CreateAndRegisterSignalFd(epoll, SIGTERM); + if (!cs_result.ok()) { + PLOG(FATAL) << cs_result.error(); + } + sigterm_fd = cs_result.value(); + } +} + +void HandleKeychord(const std::vector& keycodes) { + // Only handle keychords if adb is enabled. + std::string adb_enabled = android::base::GetProperty("init.svc.adbd", ""); + if (adb_enabled != "running") { + LOG(WARNING) << "Not starting service for keychord " << android::base::Join(keycodes, ' ') + << " because ADB is disabled"; + return; + } + + auto found = false; + for (const auto& service : ServiceList::GetInstance()) { + auto svc = service.get(); + if (svc->keycodes() == keycodes) { + found = true; + LOG(INFO) << "Starting service '" << svc->name() << "' from keychord " + << android::base::Join(keycodes, ' '); + if (auto result = svc->Start(); !result.ok()) { + LOG(ERROR) << "Could not start service '" << svc->name() << "' from keychord " + << android::base::Join(keycodes, ' ') << ": " << result.error(); + } + } + } + if (!found) { + LOG(ERROR) << "Service for keychord " << android::base::Join(keycodes, ' ') << " not found"; + } +} + +static void UmountDebugRamdisk() { + if (umount("/debug_ramdisk") != 0) { + PLOG(ERROR) << "Failed to umount /debug_ramdisk"; + } +} + +static void UmountSecondStageRes() { + if (umount(kSecondStageRes) != 0) { + PLOG(ERROR) << "Failed to umount " << kSecondStageRes; + } +} + +static void MountExtraFilesystems() { + aosp_hack(); +#define CHECKCALL(x) \ + if ((x) != 0) PLOG(FATAL) << #x " failed."; + + // /apex is used to mount APEXes + CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")); + + if (NeedsTwoMountNamespaces()) { + // /bootstrap-apex is used to mount "bootstrap" APEXes. + CHECKCALL(mount("tmpfs", "/bootstrap-apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")); + } + + // /linkerconfig is used to keep generated linker configuration + CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")); +#undef CHECKCALL +} + +static void RecordStageBoottimes(const boot_clock::time_point& second_stage_start_time) { + int64_t first_stage_start_time_ns = -1; + if (auto first_stage_start_time_str = getenv(kEnvFirstStageStartedAt); + first_stage_start_time_str) { + SetProperty("ro.boottime.init", first_stage_start_time_str); + android::base::ParseInt(first_stage_start_time_str, &first_stage_start_time_ns); + } + unsetenv(kEnvFirstStageStartedAt); + + int64_t selinux_start_time_ns = -1; + if (auto selinux_start_time_str = getenv(kEnvSelinuxStartedAt); selinux_start_time_str) { + android::base::ParseInt(selinux_start_time_str, &selinux_start_time_ns); + } + unsetenv(kEnvSelinuxStartedAt); + + if (selinux_start_time_ns == -1) return; + if (first_stage_start_time_ns == -1) return; + + SetProperty("ro.boottime.init.first_stage", + std::to_string(selinux_start_time_ns - first_stage_start_time_ns)); +#if 0 + SetProperty("ro.boottime.init.selinux", + std::to_string(second_stage_start_time.time_since_epoch().count() - + selinux_start_time_ns)); +#endif + if (auto init_module_time_str = getenv(kEnvInitModuleDurationMs); init_module_time_str) { + SetProperty("ro.boottime.init.modules", init_module_time_str); + unsetenv(kEnvInitModuleDurationMs); + } +} + +void SendLoadPersistentPropertiesMessage() { + auto init_message = InitMessage{}; + init_message.set_load_persistent_properties(true); + if (auto result = SendMessage(property_fd, init_message); !result.ok()) { + LOG(ERROR) << "Failed to send load persistent properties message: " << result.error(); + } +} + +static Result ConnectEarlyStageSnapuserdAction(const BuiltinArguments& args) { + auto pid = GetSnapuserdFirstStagePid(); + if (!pid) { + return {}; + } + + auto info = GetSnapuserdFirstStageInfo(); + if (auto iter = std::find(info.begin(), info.end(), "socket"s); iter == info.end()) { + // snapuserd does not support socket handoff, so exit early. + return {}; + } + + // Socket handoff is supported. + auto svc = ServiceList::GetInstance().FindService("snapuserd"); + if (!svc) { + LOG(FATAL) << "Failed to find snapuserd service entry"; + } + + svc->SetShutdownCritical(); + svc->SetStartedInFirstStage(*pid); + + svc = ServiceList::GetInstance().FindService("snapuserd_proxy"); + if (!svc) { + LOG(FATAL) << "Failed find snapuserd_proxy service entry, merge will never initiate"; + } + if (!svc->MarkSocketPersistent("snapuserd")) { + LOG(FATAL) << "Could not find snapuserd socket in snapuserd_proxy service entry"; + } + if (auto result = svc->Start(); !result.ok()) { + LOG(FATAL) << "Could not start snapuserd_proxy: " << result.error(); + } + return {}; +} + +int SecondStageMain(int argc, char** argv) { + if (REBOOT_BOOTLOADER_ON_PANIC) { + InstallRebootSignalHandlers(); + } + + // No threads should be spin up until signalfd + // is registered. If the threads are indeed required, + // each of these threads _should_ make sure SIGCHLD signal + // is blocked. See b/223076262 + boot_clock::time_point start_time = boot_clock::now(); + + trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); }; + + SetStdioToDevNull(argv); + // InitKernelLogging(argv); + LOG(INFO) << "init second stage started!"; + + SelinuxSetupKernelLogging(); + + // Update $PATH in the case the second stage init is newer than first stage init, where it is + // first set. + if (setenv("PATH", _PATH_DEFPATH, 1) != 0) { + PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage"; + } + + // Init should not crash because of a dependence on any other process, therefore we ignore + // SIGPIPE and handle EPIPE at the call site directly. Note that setting a signal to SIG_IGN + // is inherited across exec, but custom signal handlers are not. Since we do not want to + // ignore SIGPIPE for child processes, we set a no-op function for the signal handler instead. + { + struct sigaction action = {.sa_flags = SA_RESTART}; + action.sa_handler = [](int) {}; + sigaction(SIGPIPE, &action, nullptr); + } + + // Set init and its forked children's oom_adj. + if (auto result = + WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST)); + !result.ok()) { + LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST + << " to /proc/1/oom_score_adj: " << result.error(); + } + + // Indicate that booting is in progress to background fw loaders, etc. + close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); + + // See if need to load debug props to allow adb root, when the device is unlocked. + const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE"); + bool load_debug_prop = false; + if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) { + load_debug_prop = "true"s == force_debuggable_env; + } + unsetenv("INIT_FORCE_DEBUGGABLE"); + + // Umount the debug ramdisk so property service doesn't read .prop files from there, when it + // is not meant to. + if (!load_debug_prop) { + UmountDebugRamdisk(); + } + + PropertyInit(); + + // Umount second stage resources after property service has read the .prop files. + UmountSecondStageRes(); + + // Umount the debug ramdisk after property service has read the .prop files when it means to. + if (load_debug_prop) { + UmountDebugRamdisk(); + } + + // Mount extra filesystems required during second stage init + MountExtraFilesystems(); + + // Now set up SELinux for second stage. + SelabelInitialize(); + SelinuxRestoreContext(); + + Epoll epoll; + if (auto result = epoll.Open(); !result.ok()) { + PLOG(FATAL) << result.error(); + } + + // We always reap children before responding to the other pending functions. This is to + // prevent a race where other daemons see that a service has exited and ask init to + // start it again via ctl.start before init has reaped it. + epoll.SetFirstCallback(ReapAnyOutstandingChildren); + + InstallSignalFdHandler(&epoll); + InstallInitNotifier(&epoll); + StartPropertyService(&property_fd); + + // Make the time that init stages started available for bootstat to log. + RecordStageBoottimes(start_time); + + // Set libavb version for Framework-only OTA match in Treble build. + //if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) { + // SetProperty("ro.boot.avb_version", avb_version); + //} + //unsetenv("INIT_AVB_VERSION"); + + fs_mgr_vendor_overlay_mount_all(); + export_oem_lock_status(); + MountHandler mount_handler(&epoll); + SetUsbController(); + SetKernelVersion(); + + const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(); + Action::set_function_map(&function_map); + + if (!SetupMountNamespaces()) { + PLOG(FATAL) << "SetupMountNamespaces failed"; + } + + InitializeSubcontext(); + + ActionManager& am = ActionManager::GetInstance(); + ServiceList& sm = ServiceList::GetInstance(); + + LoadBootScripts(am, sm); + + // Turning this on and letting the INFO logging be discarded adds 0.2s to + // Nexus 9 boot time, so it's disabled by default. + if (false) DumpState(); + + // Make the GSI status available before scripts start running. + auto is_running = android::gsi::IsGsiRunning() ? "1" : "0"; + SetProperty(gsi::kGsiBootedProp, is_running); + auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0"; + SetProperty(gsi::kGsiInstalledProp, is_installed); + if (android::gsi::IsGsiRunning()) { + std::string dsu_slot; + if (android::gsi::GetActiveDsu(&dsu_slot)) { + SetProperty(gsi::kDsuSlotProp, dsu_slot); + } + } + + am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); + //am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict"); + am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux"); + am.QueueEventTrigger("early-init"); + am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd"); + + // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... + am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); + // ... so that we can start queuing up actions that require stuff from /dev. + //am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits"); + Keychords keychords; + am.QueueBuiltinAction( + [&epoll, &keychords](const BuiltinArguments& args) -> Result { + for (const auto& svc : ServiceList::GetInstance()) { + keychords.Register(svc->keycodes()); + } + keychords.Start(&epoll, HandleKeychord); + return {}; + }, + "KeychordInit"); + + // Trigger all the boot actions to get us started. + am.QueueEventTrigger("init"); + + // Don't mount filesystems or start core system services in charger mode. + std::string bootmode = GetProperty("ro.bootmode", ""); + if (bootmode == "charger") { + am.QueueEventTrigger("charger"); + } else { + am.QueueEventTrigger("late-init"); + } + + // Run all property triggers based on current state of the properties. + am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); + + // Restore prio before main loop + setpriority(PRIO_PROCESS, 0, 0); + while (true) { + // By default, sleep until something happens. Do not convert far_future into + // std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock + // is 1ns. + const boot_clock::time_point far_future = boot_clock::time_point::max(); + boot_clock::time_point next_action_time = far_future; + + auto shutdown_command = shutdown_state.CheckShutdown(); + if (shutdown_command) { + LOG(INFO) << "Got shutdown_command '" << *shutdown_command + << "' Calling HandlePowerctlMessage()"; + // HandlePowerctlMessage(*shutdown_command); + } + + if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) { + am.ExecuteOneCommand(); + // If there's more work to do, wake up again immediately. + if (am.HasMoreCommands()) { + next_action_time = boot_clock::now(); + } + } + // Since the above code examined pending actions, no new actions must be + // queued by the code between this line and the Epoll::Wait() call below + // without calling WakeMainInitThread(). + if (!IsShuttingDown()) { + auto next_process_action_time = HandleProcessActions(); + + // If there's a process that needs restarting, wake up in time for that. + if (next_process_action_time) { + next_action_time = std::min(next_action_time, *next_process_action_time); + } + } + + std::optional epoll_timeout; + if (next_action_time != far_future) { + epoll_timeout = std::chrono::ceil( + std::max(next_action_time - boot_clock::now(), 0ns)); + } + auto epoll_result = epoll.Wait(epoll_timeout); + if (!epoll_result.ok()) { + LOG(ERROR) << epoll_result.error(); + } + if (!IsShuttingDown()) { + HandleControlMessages(); + SetUsbController(); + } + } + + return 0; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/mount_namespace.cpp b/aosp/system/core/init/mount_namespace.cpp new file mode 100644 index 000000000..8e3ba9576 --- /dev/null +++ b/aosp/system/core/init/mount_namespace.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "mount_namespace.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +namespace android { +namespace init { +namespace { + +static bool BindMount(const std::string& source, const std::string& mount_point) { + if (mount(source.c_str(), mount_point.c_str(), nullptr, MS_BIND | MS_REC, nullptr) == -1) { + PLOG(ERROR) << "Failed to bind mount " << source; + return false; + } + return true; +} + +static bool ChangeMount(const std::string& mount_point, unsigned long mountflags) { + if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) { + PLOG(ERROR) << "Failed to remount " << mount_point << " as " << std::hex << mountflags; + return false; + } + return true; +} + +static int OpenMountNamespace() { + int fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + PLOG(ERROR) << "Cannot open fd for current mount namespace"; + } + return fd; +} + +static std::string GetMountNamespaceId() { + std::string ret; + if (!android::base::Readlink("/proc/self/ns/mnt", &ret)) { + PLOG(ERROR) << "Failed to read namespace ID"; + return ""; + } + return ret; +} + +static android::base::unique_fd bootstrap_ns_fd; +static android::base::unique_fd default_ns_fd; + +static std::string bootstrap_ns_id; +static std::string default_ns_id; + +} // namespace + +// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount +// namespaces. +bool NeedsTwoMountNamespaces() { + if (IsRecoveryMode()) return false; + // In microdroid, there's only one set of APEXes in built-in directories include block devices. + if (IsMicrodroid()) return false; + return true; +} + +bool SetupMountNamespaces() { + // Set the propagation type of / as shared so that any mounting event (e.g. + // /data) is by default visible to all processes. When private mounting is + // needed for /foo/bar, then we will make /foo/bar as a mount point (by + // bind-mounting by to itself) and set the propagation type of the mount + // point to private. + if (!ChangeMount("/", MS_SHARED | MS_REC)) return false; + + // /apex is a private mountpoint to give different sets of APEXes for + // the bootstrap and default mount namespaces. The processes running with + // the bootstrap namespace get APEXes from the read-only partition. + //if (!(ChangeMount("/apex", MS_PRIVATE))) return false; + + // /linkerconfig is a private mountpoint to give a different linker configuration + // based on the mount namespace. Subdirectory will be bind-mounted based on current mount + // namespace + //if (!(ChangeMount("/linkerconfig", MS_PRIVATE))) return false; + + // The two mount namespaces present challenges for scoped storage, because + // vold, which is responsible for most of the mounting, lives in the + // bootstrap mount namespace, whereas most other daemons and all apps live + // in the default namespace. Scoped storage has a need for a + // /mnt/installer view that is a slave bind mount of /mnt/user - in other + // words, all mounts under /mnt/user should automatically show up under + // /mnt/installer. However, additional mounts done under /mnt/installer + // should not propagate back to /mnt/user. In a single mount namespace + // this is easy to achieve, by simply marking the /mnt/installer a slave + // bind mount. Unfortunately, if /mnt/installer is only created and + // bind mounted after the two namespaces are created below, we end up + // with the following situation: + // /mnt/user and /mnt/installer share the same peer group in both the + // bootstrap and default namespaces. Marking /mnt/installer slave in either + // namespace means that it won't propagate events to the /mnt/installer in + // the other namespace, which is still something we require - vold is the + // one doing the mounting under /mnt/installer, and those mounts should + // show up in the default namespace as well. + // + // The simplest solution is to do the bind mount before the two namespaces + // are created: the effect is that in both namespaces, /mnt/installer is a + // slave to the /mnt/user mount, and at the same time /mnt/installer in the + // bootstrap namespace shares a peer group with /mnt/installer in the + // default namespace. + // /mnt/androidwritable is similar to /mnt/installer but serves for + // MOUNT_EXTERNAL_ANDROID_WRITABLE apps. + if (!mkdir_recursive("/mnt/user", 0755)) return false; + if (!mkdir_recursive("/mnt/installer", 0755)) return false; + if (!mkdir_recursive("/mnt/androidwritable", 0755)) return false; + if (!(BindMount("/mnt/user", "/mnt/installer"))) return false; + if (!(BindMount("/mnt/user", "/mnt/androidwritable"))) return false; + // First, make /mnt/installer and /mnt/androidwritable a slave bind mount + if (!(ChangeMount("/mnt/installer", MS_SLAVE))) return false; + if (!(ChangeMount("/mnt/androidwritable", MS_SLAVE))) return false; + // Then, make it shared again - effectively creating a new peer group, that + // will be inherited by new mount namespaces. + if (!(ChangeMount("/mnt/installer", MS_SHARED))) return false; + if (!(ChangeMount("/mnt/androidwritable", MS_SHARED))) return false; + + bootstrap_ns_fd.reset(OpenMountNamespace()); + bootstrap_ns_id = GetMountNamespaceId(); + + // When APEXes are updatable (e.g. not-flattened), we create separate mount + // namespaces for processes that are started before and after the APEX is + // activated by apexd. In the namespace for pre-apexd processes, small + // number of essential APEXes (e.g. com.android.runtime) are activated. + // In the namespace for post-apexd processes, all APEXes are activated. + bool success = true; + if (NeedsTwoMountNamespaces()) { + // Creating a new namespace by cloning, saving, and switching back to + // the original namespace. + if (unshare(CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Cannot create mount namespace"; + return false; + } + default_ns_fd.reset(OpenMountNamespace()); + default_ns_id = GetMountNamespaceId(); + + if (setns(bootstrap_ns_fd.get(), CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Cannot switch back to bootstrap mount namespace"; + return false; + } + + // Some components (e.g. servicemanager) need to access bootstrap + // APEXes from the default mount namespace. To achieve that, we bind-mount + // /apex to /bootstrap-apex in the bootstrap mount namespace. Since /bootstrap-apex + // is "shared", the mounts are visible in the default mount namespace as well. + // + // The end result will look like: + // in the bootstrap mount namespace: + // /apex (== /bootstrap-apex) + // {bootstrap APEXes from the read-only partition} + // + // in the default mount namespace: + // /bootstrap-apex + // {bootstrap APEXes from the read-only partition} + // /apex + // {APEXes, can be from /data partition} + if (!(BindMount("/bootstrap-apex", "/apex"))) return false; + } else { + // Otherwise, default == bootstrap + default_ns_fd.reset(OpenMountNamespace()); + default_ns_id = GetMountNamespaceId(); + } + + LOG(INFO) << "SetupMountNamespaces done"; + return success; +} + +// Switch the mount namespace of the current process from bootstrap to default OR from default to +// bootstrap. If the current mount namespace is neither bootstrap nor default, keep it that way. +Result SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace) { + if (IsRecoveryMode()) { + // we don't have multiple namespaces in recovery mode or if apex is not updatable + return {}; + } + + const std::string current_namespace_id = GetMountNamespaceId(); + MountNamespace current_mount_namespace; + if (current_namespace_id == bootstrap_ns_id) { + current_mount_namespace = NS_BOOTSTRAP; + } else if (current_namespace_id == default_ns_id) { + current_mount_namespace = NS_DEFAULT; + } else { + // services with `namespace mnt` start in its own mount namespace. So we need to keep it. + return {}; + } + + // We're already in the target mount namespace. + if (current_mount_namespace == target_mount_namespace) { + return {}; + } + + const auto& ns_fd = target_mount_namespace == NS_BOOTSTRAP ? bootstrap_ns_fd : default_ns_fd; + const auto& ns_name = target_mount_namespace == NS_BOOTSTRAP ? "bootstrap" : "default"; + if (ns_fd.get() != -1) { + if (setns(ns_fd.get(), CLONE_NEWNS) == -1) { + return ErrnoError() << "Failed to switch to " << ns_name << " mount namespace."; + } + } + return {}; +} + +base::Result GetCurrentMountNamespace() { + std::string current_namespace_id = GetMountNamespaceId(); + if (current_namespace_id == "") { + return Error() << "Failed to get current mount namespace ID"; + } + + if (current_namespace_id == bootstrap_ns_id) { + return NS_BOOTSTRAP; + } else if (current_namespace_id == default_ns_id) { + return NS_DEFAULT; + } + + return Error() << "Failed to find current mount namespace"; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/property_service.cpp b/aosp/system/core/init/property_service.cpp new file mode 100644 index 000000000..d226327ba --- /dev/null +++ b/aosp/system/core/init/property_service.cpp @@ -0,0 +1,1551 @@ +/* + * Copyright (C) 2007 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. + */ + +#include "property_service.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug_ramdisk.h" +#include "epoll.h" +#include "init.h" +#include "persistent_properties.h" +#include "property_type.h" +#include "proto_utils.h" +#include "second_stage_resources.h" +#include "selinux.h" +#include "subcontext.h" +#include "system/core/init/property_service.pb.h" +#include "util.h" + +static constexpr char APPCOMPAT_OVERRIDE_PROP_FOLDERNAME[] = + "/dev/__properties__/appcompat_override"; +static constexpr char APPCOMPAT_OVERRIDE_PROP_TREE_FILE[] = + "/dev/__properties__/appcompat_override/property_info"; +using namespace std::literals; + +using android::base::ErrnoError; +using android::base::Error; +using android::base::GetIntProperty; +using android::base::GetProperty; +using android::base::ParseInt; +using android::base::ReadFileToString; +using android::base::Result; +using android::base::Split; +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::Timer; +using android::base::Trim; +using android::base::unique_fd; +using android::base::WriteStringToFile; +using android::properties::BuildTrie; +using android::properties::ParsePropertyInfoFile; +using android::properties::PropertyInfoAreaFile; +using android::properties::PropertyInfoEntry; +using android::sysprop::InitProperties::is_userspace_reboot_supported; + +namespace android { +namespace init { + +class PersistWriteThread; + +constexpr auto FINGERPRINT_PROP = "ro.build.fingerprint"; +constexpr auto LEGACY_FINGERPRINT_PROP = "ro.build.legacy.fingerprint"; +constexpr auto ID_PROP = "ro.build.id"; +constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id"; +constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest"; +constexpr auto DIGEST_SIZE_USED = 8; + +static bool persistent_properties_loaded = false; + +static int from_init_socket = -1; +static int init_socket = -1; +static bool accept_messages = false; +static std::mutex accept_messages_lock; +static std::mutex selinux_check_access_lock; +static std::thread property_service_thread; +static std::thread property_service_for_system_thread; + +static std::unique_ptr persist_write_thread; + +static PropertyInfoAreaFile property_info_area; + +struct PropertyAuditData { + const ucred* cr; + const char* name; +}; +#if 0 +static int PropertyAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) { + auto* d = reinterpret_cast(data); + + if (!d || !d->name || !d->cr) { + LOG(ERROR) << "AuditCallback invoked with null data arguments!"; + return 0; + } + + snprintf(buf, len, "property=%s pid=%d uid=%d gid=%d", d->name, d->cr->pid, d->cr->uid, + d->cr->gid); + return 0; +} +#endif +void StartSendingMessages() { + auto lock = std::lock_guard{accept_messages_lock}; + accept_messages = true; +} + +void StopSendingMessages() { + auto lock = std::lock_guard{accept_messages_lock}; + accept_messages = false; +} + +bool CanReadProperty(const std::string& source_context, const std::string& name) { + const char* target_context = nullptr; + property_info_area->GetPropertyInfo(name.c_str(), &target_context, nullptr); + + PropertyAuditData audit_data; + + audit_data.name = name.c_str(); + + ucred cr = {.pid = 0, .uid = 0, .gid = 0}; + audit_data.cr = &cr; + + auto lock = std::lock_guard{selinux_check_access_lock}; + return selinux_check_access(source_context.c_str(), target_context, "file", "read", + &audit_data) == 0; +} + +static bool CheckMacPerms(const std::string& name, const char* target_context, + const char* source_context, const ucred& cr) { + if (!target_context || !source_context) { + return false; + } + + PropertyAuditData audit_data; + + audit_data.name = name.c_str(); + audit_data.cr = &cr; + + auto lock = std::lock_guard{selinux_check_access_lock}; + return selinux_check_access(source_context, target_context, "property_service", "set", + &audit_data) == 0; +} + +void NotifyPropertyChange(const std::string& name, const std::string& value) { + // If init hasn't started its main loop, then it won't be handling property changed messages + // anyway, so there's no need to try to send them. + auto lock = std::lock_guard{accept_messages_lock}; + if (accept_messages) { + PropertyChanged(name, value); + } +} + +class AsyncRestorecon { + public: + void TriggerRestorecon(const std::string& path) { + auto guard = std::lock_guard{mutex_}; + paths_.emplace(path); + + if (!thread_started_) { + thread_started_ = true; + std::thread{&AsyncRestorecon::ThreadFunction, this}.detach(); + } + } + + private: + void ThreadFunction() { + auto lock = std::unique_lock{mutex_}; + + while (!paths_.empty()) { + auto path = paths_.front(); + paths_.pop(); + + lock.unlock(); + if (selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) { + LOG(ERROR) << "Asynchronous restorecon of '" << path << "' failed'"; + } + android::base::SetProperty(kRestoreconProperty, path); + lock.lock(); + } + + thread_started_ = false; + } + + std::mutex mutex_; + std::queue paths_; + bool thread_started_ = false; +}; + +class SocketConnection { + public: + SocketConnection() = default; + SocketConnection(int socket, const ucred& cred) : socket_(socket), cred_(cred) {} + SocketConnection(SocketConnection&&) = default; + + bool RecvUint32(uint32_t* value, uint32_t* timeout_ms) { + return RecvFully(value, sizeof(*value), timeout_ms); + } + + bool RecvChars(char* chars, size_t size, uint32_t* timeout_ms) { + return RecvFully(chars, size, timeout_ms); + } + + bool RecvString(std::string* value, uint32_t* timeout_ms) { + uint32_t len = 0; + if (!RecvUint32(&len, timeout_ms)) { + return false; + } + + if (len == 0) { + *value = ""; + return true; + } + + // http://b/35166374: don't allow init to make arbitrarily large allocations. + if (len > 0xffff) { + LOG(ERROR) << "sys_prop: RecvString asked to read huge string: " << len; + errno = ENOMEM; + return false; + } + + std::vector chars(len); + if (!RecvChars(&chars[0], len, timeout_ms)) { + return false; + } + + *value = std::string(&chars[0], len); + return true; + } + + bool SendUint32(uint32_t value) { + if (!socket_.ok()) { + return true; + } + int result = TEMP_FAILURE_RETRY(send(socket_.get(), &value, sizeof(value), 0)); + return result == sizeof(value); + } + + bool GetSourceContext(std::string* source_context) const { + char* c_source_context = nullptr; + if (getpeercon(socket_.get(), &c_source_context) != 0) { + return false; + } + *source_context = c_source_context; + freecon(c_source_context); + return true; + } + + [[nodiscard]] int Release() { return socket_.release(); } + + const ucred& cred() { return cred_; } + + SocketConnection& operator=(SocketConnection&&) = default; + + private: + bool PollIn(uint32_t* timeout_ms) { + struct pollfd ufd = { + .fd = socket_.get(), + .events = POLLIN, + }; + while (*timeout_ms > 0) { + auto start_time = std::chrono::steady_clock::now(); + int nr = poll(&ufd, 1, *timeout_ms); + auto now = std::chrono::steady_clock::now(); + auto time_elapsed = + std::chrono::duration_cast(now - start_time); + uint64_t millis = time_elapsed.count(); + *timeout_ms = (millis > *timeout_ms) ? 0 : *timeout_ms - millis; + + if (nr > 0) { + return true; + } + + if (nr == 0) { + // Timeout + break; + } + + if (nr < 0 && errno != EINTR) { + PLOG(ERROR) << "sys_prop: error waiting for uid " << cred_.uid + << " to send property message"; + return false; + } else { // errno == EINTR + // Timer rounds milliseconds down in case of EINTR we want it to be rounded up + // to avoid slowing init down by causing EINTR with under millisecond timeout. + if (*timeout_ms > 0) { + --(*timeout_ms); + } + } + } + + LOG(ERROR) << "sys_prop: timeout waiting for uid " << cred_.uid + << " to send property message."; + return false; + } + + bool RecvFully(void* data_ptr, size_t size, uint32_t* timeout_ms) { + size_t bytes_left = size; + char* data = static_cast(data_ptr); + while (*timeout_ms > 0 && bytes_left > 0) { + if (!PollIn(timeout_ms)) { + return false; + } + + int result = TEMP_FAILURE_RETRY(recv(socket_.get(), data, bytes_left, MSG_DONTWAIT)); + if (result <= 0) { + PLOG(ERROR) << "sys_prop: recv error"; + return false; + } + + bytes_left -= result; + data += result; + } + + if (bytes_left != 0) { + LOG(ERROR) << "sys_prop: recv data is not properly obtained."; + } + + return bytes_left == 0; + } + + unique_fd socket_; + ucred cred_; + + DISALLOW_COPY_AND_ASSIGN(SocketConnection); +}; + +class PersistWriteThread { + public: + PersistWriteThread(); + void Write(std::string name, std::string value, SocketConnection socket); + + private: + void Work(); + + private: + std::thread thread_; + std::mutex mutex_; + std::condition_variable cv_; + std::deque> work_; +}; + +static std::optional PropertySet(const std::string& name, const std::string& value, + SocketConnection* socket, std::string* error) { + size_t valuelen = value.size(); + + if (!IsLegalPropertyName(name)) { + *error = "Illegal property name"; + return {PROP_ERROR_INVALID_NAME}; + } + + if (auto result = IsLegalPropertyValue(name, value); !result.ok()) { + *error = result.error().message(); + return {PROP_ERROR_INVALID_VALUE}; + } + + if (name == "sys.powerctl") { + // No action here - NotifyPropertyChange will trigger the appropriate action, and since this + // can come to the second thread, we mustn't call out to the __system_property_* functions + // which support multiple readers but only one mutator. + } else { + prop_info* pi = (prop_info*)__system_property_find(name.c_str()); + if (pi != nullptr) { + // ro.* properties are actually "write-once". + if (StartsWith(name, "ro.")) { + *error = "Read-only property was already set"; + return {PROP_ERROR_READ_ONLY_PROPERTY}; + } + + __system_property_update(pi, value.c_str(), valuelen); + } else { + int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen); + if (rc < 0) { + *error = "__system_property_add failed"; + return {PROP_ERROR_SET_FAILED}; + } + } + + // Don't write properties to disk until after we have read all default + // properties to prevent them from being overwritten by default values. + bool need_persist = StartsWith(name, "persist.") || StartsWith(name, "next_boot."); + if (socket && persistent_properties_loaded && need_persist) { + if (persist_write_thread) { + persist_write_thread->Write(name, value, std::move(*socket)); + return {}; + } + WritePersistentProperty(name, value); + } + } + + NotifyPropertyChange(name, value); + return {PROP_SUCCESS}; +} + +// Helper for PropertySet, for the case where no socket is used, and therefore an asynchronous +// return is not possible. +static uint32_t PropertySetNoSocket(const std::string& name, const std::string& value, + std::string* error) { + auto ret = PropertySet(name, value, nullptr, error); + CHECK(ret.has_value()); + return *ret; +} + +static uint32_t SendControlMessage(const std::string& msg, const std::string& name, pid_t pid, + SocketConnection* socket, std::string* error) { + auto lock = std::lock_guard{accept_messages_lock}; + if (!accept_messages) { + *error = "Received control message after shutdown, ignoring"; + return PROP_ERROR_HANDLE_CONTROL_MESSAGE; + } + + // We must release the fd before sending it to init, otherwise there will be a race with init. + // If init calls close() before Release(), then fdsan will see the wrong tag and abort(). + int fd = -1; + if (socket != nullptr && SelinuxGetVendorAndroidVersion() > __ANDROID_API_Q__) { + fd = socket->Release(); + } + + bool queue_success = QueueControlMessage(msg, name, pid, fd); + if (!queue_success && fd != -1) { + uint32_t response = PROP_ERROR_HANDLE_CONTROL_MESSAGE; + TEMP_FAILURE_RETRY(send(fd, &response, sizeof(response), 0)); + close(fd); + } + + return PROP_SUCCESS; +} + +bool CheckControlPropertyPerms(const std::string& name, const std::string& value, + const std::string& source_context, const ucred& cr) { + // We check the legacy method first but these properties are dontaudit, so we only log an audit + // if the newer method fails as well. We only do this with the legacy ctl. properties. + if (name == "ctl.start" || name == "ctl.stop" || name == "ctl.restart") { + // The legacy permissions model is that ctl. properties have their name ctl. and + // their value is the name of the service to apply that action to. Permissions for these + // actions are based on the service, so we must create a fake name of ctl. to + // check permissions. + auto control_string_legacy = "ctl." + value; + const char* target_context_legacy = nullptr; + const char* type_legacy = nullptr; + property_info_area->GetPropertyInfo(control_string_legacy.c_str(), &target_context_legacy, + &type_legacy); + + if (CheckMacPerms(control_string_legacy, target_context_legacy, source_context.c_str(), cr)) { + return true; + } + } + + auto control_string_full = name + "$" + value; + const char* target_context_full = nullptr; + const char* type_full = nullptr; + property_info_area->GetPropertyInfo(control_string_full.c_str(), &target_context_full, + &type_full); + + return CheckMacPerms(control_string_full, target_context_full, source_context.c_str(), cr); +} + +// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*. +uint32_t CheckPermissions(const std::string& name, const std::string& value, + const std::string& source_context, const ucred& cr, std::string* error) { + if (!IsLegalPropertyName(name)) { + *error = "Illegal property name"; + return PROP_ERROR_INVALID_NAME; + } + + if (StartsWith(name, "ctl.")) { + if (!CheckControlPropertyPerms(name, value, source_context, cr)) { + *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4, + value.c_str()); + return PROP_ERROR_HANDLE_CONTROL_MESSAGE; + } + + return PROP_SUCCESS; + } + + const char* target_context = nullptr; + const char* type = nullptr; + property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type); + + if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) { + *error = "SELinux permission check failed"; + return PROP_ERROR_PERMISSION_DENIED; + } + + if (!CheckType(type, value)) { + *error = StringPrintf("Property type check failed, value doesn't match expected type '%s'", + (type ?: "(null)")); + return PROP_ERROR_INVALID_VALUE; + } + + return PROP_SUCCESS; +} + +// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*, or std::nullopt +// if asynchronous. +std::optional HandlePropertySet(const std::string& name, const std::string& value, + const std::string& source_context, const ucred& cr, + SocketConnection* socket, std::string* error) { + if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) { + return {ret}; + } + + if (StartsWith(name, "ctl.")) { + return {SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error)}; + } + + // sys.powerctl is a special property that is used to make the device reboot. We want to log + // any process that sets this property to be able to accurately blame the cause of a shutdown. + if (name == "sys.powerctl") { + std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid); + std::string process_cmdline; + std::string process_log_string; + if (ReadFileToString(cmdline_path, &process_cmdline)) { + // Since cmdline is null deliminated, .c_str() conveniently gives us just the process + // path. + process_log_string = StringPrintf(" (%s)", process_cmdline.c_str()); + } + LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid + << process_log_string; + if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) { + *error = "Userspace reboot is not supported by this device"; + return {PROP_ERROR_INVALID_VALUE}; + } + } +#if 0 + // If a process other than init is writing a non-empty value, it means that process is + // requesting that init performs a restorecon operation on the path specified by 'value'. + // We use a thread to do this restorecon operation to prevent holding up init, as it may take + // a long time to complete. + if (name == kRestoreconProperty && cr.pid != 1 && !value.empty()) { + static AsyncRestorecon async_restorecon; + async_restorecon.TriggerRestorecon(value); + return {PROP_SUCCESS}; + } +#endif + return PropertySet(name, value, socket, error); +} + +// Helper for HandlePropertySet, for the case where no socket is used, and +// therefore an asynchronous return is not possible. +uint32_t HandlePropertySetNoSocket(const std::string& name, const std::string& value, + const std::string& source_context, const ucred& cr, + std::string* error) { + auto ret = HandlePropertySet(name, value, source_context, cr, nullptr, error); + CHECK(ret.has_value()); + return *ret; +} + +static void handle_property_set_fd(int fd) { + static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */ + + int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC); + if (s == -1) { + return; + } + + ucred cr; + socklen_t cr_size = sizeof(cr); + if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { + close(s); + PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED"; + return; + } + + SocketConnection socket(s, cr); + uint32_t timeout_ms = kDefaultSocketTimeout; + + uint32_t cmd = 0; + if (!socket.RecvUint32(&cmd, &timeout_ms)) { + PLOG(ERROR) << "sys_prop: error while reading command from the socket"; + socket.SendUint32(PROP_ERROR_READ_CMD); + return; + } + + switch (cmd) { + case PROP_MSG_SETPROP: { + char prop_name[PROP_NAME_MAX]; + char prop_value[PROP_VALUE_MAX]; + + if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) || + !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) { + PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket"; + return; + } + + prop_name[PROP_NAME_MAX-1] = 0; + prop_value[PROP_VALUE_MAX-1] = 0; + + std::string source_context; + if (!socket.GetSourceContext(&source_context)) { + PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed"; + return; + } + + const auto& cr = socket.cred(); + std::string error; + auto result = HandlePropertySetNoSocket(prop_name, prop_value, source_context, cr, &error); + if (result != PROP_SUCCESS) { + LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid + << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error; + } + + break; + } + + case PROP_MSG_SETPROP2: { + std::string name; + std::string value; + if (!socket.RecvString(&name, &timeout_ms) || + !socket.RecvString(&value, &timeout_ms)) { + PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket"; + socket.SendUint32(PROP_ERROR_READ_DATA); + return; + } + + std::string source_context; + if (!socket.GetSourceContext(&source_context)) { + PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed"; + socket.SendUint32(PROP_ERROR_PERMISSION_DENIED); + return; + } + + // HandlePropertySet takes ownership of the socket if the set is handled asynchronously. + const auto& cr = socket.cred(); + std::string error; + auto result = HandlePropertySet(name, value, source_context, cr, &socket, &error); + if (!result) { + // Result will be sent after completion. + return; + } + if (*result != PROP_SUCCESS) { + LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid + << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error; + } + socket.SendUint32(*result); + break; + } + + default: + LOG(ERROR) << "sys_prop: invalid command " << cmd; + socket.SendUint32(PROP_ERROR_INVALID_CMD); + break; + } +} + +uint32_t InitPropertySet(const std::string& name, const std::string& value) { + ucred cr = {.pid = 1, .uid = 0, .gid = 0}; + std::string error; + auto result = HandlePropertySetNoSocket(name, value, kInitContext, cr, &error); + if (result != PROP_SUCCESS) { + LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error; + } + + return result; +} + +static Result load_properties_from_file(const char*, const char*, + std::map*); + +/* + * Filter is used to decide which properties to load: NULL loads all keys, + * "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match. + */ +static void LoadProperties(char* data, const char* filter, const char* filename, + std::map* properties) { + char *key, *value, *eol, *sol, *tmp, *fn; + size_t flen = 0; + + static constexpr const char* const kVendorPathPrefixes[4] = { + "/vendor", + "/odm", + "/vendor_dlkm", + "/odm_dlkm", + }; + + const char* context = kInitContext; + if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_P__) { + for (const auto& vendor_path_prefix : kVendorPathPrefixes) { + if (StartsWith(filename, vendor_path_prefix)) { + context = kVendorContext; + } + } + } + + if (filter) { + flen = strlen(filter); + } + + sol = data; + while ((eol = strchr(sol, '\n'))) { + key = sol; + *eol++ = 0; + sol = eol; + + while (isspace(*key)) key++; + if (*key == '#') continue; + + tmp = eol - 2; + while ((tmp > key) && isspace(*tmp)) *tmp-- = 0; + + if (!strncmp(key, "import ", 7) && flen == 0) { + fn = key + 7; + while (isspace(*fn)) fn++; + + key = strchr(fn, ' '); + if (key) { + *key++ = 0; + while (isspace(*key)) key++; + } + + std::string raw_filename(fn); + auto expanded_filename = ExpandProps(raw_filename); + + if (!expanded_filename.ok()) { + LOG(ERROR) << "Could not expand filename ': " << expanded_filename.error(); + continue; + } + + if (auto res = load_properties_from_file(expanded_filename->c_str(), key, properties); + !res.ok()) { + LOG(WARNING) << res.error(); + } + } else { + value = strchr(key, '='); + if (!value) continue; + *value++ = 0; + + tmp = value - 2; + while ((tmp > key) && isspace(*tmp)) *tmp-- = 0; + + while (isspace(*value)) value++; + + if (flen > 0) { + if (filter[flen - 1] == '*') { + if (strncmp(key, filter, flen - 1) != 0) continue; + } else { + if (strcmp(key, filter) != 0) continue; + } + } + + if (StartsWith(key, "ctl.") || key == "sys.powerctl"s || + std::string{key} == kRestoreconProperty) { + LOG(ERROR) << "Ignoring disallowed property '" << key + << "' with special meaning in prop file '" << filename << "'"; + continue; + } + + ucred cr = {.pid = 1, .uid = 0, .gid = 0}; + std::string error; + if (CheckPermissions(key, value, context, cr, &error) == PROP_SUCCESS) { + auto it = properties->find(key); + if (it == properties->end()) { + (*properties)[key] = value; + } else if (it->second != value) { + LOG(WARNING) << "Overriding previous property '" << key << "':'" << it->second + << "' with new value '" << value << "'"; + it->second = value; + } + } else { + LOG(ERROR) << "Do not have permissions to set '" << key << "' to '" << value + << "' in property file '" << filename << "': " << error; + } + } + } +} + +// Filter is used to decide which properties to load: NULL loads all keys, +// "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match. +static Result load_properties_from_file(const char* filename, const char* filter, + std::map* properties) { + Timer t; + auto file_contents = ReadFile(filename); + if (!file_contents.ok()) { + return Error() << "Couldn't load property file '" << filename + << "': " << file_contents.error(); + } + file_contents->push_back('\n'); + + LoadProperties(file_contents->data(), filter, filename, properties); + LOG(VERBOSE) << "(Loading properties from " << filename << " took " << t << ".)"; + return {}; +} + +static void LoadPropertiesFromSecondStageRes(std::map* properties) { + std::string prop = GetRamdiskPropForSecondStage(); + if (access(prop.c_str(), R_OK) != 0) { + CHECK(errno == ENOENT) << "Cannot access " << prop << ": " << strerror(errno); + return; + } + if (auto res = load_properties_from_file(prop.c_str(), nullptr, properties); !res.ok()) { + LOG(WARNING) << res.error(); + } +} + +// persist.sys.usb.config values can't be combined on build-time when property +// files are split into each partition. +// So we need to apply the same rule of build/make/tools/post_process_props.py +// on runtime. +static void update_sys_usb_config() { + bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false); + std::string config = android::base::GetProperty("persist.sys.usb.config", ""); + // b/150130503, add (config == "none") condition here to prevent appending + // ",adb" if "none" is explicitly defined in default prop. + if (config.empty() || config == "none") { + InitPropertySet("persist.sys.usb.config", is_debuggable ? "adb" : "none"); + } else if (is_debuggable && config.find("adb") == std::string::npos && + config.length() + 4 < PROP_VALUE_MAX) { + config.append(",adb"); + InitPropertySet("persist.sys.usb.config", config); + } +} + +static void load_override_properties() { + if (ALLOW_LOCAL_PROP_OVERRIDE) { + std::map properties; + load_properties_from_file("/data/local.prop", nullptr, &properties); + for (const auto& [name, value] : properties) { + std::string error; + if (PropertySetNoSocket(name, value, &error) != PROP_SUCCESS) { + LOG(ERROR) << "Could not set '" << name << "' to '" << value + << "' in /data/local.prop: " << error; + } + } + } +} + +// If the ro.product.[brand|device|manufacturer|model|name] properties have not been explicitly +// set, derive them from ro.product.${partition}.* properties +static void property_initialize_ro_product_props() { + const char* RO_PRODUCT_PROPS_PREFIX = "ro.product."; + const char* RO_PRODUCT_PROPS[] = { + "brand", "device", "manufacturer", "model", "name", + }; + const char* RO_PRODUCT_PROPS_ALLOWED_SOURCES[] = { + "odm", "product", "system_ext", "system", "vendor", + }; + const char* RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = "product,odm,vendor,system_ext,system"; + const std::string EMPTY = ""; + + std::string ro_product_props_source_order = + GetProperty("ro.product.property_source_order", EMPTY); + + if (!ro_product_props_source_order.empty()) { + // Verify that all specified sources are valid + for (const auto& source : Split(ro_product_props_source_order, ",")) { + // Verify that the specified source is valid + bool is_allowed_source = false; + for (const auto& allowed_source : RO_PRODUCT_PROPS_ALLOWED_SOURCES) { + if (source == allowed_source) { + is_allowed_source = true; + break; + } + } + if (!is_allowed_source) { + LOG(ERROR) << "Found unexpected source in ro.product.property_source_order; " + "using the default property source order"; + ro_product_props_source_order = RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER; + break; + } + } + } else { + ro_product_props_source_order = RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER; + } + + for (const auto& ro_product_prop : RO_PRODUCT_PROPS) { + std::string base_prop(RO_PRODUCT_PROPS_PREFIX); + base_prop += ro_product_prop; + + std::string base_prop_val = GetProperty(base_prop, EMPTY); + if (!base_prop_val.empty()) { + continue; + } + + for (const auto& source : Split(ro_product_props_source_order, ",")) { + std::string target_prop(RO_PRODUCT_PROPS_PREFIX); + target_prop += source; + target_prop += '.'; + target_prop += ro_product_prop; + + std::string target_prop_val = GetProperty(target_prop, EMPTY); + if (!target_prop_val.empty()) { + LOG(INFO) << "Setting product property " << base_prop << " to '" << target_prop_val + << "' (from " << target_prop << ")"; + std::string error; + auto res = PropertySetNoSocket(base_prop, target_prop_val, &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Error setting product property " << base_prop << ": err=" << res + << " (" << error << ")"; + } + break; + } + } + } +} + +static void property_initialize_build_id() { + std::string build_id = GetProperty(ID_PROP, ""); + if (!build_id.empty()) { + return; + } + + std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, ""); + std::string vbmeta_digest = GetProperty(VBMETA_DIGEST_PROP, ""); + if (vbmeta_digest.size() < DIGEST_SIZE_USED) { + LOG(ERROR) << "vbmeta digest size too small " << vbmeta_digest; + // Still try to set the id field in the unexpected case. + build_id = legacy_build_id; + } else { + // Derive the ro.build.id by appending the vbmeta digest to the base value. + build_id = legacy_build_id + "." + vbmeta_digest.substr(0, DIGEST_SIZE_USED); + } + + std::string error; + auto res = PropertySetNoSocket(ID_PROP, build_id, &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Failed to set " << ID_PROP << " to " << build_id; + } +} + +static std::string ConstructBuildFingerprint(bool legacy) { + const std::string UNKNOWN = "unknown"; + std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN); + build_fingerprint += '/'; + build_fingerprint += GetProperty("ro.product.name", UNKNOWN); + build_fingerprint += '/'; + build_fingerprint += GetProperty("ro.product.device", UNKNOWN); + build_fingerprint += ':'; + build_fingerprint += GetProperty("ro.build.version.release_or_codename", UNKNOWN); + build_fingerprint += '/'; + + std::string build_id = + legacy ? GetProperty(LEGACY_ID_PROP, UNKNOWN) : GetProperty(ID_PROP, UNKNOWN); + build_fingerprint += build_id; + build_fingerprint += '/'; + build_fingerprint += GetProperty("ro.build.version.incremental", UNKNOWN); + build_fingerprint += ':'; + build_fingerprint += GetProperty("ro.build.type", UNKNOWN); + build_fingerprint += '/'; + build_fingerprint += GetProperty("ro.build.tags", UNKNOWN); + + return build_fingerprint; +} + +// Derive the legacy build fingerprint if we overwrite the build id at runtime. +static void property_derive_legacy_build_fingerprint() { + std::string legacy_build_fingerprint = GetProperty(LEGACY_FINGERPRINT_PROP, ""); + if (!legacy_build_fingerprint.empty()) { + return; + } + + // The device doesn't have a legacy build id, skipping the legacy fingerprint. + std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, ""); + if (legacy_build_id.empty()) { + return; + } + + legacy_build_fingerprint = ConstructBuildFingerprint(true /* legacy fingerprint */); + LOG(INFO) << "Setting property '" << LEGACY_FINGERPRINT_PROP << "' to '" + << legacy_build_fingerprint << "'"; + + std::string error; + auto res = PropertySetNoSocket(LEGACY_FINGERPRINT_PROP, legacy_build_fingerprint, &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Error setting property '" << LEGACY_FINGERPRINT_PROP << "': err=" << res + << " (" << error << ")"; + } +} + +// If the ro.build.fingerprint property has not been set, derive it from constituent pieces +static void property_derive_build_fingerprint() { + std::string build_fingerprint = GetProperty("ro.build.fingerprint", ""); + if (!build_fingerprint.empty()) { + return; + } + + build_fingerprint = ConstructBuildFingerprint(false /* legacy fingerprint */); + LOG(INFO) << "Setting property '" << FINGERPRINT_PROP << "' to '" << build_fingerprint << "'"; + + std::string error; + auto res = PropertySetNoSocket(FINGERPRINT_PROP, build_fingerprint, &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Error setting property '" << FINGERPRINT_PROP << "': err=" << res << " (" + << error << ")"; + } +} + +// If the ro.product.cpu.abilist* properties have not been explicitly +// set, derive them from ro.${partition}.product.cpu.abilist* properties. +static void property_initialize_ro_cpu_abilist() { + // From high to low priority. + const char* kAbilistSources[] = { + "product", + "odm", + "vendor", + "system", + }; + const std::string EMPTY = ""; + const char* kAbilistProp = "ro.product.cpu.abilist"; + const char* kAbilist32Prop = "ro.product.cpu.abilist32"; + const char* kAbilist64Prop = "ro.product.cpu.abilist64"; + + // If the properties are defined explicitly, just use them. + if (GetProperty(kAbilistProp, EMPTY) != EMPTY) { + return; + } + + // Find the first source defining these properties by order. + std::string abilist32_prop_val; + std::string abilist64_prop_val; + for (const auto& source : kAbilistSources) { + const auto abilist32_prop = std::string("ro.") + source + ".product.cpu.abilist32"; + const auto abilist64_prop = std::string("ro.") + source + ".product.cpu.abilist64"; + abilist32_prop_val = GetProperty(abilist32_prop, EMPTY); + abilist64_prop_val = GetProperty(abilist64_prop, EMPTY); + // The properties could be empty on 32-bit-only or 64-bit-only devices, + // but we cannot identify a property is empty or undefined by GetProperty(). + // So, we assume both of these 2 properties are empty as undefined. + if (abilist32_prop_val != EMPTY || abilist64_prop_val != EMPTY) { + break; + } + } + + // Merge ABI lists for ro.product.cpu.abilist + auto abilist_prop_val = abilist64_prop_val; + if (abilist32_prop_val != EMPTY) { + if (abilist_prop_val != EMPTY) { + abilist_prop_val += ","; + } + abilist_prop_val += abilist32_prop_val; + } + + // Set these properties + const std::pair set_prop_list[] = { + {kAbilistProp, abilist_prop_val}, + {kAbilist32Prop, abilist32_prop_val}, + {kAbilist64Prop, abilist64_prop_val}, + }; + for (const auto& [prop, prop_val] : set_prop_list) { + LOG(INFO) << "Setting property '" << prop << "' to '" << prop_val << "'"; + + std::string error; + auto res = PropertySetNoSocket(prop, prop_val, &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Error setting property '" << prop << "': err=" << res << " (" << error + << ")"; + } + } +} + +static void property_initialize_ro_vendor_api_level() { + // ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are + // required to support. + constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level"; + + auto vendor_api_level = GetIntProperty("ro.board.first_api_level", __ANDROID_VENDOR_API_MAX__); + if (vendor_api_level != __ANDROID_VENDOR_API_MAX__) { + // Update the vendor_api_level with "ro.board.api_level" only if both "ro.board.api_level" + // and "ro.board.first_api_level" are defined. + vendor_api_level = GetIntProperty("ro.board.api_level", vendor_api_level); + } + + auto product_first_api_level = + GetIntProperty("ro.product.first_api_level", __ANDROID_API_FUTURE__); + if (product_first_api_level == __ANDROID_API_FUTURE__) { + // Fallback to "ro.build.version.sdk" if the "ro.product.first_api_level" is not defined. + product_first_api_level = GetIntProperty("ro.build.version.sdk", __ANDROID_API_FUTURE__); + } + + vendor_api_level = + std::min(AVendorSupport_getVendorApiLevelOf(product_first_api_level), vendor_api_level); + + if (vendor_api_level < 0) { + LOG(ERROR) << "Unexpected vendor api level for " << VENDOR_API_LEVEL_PROP << ". Check " + << "ro.product.first_api_level and ro.build.version.sdk."; + vendor_api_level = __ANDROID_VENDOR_API_MAX__; + } + + std::string error; + auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(vendor_api_level), &error); + if (res != PROP_SUCCESS) { + LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << vendor_api_level + << ": " << error << "(" << res << ")"; + } +} + +void PropertyLoadBootDefaults() { + // We read the properties and their values into a map, in order to always allow properties + // loaded in the later property files to override the properties in loaded in the earlier + // property files, regardless of if they are "ro." properties or not. + std::map properties; + + if (IsRecoveryMode()) { + if (auto res = load_properties_from_file("/prop.default", nullptr, &properties); + !res.ok()) { + LOG(ERROR) << res.error(); + } + } + + // //etc/build.prop is the canonical location of the build-time properties since S. + // Falling back to //defalt.prop and //build.prop only when legacy path has to + // be supported, which is controlled by the support_legacy_path_until argument. + const auto load_properties_from_partition = [&properties](const std::string& partition, + int support_legacy_path_until) { + auto path = "/" + partition + "/etc/build.prop"; + if (load_properties_from_file(path.c_str(), nullptr, &properties).ok()) { + return; + } + // To read ro..build.version.sdk, temporarily load the legacy paths into a + // separate map. Then by comparing its value with legacy_version, we know that if the + // partition is old enough so that we need to respect the legacy paths. + std::map temp; + auto legacy_path1 = "/" + partition + "/default.prop"; + auto legacy_path2 = "/" + partition + "/build.prop"; + load_properties_from_file(legacy_path1.c_str(), nullptr, &temp); + load_properties_from_file(legacy_path2.c_str(), nullptr, &temp); + bool support_legacy_path = false; + auto version_prop_name = "ro." + partition + ".build.version.sdk"; + auto it = temp.find(version_prop_name); + if (it == temp.end()) { + // This is embarassing. Without the prop, we can't determine how old the partition is. + // Let's be conservative by assuming it is very very old. + support_legacy_path = true; + } else if (int value; + ParseInt(it->second.c_str(), &value) && value <= support_legacy_path_until) { + support_legacy_path = true; + } + if (support_legacy_path) { + // We don't update temp into properties directly as it might skip any (future) logic + // for resolving duplicates implemented in load_properties_from_file. Instead, read + // the files again into the properties map. + load_properties_from_file(legacy_path1.c_str(), nullptr, &properties); + load_properties_from_file(legacy_path2.c_str(), nullptr, &properties); + } else { + LOG(FATAL) << legacy_path1 << " and " << legacy_path2 << " were not loaded " + << "because " << version_prop_name << "(" << it->second << ") is newer " + << "than " << support_legacy_path_until; + } + }; + + // Order matters here. The more the partition is specific to a product, the higher its + // precedence is. + LoadPropertiesFromSecondStageRes(&properties); + + // system should have build.prop, unlike the other partitions + if (auto res = load_properties_from_file("/system/build.prop", nullptr, &properties); + !res.ok()) { + LOG(WARNING) << res.error(); + } + + load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30); + load_properties_from_file("/system_dlkm/etc/build.prop", nullptr, &properties); + // TODO(b/117892318): uncomment the following condition when vendor.imgs for aosp_* targets are + // all updated. + // if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_R__) { + load_properties_from_file("/vendor/default.prop", nullptr, &properties); + // } + load_properties_from_file("/vendor/build.prop", nullptr, &properties); + load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties); + load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties); + load_properties_from_partition("odm", /* support_legacy_path_until */ 28); + load_properties_from_partition("product", /* support_legacy_path_until */ 30); + + if (access(kDebugRamdiskProp, R_OK) == 0) { + LOG(INFO) << "Loading " << kDebugRamdiskProp; + if (auto res = load_properties_from_file(kDebugRamdiskProp, nullptr, &properties); + !res.ok()) { + LOG(WARNING) << res.error(); + } + } + + for (const auto& [name, value] : properties) { + std::string error; + if (PropertySetNoSocket(name, value, &error) != PROP_SUCCESS) { + LOG(ERROR) << "Could not set '" << name << "' to '" << value + << "' while loading .prop files" << error; + } + } + + property_initialize_ro_product_props(); + property_initialize_build_id(); + property_derive_build_fingerprint(); + property_derive_legacy_build_fingerprint(); + property_initialize_ro_cpu_abilist(); + property_initialize_ro_vendor_api_level(); + + update_sys_usb_config(); +} + +bool LoadPropertyInfoFromFile(const std::string& filename, + std::vector* property_infos) { + auto file_contents = std::string(); + if (!ReadFileToString(filename, &file_contents)) { + PLOG(ERROR) << "Could not read properties from '" << filename << "'"; + return false; + } + + auto errors = std::vector{}; + bool require_prefix_or_exact = SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__; + ParsePropertyInfoFile(file_contents, require_prefix_or_exact, property_infos, &errors); + // Individual parsing errors are reported but do not cause a failed boot, which is what + // returning false would do here. + for (const auto& error : errors) { + LOG(ERROR) << "Could not read line from '" << filename << "': " << error; + } + + return true; +} + +void CreateSerializedPropertyInfo() { + auto property_infos = std::vector(); + if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) { + if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts", + &property_infos)) { + return; + } + // Don't check for failure here, since we don't always have all of these partitions. + // E.g. In case of recovery, the vendor partition will not have mounted and we + // still need the system / platform properties to function. + if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) { + LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts", + &property_infos); + } + if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) { + LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts", + &property_infos); + } + if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) { + LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts", + &property_infos); + } + if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) { + LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos); + } + } else { + if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) { + return; + } + LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos); + LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos); + LoadPropertyInfoFromFile("/product_property_contexts", &property_infos); + LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos); + } + + auto serialized_contexts = std::string(); + auto error = std::string(); + if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts, + &error)) { + LOG(ERROR) << "Unable to serialize property contexts: " << error; + return; + } + + if (!WriteStringToFile(serialized_contexts, PROP_TREE_FILE, 0444, 0, 0, false)) { + PLOG(ERROR) << "Unable to write serialized property infos to file"; + } + selinux_android_restorecon(PROP_TREE_FILE, 0); + + mkdir(APPCOMPAT_OVERRIDE_PROP_FOLDERNAME, S_IRWXU | S_IXGRP | S_IXOTH); + if (!WriteStringToFile(serialized_contexts, APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0444, 0, 0, + false)) { + PLOG(ERROR) << "Unable to write appcompat override property infos to file"; + } + selinux_android_restorecon(APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0); +} + +static void ExportKernelBootProps() { + constexpr const char* UNSET = ""; + struct { + const char* src_prop; + const char* dst_prop; + const char* default_value; + } prop_map[] = { + // clang-format off + { "ro.boot.serialno", "ro.serialno", UNSET, }, + { "ro.boot.mode", "ro.bootmode", "unknown", }, + { "ro.boot.baseband", "ro.baseband", "unknown", }, + { "ro.boot.bootloader", "ro.bootloader", "unknown", }, + { "ro.boot.hardware", "ro.hardware", "unknown", }, + { "ro.boot.revision", "ro.revision", "0", }, + // clang-format on + }; + for (const auto& prop : prop_map) { + std::string value = GetProperty(prop.src_prop, prop.default_value); + if (value != UNSET) InitPropertySet(prop.dst_prop, value); + } +} + +static void ProcessKernelDt() { + if (!is_android_dt_value_expected("compatible", "android,firmware")) { + return; + } + + std::unique_ptr dir(opendir(android::fs_mgr::GetAndroidDtDir().c_str()), + closedir); + if (!dir) return; + + std::string dt_file; + struct dirent* dp; + while ((dp = readdir(dir.get())) != NULL) { + if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || + !strcmp(dp->d_name, "name")) { + continue; + } + + std::string file_name = android::fs_mgr::GetAndroidDtDir() + dp->d_name; + + android::base::ReadFileToString(file_name, &dt_file); + std::replace(dt_file.begin(), dt_file.end(), ',', '.'); + + InitPropertySet("ro.boot."s + dp->d_name, dt_file); + } +} + +constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv; + +static void ProcessKernelCmdline() { + android::fs_mgr::ImportKernelCmdline([&](const std::string& key, const std::string& value) { + if (StartsWith(key, ANDROIDBOOT_PREFIX)) { + InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value); + } + }); +} + + +static void ProcessBootconfig() { + android::fs_mgr::ImportBootconfig([&](const std::string& key, const std::string& value) { + if (StartsWith(key, ANDROIDBOOT_PREFIX)) { + InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value); + } + }); +} + +void PropertyInit() { +#if 0 + selinux_callback cb; + cb.func_audit = PropertyAuditCallback; + selinux_set_callback(SELINUX_CB_AUDIT, cb); +#endif + + mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); + CreateSerializedPropertyInfo(); + if (__system_property_area_init()) { + LOG(FATAL) << "Failed to initialize property area"; + } + if (!property_info_area.LoadDefaultPath()) { + LOG(FATAL) << "Failed to load serialized property info file"; + } + + // If arguments are passed both on the command line and in DT, + // properties set in DT always have priority over the command-line ones. + ProcessKernelDt(); + ProcessKernelCmdline(); + ProcessBootconfig(); + + // Propagate the kernel variables to internal variables + // used by init as well as the current required properties. + ExportKernelBootProps(); + + PropertyLoadBootDefaults(); +} + +static void HandleInitSocket() { + auto message = ReadMessage(init_socket); + if (!message.ok()) { + LOG(ERROR) << "Could not read message from init_dedicated_recv_socket: " << message.error(); + return; + } + + auto init_message = InitMessage{}; + if (!init_message.ParseFromString(*message)) { + LOG(ERROR) << "Could not parse message from init"; + return; + } + + switch (init_message.msg_case()) { + case InitMessage::kLoadPersistentProperties: { + load_override_properties(); + + auto persistent_properties = LoadPersistentProperties(); + for (const auto& property_record : persistent_properties.properties()) { + auto const& prop_name = property_record.name(); + auto const& prop_value = property_record.value(); + InitPropertySet(prop_name, prop_value); + } + + // Apply debug ramdisk special settings after persistent properties are loaded. + if (android::base::GetBoolProperty("ro.force.debuggable", false)) { + // Always enable usb adb if device is booted with debug ramdisk. + update_sys_usb_config(); + } + InitPropertySet("ro.persistent_properties.ready", "true"); + persistent_properties_loaded = true; + break; + } + default: + LOG(ERROR) << "Unknown message type from init: " << init_message.msg_case(); + } +} + +static void PropertyServiceThread(int fd, bool listen_init) { + Epoll epoll; + if (auto result = epoll.Open(); !result.ok()) { + LOG(FATAL) << result.error(); + } + + if (auto result = epoll.RegisterHandler(fd, std::bind(handle_property_set_fd, fd)); + !result.ok()) { + LOG(FATAL) << result.error(); + } + + if (listen_init) { + if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) { + LOG(FATAL) << result.error(); + } + } + + while (true) { + auto epoll_result = epoll.Wait(std::nullopt); + if (!epoll_result.ok()) { + LOG(ERROR) << epoll_result.error(); + } + } +} + +PersistWriteThread::PersistWriteThread() { + auto new_thread = std::thread([this]() -> void { Work(); }); + thread_.swap(new_thread); +} + +void PersistWriteThread::Work() { + while (true) { + std::tuple item; + + // Grab the next item within the lock. + { + std::unique_lock lock(mutex_); + + while (work_.empty()) { + cv_.wait(lock); + } + + item = std::move(work_.front()); + work_.pop_front(); + } + + // Perform write/fsync outside the lock. + WritePersistentProperty(std::get<0>(item), std::get<1>(item)); + NotifyPropertyChange(std::get<0>(item), std::get<1>(item)); + + SocketConnection& socket = std::get<2>(item); + socket.SendUint32(PROP_SUCCESS); + } +} + +void PersistWriteThread::Write(std::string name, std::string value, SocketConnection socket) { + { + std::unique_lock lock(mutex_); + work_.emplace_back(std::move(name), std::move(value), std::move(socket)); + } + cv_.notify_all(); +} + +void StartThread(const char* name, int mode, int gid, std::thread& t, bool listen_init) { + int fd = -1; + if (auto result = CreateSocket(name, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + /*passcred=*/false, /*should_listen=*/false, mode, /*uid=*/0, + /*gid=*/gid, /*socketcon=*/{}); + result.ok()) { + fd = *result; + } else { + LOG(FATAL) << "start_property_service socket creation failed: " << result.error(); + } + + listen(fd, 8); + + auto new_thread = std::thread(PropertyServiceThread, fd, listen_init); + t.swap(new_thread); +} + +void StartPropertyService(int* epoll_socket) { + InitPropertySet("ro.property_service.version", "2"); + + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) { + PLOG(FATAL) << "Failed to socketpair() between property_service and init"; + } + *epoll_socket = from_init_socket = sockets[0]; + init_socket = sockets[1]; + StartSendingMessages(); + + StartThread(PROP_SERVICE_FOR_SYSTEM_NAME, 0660, AID_SYSTEM, property_service_for_system_thread, + true); + StartThread(PROP_SERVICE_NAME, 0666, 0, property_service_thread, false); + + auto async_persist_writes = + android::base::GetBoolProperty("ro.property_service.async_persist_writes", false); + + if (async_persist_writes) { + persist_write_thread = std::make_unique(); + } +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/selinux.cpp b/aosp/system/core/init/selinux.cpp new file mode 100644 index 000000000..50d7c48b1 --- /dev/null +++ b/aosp/system/core/init/selinux.cpp @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2017 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. + */ + +// This file contains the functions that initialize SELinux during boot as well as helper functions +// for SELinux operation for init. + +// When the system boots, there is no SEPolicy present and init is running in the kernel domain. +// Init loads the SEPolicy from the file system, restores the context of /system/bin/init based on +// this SEPolicy, and finally exec()'s itself to run in the proper domain. + +// The SEPolicy on Android comes in two variants: monolithic and split. + +// The monolithic policy variant is for legacy non-treble devices that contain a single SEPolicy +// file located at /sepolicy and is directly loaded into the kernel SELinux subsystem. + +// The split policy is for supporting treble devices. It splits the SEPolicy across files on +// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'vendor' +// portion of the policy). This is necessary to allow the system image to be updated independently +// of the vendor image, while maintaining contributions from both partitions in the SEPolicy. This +// is especially important for VTS testing, where the SEPolicy on the Google System Image may not be +// identical to the system image shipped on a vendor's device. + +// The split SEPolicy is loaded as described below: +// 1) There is a precompiled SEPolicy located at either /vendor/etc/selinux/precompiled_sepolicy or +// /odm/etc/selinux/precompiled_sepolicy if odm parition is present. Stored along with this file +// are the sha256 hashes of the parts of the SEPolicy on /system, /system_ext and /product that +// were used to compile this precompiled policy. The system partition contains a similar sha256 +// of the parts of the SEPolicy that it currently contains. Symmetrically, system_ext and +// product paritition contain sha256 hashes of their SEPolicy. The init loads this +// precompiled_sepolicy directly if and only if the hashes along with the precompiled SEPolicy on +// /vendor or /odm match the hashes for system, system_ext and product SEPolicy, respectively. +// 2) If these hashes do not match, then either /system or /system_ext or /product (or some of them) +// have been updated out of sync with /vendor (or /odm if it is present) and the init needs to +// compile the SEPolicy. /system contains the SEPolicy compiler, secilc, and it is used by the +// OpenSplitPolicy() function below to compile the SEPolicy to a temp directory and load it. +// That function contains even more documentation with the specific implementation details of how +// the SEPolicy is compiled if needed. + +#include "selinux.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "block_dev_initializer.h" +#include "debug_ramdisk.h" +#include "reboot_utils.h" +#include "snapuserd_transition.h" +#include "util.h" + +using namespace std::string_literals; + +using android::base::ParseInt; +using android::base::Timer; +using android::base::unique_fd; +using android::fs_mgr::AvbHandle; +using android::snapshot::SnapshotManager; + +namespace android { +namespace init { + +namespace { + +enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING }; + +EnforcingStatus StatusFromProperty() { + std::string value; + if (android::fs_mgr::GetKernelCmdline("androidboot.selinux", &value) && value == "permissive") { + return SELINUX_PERMISSIVE; + } + if (android::fs_mgr::GetBootconfig("androidboot.selinux", &value) && value == "permissive") { + return SELINUX_PERMISSIVE; + } + return SELINUX_ENFORCING; +} + +bool IsEnforcing() { + aosp_hack_p(false); + if (ALLOW_PERMISSIVE_SELINUX) { + return StatusFromProperty() == SELINUX_ENFORCING; + } + return true; +} + +// Forks, executes the provided program in the child, and waits for the completion in the parent. +// Child's stderr is captured and logged using LOG(ERROR). +bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) { + // Create a pipe used for redirecting child process's output. + // * pipe_fds[0] is the FD the parent will use for reading. + // * pipe_fds[1] is the FD the child will use for writing. + int pipe_fds[2]; + if (pipe(pipe_fds) == -1) { + PLOG(ERROR) << "Failed to create pipe"; + return false; + } + + pid_t child_pid = fork(); + if (child_pid == -1) { + PLOG(ERROR) << "Failed to fork for " << filename; + return false; + } + + if (child_pid == 0) { + // fork succeeded -- this is executing in the child process + + // Close the pipe FD not used by this process + close(pipe_fds[0]); + + // Redirect stderr to the pipe FD provided by the parent + if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) { + PLOG(ERROR) << "Failed to redirect stderr of " << filename; + _exit(127); + return false; + } + close(pipe_fds[1]); + + if (execv(filename, argv) == -1) { + PLOG(ERROR) << "Failed to execve " << filename; + return false; + } + // Unreachable because execve will have succeeded and replaced this code + // with child process's code. + _exit(127); + return false; + } else { + // fork succeeded -- this is executing in the original/parent process + + // Close the pipe FD not used by this process + close(pipe_fds[1]); + + // Log the redirected output of the child process. + // It's unfortunate that there's no standard way to obtain an istream for a file descriptor. + // As a result, we're buffering all output and logging it in one go at the end of the + // invocation, instead of logging it as it comes in. + const int child_out_fd = pipe_fds[0]; + std::string child_output; + if (!android::base::ReadFdToString(child_out_fd, &child_output)) { + PLOG(ERROR) << "Failed to capture full output of " << filename; + } + close(child_out_fd); + if (!child_output.empty()) { + // Log captured output, line by line, because LOG expects to be invoked for each line + std::istringstream in(child_output); + std::string line; + while (std::getline(in, line)) { + LOG(ERROR) << filename << ": " << line; + } + } + + // Wait for child to terminate + int status; + if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) { + PLOG(ERROR) << "Failed to wait for " << filename; + return false; + } + + if (WIFEXITED(status)) { + int status_code = WEXITSTATUS(status); + if (status_code == 0) { + return true; + } else { + LOG(ERROR) << filename << " exited with status " << status_code; + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status); + } else if (WIFSTOPPED(status)) { + LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status); + } else { + LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status; + } + + return false; + } +} + +bool ReadFirstLine(const char* file, std::string* line) { + line->clear(); + + std::string contents; + if (!android::base::ReadFileToString(file, &contents, true /* follow symlinks */)) { + return false; + } + std::istringstream in(contents); + std::getline(in, *line); + return true; +} + +Result FindPrecompiledSplitPolicy() { + std::string precompiled_sepolicy; + // If there is an odm partition, precompiled_sepolicy will be in + // odm/etc/selinux. Otherwise it will be in vendor/etc/selinux. + static constexpr const char vendor_precompiled_sepolicy[] = + "/vendor/etc/selinux/precompiled_sepolicy"; + static constexpr const char odm_precompiled_sepolicy[] = + "/odm/etc/selinux/precompiled_sepolicy"; + if (access(odm_precompiled_sepolicy, R_OK) == 0) { + precompiled_sepolicy = odm_precompiled_sepolicy; + } else if (access(vendor_precompiled_sepolicy, R_OK) == 0) { + precompiled_sepolicy = vendor_precompiled_sepolicy; + } else { + return ErrnoError() << "No precompiled sepolicy at " << vendor_precompiled_sepolicy; + } + + // Use precompiled sepolicy only when all corresponding hashes are equal. + std::vector> sepolicy_hashes{ + {"/system/etc/selinux/plat_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"}, + {"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"}, + {"/product/etc/selinux/product_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"}, + }; + + for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) { + // Both of them should exist or both of them shouldn't exist. + if (access(actual_id_path.c_str(), R_OK) != 0) { + if (access(precompiled_id_path.c_str(), R_OK) == 0) { + return Error() << precompiled_id_path << " exists but " << actual_id_path + << " doesn't"; + } + continue; + } + + std::string actual_id; + if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) { + return ErrnoError() << "Failed to read " << actual_id_path; + } + + std::string precompiled_id; + if (!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) { + return ErrnoError() << "Failed to read " << precompiled_id_path; + } + + if (actual_id.empty() || actual_id != precompiled_id) { + return Error() << actual_id_path << " and " << precompiled_id_path << " differ"; + } + } + + return precompiled_sepolicy; +} + +bool GetVendorMappingVersion(std::string* plat_vers) { + if (!ReadFirstLine("/vendor/etc/selinux/plat_sepolicy_vers.txt", plat_vers)) { + PLOG(ERROR) << "Failed to read /vendor/etc/selinux/plat_sepolicy_vers.txt"; + return false; + } + if (plat_vers->empty()) { + LOG(ERROR) << "No version present in plat_sepolicy_vers.txt"; + return false; + } + return true; +} + +constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil"; + +bool IsSplitPolicyDevice() { + return access(plat_policy_cil_file, R_OK) != -1; +} + +std::optional GetUserdebugPlatformPolicyFile() { + // See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil. + const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE"); + if (force_debuggable_env && "true"s == force_debuggable_env && AvbHandle::IsDeviceUnlocked()) { + const std::vector debug_policy_candidates = { +#if INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT == 1 + "/system_ext/etc/selinux/userdebug_plat_sepolicy.cil", +#endif + kDebugRamdiskSEPolicy, + }; + for (const char* debug_policy : debug_policy_candidates) { + if (access(debug_policy, F_OK) == 0) { + return debug_policy; + } + } + } + return std::nullopt; +} + +struct PolicyFile { + unique_fd fd; + std::string path; +}; + +bool OpenSplitPolicy(PolicyFile* policy_file) { + // IMPLEMENTATION NOTE: Split policy consists of three or more CIL files: + // * platform -- policy needed due to logic contained in the system image, + // * vendor -- policy needed due to logic contained in the vendor image, + // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy + // with newer versions of platform policy. + // * (optional) policy needed due to logic on product, system_ext, or odm images. + // secilc is invoked to compile the above three policy files into a single monolithic policy + // file. This file is then loaded into the kernel. + + const auto userdebug_plat_sepolicy = GetUserdebugPlatformPolicyFile(); + const bool use_userdebug_policy = userdebug_plat_sepolicy.has_value(); + if (use_userdebug_policy) { + LOG(INFO) << "Using userdebug system sepolicy " << *userdebug_plat_sepolicy; + } + + // Load precompiled policy from vendor image, if a matching policy is found there. The policy + // must match the platform policy on the system image. + // use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil. + // Thus it cannot use the precompiled policy from vendor image. + if (!use_userdebug_policy) { + if (auto res = FindPrecompiledSplitPolicy(); res.ok()) { + unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); + if (fd != -1) { + policy_file->fd = std::move(fd); + policy_file->path = std::move(*res); + return true; + } + } else { + LOG(INFO) << res.error(); + } + } + // No suitable precompiled policy could be loaded + + LOG(INFO) << "Compiling SELinux policy"; + + // We store the output of the compilation on /dev because this is the most convenient tmpfs + // storage mount available this early in the boot sequence. + char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX"; + unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC)); + if (compiled_sepolicy_fd < 0) { + PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy; + return false; + } + + // Determine which mapping file to include + std::string vend_plat_vers; + if (!GetVendorMappingVersion(&vend_plat_vers)) { + return false; + } + std::string plat_mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil"); + + std::string plat_compat_cil_file("/system/etc/selinux/mapping/" + vend_plat_vers + + ".compat.cil"); + if (access(plat_compat_cil_file.c_str(), F_OK) == -1) { + plat_compat_cil_file.clear(); + } + + std::string system_ext_policy_cil_file("/system_ext/etc/selinux/system_ext_sepolicy.cil"); + if (access(system_ext_policy_cil_file.c_str(), F_OK) == -1) { + system_ext_policy_cil_file.clear(); + } + + std::string system_ext_mapping_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers + + ".cil"); + if (access(system_ext_mapping_file.c_str(), F_OK) == -1) { + system_ext_mapping_file.clear(); + } + + std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers + + ".compat.cil"); + if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) { + system_ext_compat_cil_file.clear(); + } + + std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil"); + if (access(product_policy_cil_file.c_str(), F_OK) == -1) { + product_policy_cil_file.clear(); + } + + std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil"); + if (access(product_mapping_file.c_str(), F_OK) == -1) { + product_mapping_file.clear(); + } + + std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil"); + if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) { + LOG(ERROR) << "Missing " << vendor_policy_cil_file; + return false; + } + + std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil"); + if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) { + LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file; + return false; + } + + // odm_sepolicy.cil is default but optional. + std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil"); + if (access(odm_policy_cil_file.c_str(), F_OK) == -1) { + odm_policy_cil_file.clear(); + } + const std::string version_as_string = std::to_string(SEPOLICY_VERSION); + + // clang-format off + std::vector compile_args { + "/system/bin/secilc", + use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file, + "-m", "-M", "true", "-G", "-N", + "-c", version_as_string.c_str(), + plat_mapping_file.c_str(), + "-o", compiled_sepolicy, + // We don't care about file_contexts output by the compiler + "-f", "/sys/fs/selinux/null", // /dev/null is not yet available + }; + // clang-format on + + if (!plat_compat_cil_file.empty()) { + compile_args.push_back(plat_compat_cil_file.c_str()); + } + if (!system_ext_policy_cil_file.empty()) { + compile_args.push_back(system_ext_policy_cil_file.c_str()); + } + if (!system_ext_mapping_file.empty()) { + compile_args.push_back(system_ext_mapping_file.c_str()); + } + if (!system_ext_compat_cil_file.empty()) { + compile_args.push_back(system_ext_compat_cil_file.c_str()); + } + if (!product_policy_cil_file.empty()) { + compile_args.push_back(product_policy_cil_file.c_str()); + } + if (!product_mapping_file.empty()) { + compile_args.push_back(product_mapping_file.c_str()); + } + if (!plat_pub_versioned_cil_file.empty()) { + compile_args.push_back(plat_pub_versioned_cil_file.c_str()); + } + if (!vendor_policy_cil_file.empty()) { + compile_args.push_back(vendor_policy_cil_file.c_str()); + } + if (!odm_policy_cil_file.empty()) { + compile_args.push_back(odm_policy_cil_file.c_str()); + } + compile_args.push_back(nullptr); + + if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) { + unlink(compiled_sepolicy); + return false; + } + unlink(compiled_sepolicy); + + policy_file->fd = std::move(compiled_sepolicy_fd); + policy_file->path = compiled_sepolicy; + return true; +} + +bool OpenMonolithicPolicy(PolicyFile* policy_file) { + static constexpr char kSepolicyFile[] = "/sepolicy"; + + LOG(INFO) << "Opening SELinux policy from monolithic file " << kSepolicyFile; + policy_file->fd.reset(open(kSepolicyFile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (policy_file->fd < 0) { + PLOG(ERROR) << "Failed to open monolithic SELinux policy"; + return false; + } + policy_file->path = kSepolicyFile; + return true; +} + +void ReadPolicy(std::string* policy) { + PolicyFile policy_file; + + bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file) + : OpenMonolithicPolicy(&policy_file); + if (!ok) { + LOG(FATAL) << "Unable to open SELinux policy"; + } + + if (!android::base::ReadFdToString(policy_file.fd, policy)) { + PLOG(FATAL) << "Failed to read policy file: " << policy_file.path; + } +} + +void SelinuxSetEnforcement() { + bool kernel_enforcing = (security_getenforce() == 1); + bool is_enforcing = IsEnforcing(); + if (kernel_enforcing != is_enforcing) { + if (security_setenforce(is_enforcing)) { + PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false") + << ") failed"; + } + } +} + +constexpr size_t kKlogMessageSize = 1024; + +void SelinuxAvcLog(char* buf) { + struct NetlinkMessage { + nlmsghdr hdr; + char buf[kKlogMessageSize]; + } request = {}; + + request.hdr.nlmsg_flags = NLM_F_REQUEST; + request.hdr.nlmsg_type = AUDIT_USER_AVC; + request.hdr.nlmsg_len = sizeof(request); + strlcpy(request.buf, buf, sizeof(request.buf)); + + auto fd = unique_fd{socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT)}; + if (!fd.ok()) { + return; + } + + TEMP_FAILURE_RETRY(send(fd.get(), &request, sizeof(request), 0)); +} + +int RestoreconIfExists(const char* path, unsigned int flags) { + if (access(path, F_OK) != 0 && errno == ENOENT) { + // Avoid error message for path that is expected to not always exist. + return 0; + } + return selinux_android_restorecon(path, flags); +} + +} // namespace + +void SelinuxRestoreContext() { + LOG(INFO) << "Running restorecon..."; + selinux_android_restorecon("/dev", 0); + selinux_android_restorecon("/dev/console", 0); + selinux_android_restorecon("/dev/kmsg", 0); + if constexpr (WORLD_WRITABLE_KMSG) { + selinux_android_restorecon("/dev/kmsg_debug", 0); + } + selinux_android_restorecon("/dev/null", 0); + selinux_android_restorecon("/dev/ptmx", 0); + selinux_android_restorecon("/dev/socket", 0); + selinux_android_restorecon("/dev/random", 0); + selinux_android_restorecon("/dev/urandom", 0); + selinux_android_restorecon("/dev/__properties__", 0); + + selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE); + selinux_android_restorecon("/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE); + selinux_android_restorecon("/dev/device-mapper", 0); + + selinux_android_restorecon("/apex", 0); + selinux_android_restorecon("/bootstrap-apex", 0); + selinux_android_restorecon("/linkerconfig", 0); + + // adb remount, snapshot-based updates, and DSUs all create files during + // first-stage init. + RestoreconIfExists(SnapshotManager::GetGlobalRollbackIndicatorPath().c_str(), 0); + RestoreconIfExists("/metadata/gsi", + SELINUX_ANDROID_RESTORECON_RECURSE | SELINUX_ANDROID_RESTORECON_SKIP_SEHASH); +} + +int SelinuxKlogCallback(int type, const char* fmt, ...) { + android::base::LogSeverity severity = android::base::ERROR; + if (type == SELINUX_WARNING) { + severity = android::base::WARNING; + } else if (type == SELINUX_INFO) { + severity = android::base::INFO; + } + char buf[kKlogMessageSize]; + va_list ap; + va_start(ap, fmt); + int length_written = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (length_written <= 0) { + return 0; + } + + // libselinux log messages usually contain a new line character, while + // Android LOG() does not expect it. Remove it to avoid empty lines in + // the log buffers. + size_t str_len = strlen(buf); + if (buf[str_len - 1] == '\n') { + buf[str_len - 1] = '\0'; + } + + if (type == SELINUX_AVC) { + SelinuxAvcLog(buf); + } else { + android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf); + } + return 0; +} + +void SelinuxSetupKernelLogging() { + selinux_callback cb; + cb.func_log = SelinuxKlogCallback; + selinux_set_callback(SELINUX_CB_LOG, cb); +} + +int SelinuxGetVendorAndroidVersion() { + if (IsMicrodroid()) { + // As of now Microdroid doesn't have any vendor code. + return __ANDROID_API_FUTURE__; + } + static int vendor_android_version = [] { + if (!IsSplitPolicyDevice()) { + // If this device does not split sepolicy files, it's not a Treble device and therefore, + // we assume it's always on the latest platform. + return __ANDROID_API_FUTURE__; + } + + std::string version; + if (!GetVendorMappingVersion(&version)) { + LOG(FATAL) << "Could not read vendor SELinux version"; + } + + int major_version; + std::string major_version_str(version, 0, version.find('.')); + if (!ParseInt(major_version_str, &major_version)) { + PLOG(FATAL) << "Failed to parse the vendor sepolicy major version " + << major_version_str; + } + + return major_version; + }(); + return vendor_android_version; +} + +// This is for R system.img/system_ext.img to work on old vendor.img as system_ext.img +// is introduced in R. We mount system_ext in second stage init because the first-stage +// init in boot.img won't be updated in the system-only OTA scenario. +void MountMissingSystemPartitions() { + android::fs_mgr::Fstab fstab; + if (!ReadDefaultFstab(&fstab)) { + LOG(ERROR) << "Could not read default fstab"; + } + + android::fs_mgr::Fstab mounts; + if (!ReadFstabFromFile("/proc/mounts", &mounts)) { + LOG(ERROR) << "Could not read /proc/mounts"; + } + + static const std::vector kPartitionNames = {"system_ext", "product"}; + + android::fs_mgr::Fstab extra_fstab; + for (const auto& name : kPartitionNames) { + if (GetEntryForMountPoint(&mounts, "/"s + name)) { + // The partition is already mounted. + continue; + } + + auto system_entries = GetEntriesForMountPoint(&fstab, "/system"); + for (auto& system_entry : system_entries) { + if (!system_entry) { + LOG(ERROR) << "Could not find mount entry for /system"; + break; + } + if (!system_entry->fs_mgr_flags.logical) { + LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic."; + break; + } + + auto entry = *system_entry; + auto partition_name = name + fs_mgr_get_slot_suffix(); + auto replace_name = "system"s + fs_mgr_get_slot_suffix(); + + entry.mount_point = "/"s + name; + entry.blk_device = + android::base::StringReplace(entry.blk_device, replace_name, partition_name, false); + if (!fs_mgr_update_logical_partition(&entry)) { + LOG(ERROR) << "Could not update logical partition"; + continue; + } + + extra_fstab.emplace_back(std::move(entry)); + } + } + + SkipMountingPartitions(&extra_fstab, true /* verbose */); + if (extra_fstab.empty()) { + return; + } + + BlockDevInitializer block_dev_init; + for (auto& entry : extra_fstab) { + if (access(entry.blk_device.c_str(), F_OK) != 0) { + auto block_dev = android::base::Basename(entry.blk_device); + if (!block_dev_init.InitDmDevice(block_dev)) { + LOG(ERROR) << "Failed to find device-mapper node: " << block_dev; + continue; + } + } + if (fs_mgr_do_mount_one(entry)) { + LOG(ERROR) << "Could not mount " << entry.mount_point; + } + } +} + +static void LoadSelinuxPolicy(std::string& policy) { + LOG(INFO) << "Loading SELinux policy"; + + set_selinuxmnt("/sys/fs/selinux"); + if (security_load_policy(policy.data(), policy.size()) < 0) { + PLOG(FATAL) << "SELinux: Could not load policy"; + } +} + +// Encapsulates steps to load SELinux policy in Microdroid. +// So far the process is very straightforward - just load the precompiled policy from /system. +void LoadSelinuxPolicyMicrodroid() { + constexpr const char kMicrodroidPrecompiledSepolicy[] = + "/system/etc/selinux/microdroid_precompiled_sepolicy"; + + LOG(INFO) << "Opening SELinux policy from " << kMicrodroidPrecompiledSepolicy; + unique_fd policy_fd(open(kMicrodroidPrecompiledSepolicy, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (policy_fd < 0) { + PLOG(FATAL) << "Failed to open " << kMicrodroidPrecompiledSepolicy; + } + + std::string policy; + if (!android::base::ReadFdToString(policy_fd, &policy)) { + PLOG(FATAL) << "Failed to read policy file: " << kMicrodroidPrecompiledSepolicy; + } + + LoadSelinuxPolicy(policy); +} + +// The SELinux setup process is carefully orchestrated around snapuserd. Policy +// must be loaded off dynamic partitions, and during an OTA, those partitions +// cannot be read without snapuserd. But, with kernel-privileged snapuserd +// running, loading the policy will immediately trigger audits. +// +// We use a five-step process to address this: +// (1) Read the policy into a string, with snapuserd running. +// (2) Rewrite the snapshot device-mapper tables, to generate new dm-user +// devices and to flush I/O. +// (3) Kill snapuserd, which no longer has any dm-user devices to attach to. +// (4) Load the sepolicy and issue critical restorecons in /dev, carefully +// avoiding anything that would read from /system. +// (5) Re-launch snapuserd and attach it to the dm-user devices from step (2). +// +// After this sequence, it is safe to enable enforcing mode and continue booting. +void LoadSelinuxPolicyAndroid() { + MountMissingSystemPartitions(); + + LOG(INFO) << "Opening SELinux policy"; + + // Read the policy before potentially killing snapuserd. + std::string policy; + ReadPolicy(&policy); + + auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded(); + if (snapuserd_helper) { + // Kill the old snapused to avoid audit messages. After this we cannot read from /system + // (or other dynamic partitions) until we call FinishTransition(). + snapuserd_helper->StartTransition(); + } + + LoadSelinuxPolicy(policy); + + if (snapuserd_helper) { + // Before enforcing, finish the pending snapuserd transition. + snapuserd_helper->FinishTransition(); + snapuserd_helper = nullptr; + } +} + +int SetupSelinux(char** argv) { + SetStdioToDevNull(argv); + InitKernelLogging(argv); + + if (REBOOT_BOOTLOADER_ON_PANIC) { + InstallRebootSignalHandlers(); + } + + boot_clock::time_point start_time = boot_clock::now(); + +#if 0 + SelinuxSetupKernelLogging(); + + // TODO(b/287206497): refactor into different headers to only include what we need. + if (IsMicrodroid()) { + LoadSelinuxPolicyMicrodroid(); + } else { + LoadSelinuxPolicyAndroid(); + } + + SelinuxSetEnforcement(); + + // We're in the kernel domain and want to transition to the init domain. File systems that + // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here, + // but other file systems do. In particular, this is needed for ramdisks such as the + // recovery image for A/B devices. + if (selinux_android_restorecon("/system/bin/init", 0) == -1) { + PLOG(FATAL) << "restorecon failed of /system/bin/init failed"; + } +#endif + setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1); + + const char* path = "/system/bin/init"; + const char* args[] = {path, "second_stage", nullptr}; + execv(path, const_cast(args)); + + // execv() only returns if an error happened, in which case we + // panic and never return from this function. + PLOG(FATAL) << "execv(\"" << path << "\") failed"; + + return 1; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/service.cpp b/aosp/system/core/init/service.cpp new file mode 100644 index 000000000..1b448ec84 --- /dev/null +++ b/aosp/system/core/init/service.cpp @@ -0,0 +1,1041 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "service.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "interprocess_fifo.h" +#include "lmkd_service.h" +#include "service_list.h" +#include "util.h" + +#if defined(__BIONIC__) +#include +#endif + +#ifdef INIT_FULL_SOURCES +#include + +#include "mount_namespace.h" +#include "reboot_utils.h" +#include "selinux.h" +#else +#include "host_init_stubs.h" +#endif + +using android::base::boot_clock; +using android::base::GetBoolProperty; +using android::base::GetIntProperty; +using android::base::GetProperty; +using android::base::Join; +using android::base::make_scope_guard; +using android::base::SetProperty; +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::unique_fd; +using android::base::WriteStringToFile; + +namespace android { +namespace init { + +#define MONITOR_SVCS_COUNT 3 +static std::vector hasRestartSvcs; +static std::string monitorSvcs[MONITOR_SVCS_COUNT] = {"surfaceflinger", "zygote", "zygote_secondary"}; +static std::string monitorSvcProps[MONITOR_SVCS_COUNT] = { + "sys.surfaceflinger.has_reboot", + "sys.zygote64.has_reboot", + "sys.zygote.has_reboot" +}; + +#if 0 +static Result ComputeContextFromExecutable(const std::string& service_path) { + std::string computed_context; + + char* raw_con = nullptr; + char* raw_filecon = nullptr; + + if (getcon(&raw_con) == -1) { + return Error() << "Could not get security context"; + } + std::unique_ptr mycon(raw_con, freecon); + + if (getfilecon(service_path.c_str(), &raw_filecon) == -1) { + return Error() << "Could not get file context"; + } + std::unique_ptr filecon(raw_filecon, freecon); + + char* new_con = nullptr; + int rc = security_compute_create(mycon.get(), filecon.get(), + string_to_security_class("process"), &new_con); + if (rc == 0) { + computed_context = new_con; + free(new_con); + } + if (rc == 0 && computed_context == mycon.get()) { + return Error() << "File " << service_path << "(labeled \"" << filecon.get() + << "\") has incorrect label or no domain transition from " << mycon.get() + << " to another SELinux domain defined. Have you configured your " + "service correctly? https://source.android.com/security/selinux/" + "device-policy#label_new_services_and_address_denials. Note: this " + "error shows up even in permissive mode in order to make auditing " + "denials possible."; + } + if (rc < 0) { + return Error() << "Could not get process context"; + } + return computed_context; +} +#endif + +static bool ExpandArgsAndExecv(const std::vector& args, bool sigstop) { + std::vector expanded_args; + std::vector c_strings; + + expanded_args.resize(args.size()); + c_strings.push_back(const_cast(args[0].data())); + for (std::size_t i = 1; i < args.size(); ++i) { + auto expanded_arg = ExpandProps(args[i]); + if (!expanded_arg.ok()) { + LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error(); + } + expanded_args[i] = *expanded_arg; + c_strings.push_back(expanded_args[i].data()); + } + c_strings.push_back(nullptr); + + if (sigstop) { + kill(getpid(), SIGSTOP); + } + + return execv(c_strings[0], c_strings.data()) == 0; +} + +unsigned long Service::next_start_order_ = 1; +bool Service::is_exec_service_running_ = false; + +Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands, + const std::string& filename, const std::vector& args) + : Service(name, 0, std::nullopt, 0, {}, 0, "", subcontext_for_restart_commands, filename, + args) {} + +Service::Service(const std::string& name, unsigned flags, std::optional uid, gid_t gid, + const std::vector& supp_gids, int namespace_flags, + const std::string& seclabel, Subcontext* subcontext_for_restart_commands, + const std::string& filename, const std::vector& args) + : name_(name), + classnames_({"default"}), + flags_(flags), + pid_(0), + crash_count_(0), + proc_attr_{.ioprio_class = IoSchedClass_NONE, + .ioprio_pri = 0, + .parsed_uid = uid, + .gid = gid, + .supp_gids = supp_gids, + .priority = 0}, + namespaces_{.flags = namespace_flags}, + seclabel_(seclabel), + subcontext_(subcontext_for_restart_commands), + onrestart_(false, subcontext_for_restart_commands, "", 0, + "onrestart", {}), + oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST), + start_order_(0), + args_(args), + filename_(filename) {} + +void Service::NotifyStateChange(const std::string& new_state) const { + if ((flags_ & SVC_TEMPORARY) != 0) { + // Services created by 'exec' are temporary and don't have properties tracking their state. + return; + } + + std::string prop_name = "init.svc." + name_; + SetProperty(prop_name, new_state); + + if (new_state == "running") { + uint64_t start_ns = time_started_.time_since_epoch().count(); + std::string boottime_property = "ro.boottime." + name_; + if (GetProperty(boottime_property, "").empty()) { + SetProperty(boottime_property, std::to_string(start_ns)); + } + } + + // init.svc_debug_pid.* properties are only for tests, and should not be used + // on device for security checks. + std::string pid_property = "init.svc_debug_pid." + name_; + if (new_state == "running") { + SetProperty(pid_property, std::to_string(pid_)); + } else if (new_state == "stopped") { + SetProperty(pid_property, ""); + } +} + +void Service::KillProcessGroup(int signal) { + // Always attempt the process kill if process is still running. + // Cgroup clean up routines are idempotent. It's safe to call + // killProcessGroup repeatedly. During shutdown, `init` will + // call this function to send SIGTERM/SIGKILL to all processes. + // These signals must be sent for a successful shutdown. + if (!process_cgroup_empty_ || IsRunning()) { + LOG(INFO) << "Sending signal " << signal << " to service '" << name_ << "' (pid " << pid_ + << ") process group..."; + int r; + if (signal == SIGTERM) { + r = killProcessGroupOnce(uid(), pid_, signal); + } else { + r = killProcessGroup(uid(), pid_, signal); + } + + if (r == 0) process_cgroup_empty_ = true; + } + + if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) { + LmkdUnregister(name_, pid_); + } +} + +void Service::SetProcessAttributesAndCaps(InterprocessFifo setsid_finished) { + // Keep capabilites on uid change. + if (capabilities_ && uid()) { + // If Android is running in a container, some securebits might already + // be locked, so don't change those. + unsigned long securebits = prctl(PR_GET_SECUREBITS); + if (securebits == -1UL) { + PLOG(FATAL) << "prctl(PR_GET_SECUREBITS) failed for " << name_; + } + securebits |= SECBIT_KEEP_CAPS | SECBIT_KEEP_CAPS_LOCKED; + if (prctl(PR_SET_SECUREBITS, securebits) != 0) { + PLOG(FATAL) << "prctl(PR_SET_SECUREBITS) failed for " << name_; + } + } + + if (auto result = SetProcessAttributes(proc_attr_, std::move(setsid_finished)); !result.ok()) { + LOG(FATAL) << "cannot set attribute for " << name_ << ": " << result.error(); + } + + if (!seclabel_.empty()) { + if (setexeccon(seclabel_.c_str()) < 0) { + PLOG(FATAL) << "cannot setexeccon('" << seclabel_ << "') for " << name_; + } + } + + if (capabilities_) { + if (!SetCapsForExec(*capabilities_)) { + LOG(FATAL) << "cannot set capabilities for " << name_; + } + } else if (uid()) { + // Inheritable caps can be non-zero when running in a container. + if (!DropInheritableCaps()) { + LOG(FATAL) << "cannot drop inheritable caps for " << name_; + } + } +} + +void Service::Reap(const siginfo_t& siginfo) { + if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) { + KillProcessGroup(SIGKILL); + } else { + // Legacy behavior from ~2007 until Android R: this else branch did not exist and we did not + // kill the process group in this case. + if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) { + // The new behavior in Android R is to kill these process groups in all cases. The + // 'true' parameter instructions KillProcessGroup() to report a warning message where it + // detects a difference in behavior has occurred. + KillProcessGroup(SIGKILL); + } + } + + // Remove any socket resources we may have created. + for (const auto& socket : sockets_) { + if (socket.persist) { + continue; + } + auto path = ANDROID_SOCKET_DIR "/" + socket.name; + unlink(path.c_str()); + } + + for (const auto& f : reap_callbacks_) { + f(siginfo); + } + + if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) { + LOG(ERROR) << "Service " << name_ + << " has 'reboot_on_failure' option and failed, shutting down system."; + trigger_shutdown(*on_failure_reboot_target_); + } + + if (flags_ & SVC_EXEC) UnSetExec(); + + if (name_ == "zygote" || name_ == "zygote64") { + removeAllEmptyProcessGroups(); + } + + if (flags_ & SVC_TEMPORARY) return; + + pid_ = 0; + flags_ &= (~SVC_RUNNING); + start_order_ = 0; + was_last_exit_ok_ = siginfo.si_code == CLD_EXITED && siginfo.si_status == 0; + + // Oneshot processes go into the disabled state on exit, + // except when manually restarted. + if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART) && !(flags_ & SVC_RESET)) { + flags_ |= SVC_DISABLED; + } + + // Disabled and reset processes do not get restarted automatically. + if (flags_ & (SVC_DISABLED | SVC_RESET)) { + NotifyStateChange("stopped"); + return; + } + +#if INIT_FULL_SOURCES + static bool is_apex_updatable = true; +#else + static bool is_apex_updatable = false; +#endif + const bool use_default_mount_ns = + mount_namespace_.has_value() && *mount_namespace_ == NS_DEFAULT; + const bool is_process_updatable = use_default_mount_ns && is_apex_updatable; + +#if defined(__BIONIC__) && defined(SEGV_MTEAERR) + // As a precaution, we only upgrade a service once per reboot, to limit + // the potential impact. + // + // BIONIC_SIGNAL_ART_PROFILER is a magic value used by deuggerd to signal + // that the process crashed with SIGSEGV and SEGV_MTEAERR. This signal will + // never be seen otherwise in a crash, because it always gets handled by the + // profiling signal handlers in bionic. See also + // debuggerd/handler/debuggerd_handler.cpp. + bool should_upgrade_mte = siginfo.si_code != CLD_EXITED && + siginfo.si_status == BIONIC_SIGNAL_ART_PROFILER && !upgraded_mte_; + + if (should_upgrade_mte) { + constexpr int kDefaultUpgradeSecs = 60; + int secs = GetIntProperty("persist.device_config.memory_safety_native.upgrade_secs.default", + kDefaultUpgradeSecs); + secs = GetIntProperty( + "persist.device_config.memory_safety_native.upgrade_secs.service." + name_, secs); + if (secs > 0) { + LOG(INFO) << "Upgrading service " << name_ << " to sync MTE for " << secs << " seconds"; + once_environment_vars_.emplace_back("BIONIC_MEMTAG_UPGRADE_SECS", std::to_string(secs)); + upgraded_mte_ = true; + } else { + LOG(INFO) << "Not upgrading service " << name_ << " to sync MTE due to device config"; + } + } +#endif + + // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed, + // reboot into bootloader or set crashing property + boot_clock::time_point now = boot_clock::now(); + if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART) && + !was_last_exit_ok_) { + bool boot_completed = GetBoolProperty("sys.boot_completed", false); + if (now < time_crashed_ + fatal_crash_window_ || !boot_completed) { + if (++crash_count_ > 4) { + auto exit_reason = boot_completed ? + "in " + std::to_string(fatal_crash_window_.count()) + " minutes" : + "before boot completed"; + if (flags_ & SVC_CRITICAL) { + if (!GetBoolProperty("init.svc_debug.no_fatal." + name_, false)) { + // Aborts into `fatal_reboot_target_'. + SetFatalRebootTarget(fatal_reboot_target_); + LOG(FATAL) << "critical process '" << name_ << "' exited 4 times " + << exit_reason; + } + } else { + LOG(ERROR) << "process with updatable components '" << name_ + << "' exited 4 times " << exit_reason; + // Notifies update_verifier and apexd + SetProperty("sys.init.updatable_crashing_process_name", name_); + SetProperty("sys.init.updatable_crashing", "1"); + } + } + } else { + time_crashed_ = now; + crash_count_ = 1; + } + } + + flags_ &= (~SVC_RESTART); + flags_ |= SVC_RESTARTING; + + // Execute all onrestart commands for this service. + onrestart_.ExecuteAllCommands(); + + NotifyStateChange("restarting"); + return; +} + +void Service::DumpState() const { + LOG(INFO) << "service " << name_; + LOG(INFO) << " class '" << Join(classnames_, " ") << "'"; + LOG(INFO) << " exec " << Join(args_, " "); + for (const auto& socket : sockets_) { + LOG(INFO) << " socket " << socket.name; + } + for (const auto& file : files_) { + LOG(INFO) << " file " << file.name; + } +} + + +Result Service::ExecStart() { + auto reboot_on_failure = make_scope_guard([this] { + if (on_failure_reboot_target_) { + trigger_shutdown(*on_failure_reboot_target_); + } + }); + + if (is_updatable() && !IsDefaultMountNamespaceReady()) { + // Don't delay the service for ExecStart() as the semantic is that + // the caller might depend on the side effect of the execution. + return Error() << "Cannot start an updatable service '" << name_ + << "' before configs from APEXes are all loaded"; + } + + flags_ |= SVC_ONESHOT; + + if (auto result = Start(); !result.ok()) { + return result; + } + + flags_ |= SVC_EXEC; + is_exec_service_running_ = true; + + LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " (uid " << uid() << " gid " + << proc_attr_.gid << "+" << proc_attr_.supp_gids.size() << " context " + << (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting..."; + + reboot_on_failure.Disable(); + return {}; +} + +Result Service::CheckConsole() { + if (!(flags_ & SVC_CONSOLE)) { + return {}; + } + + // On newer kernels, /dev/console will always exist because + // "console=ttynull" is hard-coded in CONFIG_CMDLINE. This new boot + // property should be set via "androidboot.serialconsole=0" to explicitly + // disable services requiring the console. For older kernels and boot + // images, not setting this at all will fall back to the old behavior + if (GetProperty("ro.boot.serialconsole", "") == "0") { + flags_ |= SVC_DISABLED; + return {}; + } + + if (proc_attr_.console.empty()) { + proc_attr_.console = "/dev/" + GetProperty("ro.boot.console", "console"); + } + + // Make sure that open call succeeds to ensure a console driver is + // properly registered for the device node + int console_fd = open(proc_attr_.console.c_str(), O_RDWR | O_CLOEXEC); + if (console_fd < 0) { + flags_ |= SVC_DISABLED; + return ErrnoError() << "Couldn't open console '" << proc_attr_.console << "'"; + } + close(console_fd); + return {}; +} + +// Configures the memory cgroup properties for the service. +void Service::ConfigureMemcg() { + if (swappiness_ != -1) { + if (!setProcessGroupSwappiness(uid(), pid_, swappiness_)) { + PLOG(ERROR) << "setProcessGroupSwappiness failed"; + } + } + + if (soft_limit_in_bytes_ != -1) { + if (!setProcessGroupSoftLimit(uid(), pid_, soft_limit_in_bytes_)) { + PLOG(ERROR) << "setProcessGroupSoftLimit failed"; + } + } + + size_t computed_limit_in_bytes = limit_in_bytes_; + if (limit_percent_ != -1) { + long page_size = sysconf(_SC_PAGESIZE); + long num_pages = sysconf(_SC_PHYS_PAGES); + if (page_size > 0 && num_pages > 0) { + size_t max_mem = SIZE_MAX; + if (size_t(num_pages) < SIZE_MAX / size_t(page_size)) { + max_mem = size_t(num_pages) * size_t(page_size); + } + computed_limit_in_bytes = + std::min(computed_limit_in_bytes, max_mem / 100 * limit_percent_); + } + } + + if (!limit_property_.empty()) { + // This ends up overwriting computed_limit_in_bytes but only if the + // property is defined. + computed_limit_in_bytes = + android::base::GetUintProperty(limit_property_, computed_limit_in_bytes, SIZE_MAX); + } + + if (computed_limit_in_bytes != size_t(-1)) { + if (!setProcessGroupLimit(uid(), pid_, computed_limit_in_bytes)) { + PLOG(ERROR) << "setProcessGroupLimit failed"; + } + } +} + +// Enters namespaces, sets environment variables, writes PID files and runs the service executable. +void Service::RunService(const std::vector& descriptors, + InterprocessFifo cgroups_activated, InterprocessFifo setsid_finished) { +#if 0 + if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_); !result.ok()) { + LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error(); + } +#endif + + for (const auto& [key, value] : once_environment_vars_) { + setenv(key.c_str(), value.c_str(), 1); + } + for (const auto& [key, value] : environment_vars_) { + setenv(key.c_str(), value.c_str(), 1); + } + + for (const auto& descriptor : descriptors) { + descriptor.Publish(); + } + +#if 0 + if (auto result = WritePidToFiles(&writepid_files_); !result.ok()) { + LOG(ERROR) << "failed to write pid to files: " << result.error(); + } + + // Wait until the cgroups have been created and until the cgroup controllers have been + // activated. + Result byte = cgroups_activated.Read(); + if (!byte.ok()) { + LOG(ERROR) << name_ << ": failed to read from notification channel: " << byte.error(); + } + cgroups_activated.Close(); + if (*byte != kCgroupsActivated) { + LOG(FATAL) << "Service '" << name_ << "' failed to start due to a fatal error"; + _exit(EXIT_FAILURE); + } + + if (task_profiles_.size() > 0) { + bool succeeded = SelinuxGetVendorAndroidVersion() < __ANDROID_API_U__ + ? + // Compatibility mode: apply the task profiles to the current + // thread. + SetTaskProfiles(getpid(), task_profiles_) + : + // Apply the task profiles to the current process. + SetProcessProfiles(getuid(), getpid(), task_profiles_); + if (!succeeded) { + LOG(ERROR) << "failed to set task profiles"; + } + } + + // As requested, set our gid, supplemental gids, uid, context, and + // priority. Aborts on failure. + SetProcessAttributesAndCaps(std::move(setsid_finished)); +#endif + + if (!ExpandArgsAndExecv(args_, sigstop_)) { + PLOG(ERROR) << "cannot execv('" << args_[0] + << "'). See the 'Debugging init' section of init's README.md for tips"; + } +} + +Result Service::Start() { + auto reboot_on_failure = make_scope_guard([this] { + if (on_failure_reboot_target_) { + trigger_shutdown(*on_failure_reboot_target_); + } + }); + + if (is_updatable() && !IsDefaultMountNamespaceReady()) { + ServiceList::GetInstance().DelayService(*this); + return Error() << "Cannot start an updatable service '" << name_ + << "' before configs from APEXes are all loaded. " + << "Queued for execution."; + } + + bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET)); + ResetFlagsForStart(); + + // Running processes require no additional work --- if they're in the + // process of exiting, we've ensured that they will immediately restart + // on exit, unless they are ONESHOT. For ONESHOT service, if it's in + // stopping status, we just set SVC_RESTART flag so it will get restarted + // in Reap(). + if (flags_ & SVC_RUNNING) { + if ((flags_ & SVC_ONESHOT) && disabled) { + flags_ |= SVC_RESTART; + } + + LOG(INFO) << "service '" << name_ + << "' requested start, but it is already running (flags: " << flags_ << ")"; + + // It is not an error to try to start a service that is already running. + reboot_on_failure.Disable(); + return {}; + } + + // cgroups_activated is used for communication from the parent to the child + // while setsid_finished is used for communication from the child process to + // the parent process. These two communication channels are separate because + // combining these into a single communication channel would introduce a + // race between the Write() calls by the parent and by the child. + InterprocessFifo cgroups_activated, setsid_finished; + OR_RETURN(cgroups_activated.Initialize()); + OR_RETURN(setsid_finished.Initialize()); + + if (Result result = CheckConsole(); !result.ok()) { + return result; + } + + struct stat sb; + if (stat(args_[0].c_str(), &sb) == -1) { + flags_ |= SVC_DISABLED; + return ErrnoError() << "Cannot find '" << args_[0] << "'"; + } + + std::string scon; +#if 0 + if (!seclabel_.empty()) { + scon = seclabel_; + } else { + auto result = ComputeContextFromExecutable(args_[0]); + if (!result.ok()) { + return result.error(); + } + scon = *result; + } +#endif + + if (!mount_namespace_.has_value()) { + // remember from which mount namespace the service should start + SetMountNamespace(); + } + + post_data_ = ServiceList::GetInstance().IsPostData(); + + LOG(INFO) << "starting service '" << name_ << "'..."; + + for (int i = 0; i < MONITOR_SVCS_COUNT; i++) { + if (name_ == monitorSvcs[i]) { + auto iter = find(hasRestartSvcs.begin(), hasRestartSvcs.end(), name_); + if (iter != hasRestartSvcs.end()) { + SetProperty(monitorSvcProps[i], "1"); + } else { + hasRestartSvcs.push_back(name_); + } + } + } + + std::vector descriptors; + for (const auto& socket : sockets_) { + if (auto result = socket.Create(scon); result.ok()) { + descriptors.emplace_back(std::move(*result)); + } else { + LOG(INFO) << "Could not create socket '" << socket.name << "': " << result.error(); + } + } + + for (const auto& file : files_) { + if (auto result = file.Create(); result.ok()) { + descriptors.emplace_back(std::move(*result)); + } else { + LOG(INFO) << "Could not open file '" << file.name << "': " << result.error(); + } + } + + pid_t pid = -1; + if (namespaces_.flags) { + pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr); + } else { + pid = fork(); + } + + if (pid == 0) { + umask(077); + cgroups_activated.CloseWriteFd(); + setsid_finished.CloseReadFd(); + RunService(descriptors, std::move(cgroups_activated), std::move(setsid_finished)); + _exit(127); + } else { + cgroups_activated.CloseReadFd(); + setsid_finished.CloseWriteFd(); + } + + if (pid < 0) { + pid_ = 0; + return ErrnoError() << "Failed to fork"; + } + + once_environment_vars_.clear(); + + if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) { + std::string oom_str = std::to_string(oom_score_adjust_); + std::string oom_file = StringPrintf("/proc/%d/oom_score_adj", pid); + if (!WriteStringToFile(oom_str, oom_file)) { + PLOG(ERROR) << "couldn't write oom_score_adj"; + } + } + + time_started_ = boot_clock::now(); + pid_ = pid; + flags_ |= SVC_RUNNING; + start_order_ = next_start_order_++; + process_cgroup_empty_ = false; + + if (CgroupsAvailable()) { + bool use_memcg = swappiness_ != -1 || soft_limit_in_bytes_ != -1 || limit_in_bytes_ != -1 || + limit_percent_ != -1 || !limit_property_.empty(); + errno = -createProcessGroup(uid(), pid_, use_memcg); + if (errno != 0) { + Result result = cgroups_activated.Write(kActivatingCgroupsFailed); + if (!result.ok()) { + return Error() << "Sending notification failed: " << result.error(); + } + return Error() << "createProcessGroup(" << uid() << ", " << pid_ << ", " << use_memcg + << ") failed for service '" << name_ << "': " << strerror(errno); + } + + // When the blkio controller is mounted in the v1 hierarchy, NormalIoPriority is + // the default (/dev/blkio). When the blkio controller is mounted in the v2 hierarchy, the + // NormalIoPriority profile has to be applied explicitly. + SetProcessProfiles(uid(), pid_, {"NormalIoPriority"}); + + if (use_memcg) { + ConfigureMemcg(); + } + } + + if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) { + LmkdRegister(name_, uid(), pid_, oom_score_adjust_); + } + + if (Result result = cgroups_activated.Write(kCgroupsActivated); !result.ok()) { + return Error() << "Sending cgroups activated notification failed: " << result.error(); + } + + cgroups_activated.Close(); + + // Call setpgid() from the parent process to make sure that this call has + // finished before the parent process calls kill(-pgid, ...). + if (!RequiresConsole(proc_attr_)) { + if (setpgid(pid, pid) < 0) { + switch (errno) { + case EACCES: // Child has already performed setpgid() followed by execve(). + case ESRCH: // Child process no longer exists. + break; + default: + PLOG(ERROR) << "setpgid() from parent failed"; + } + } + } else { + // The Read() call below will return an error if the child is killed. + if (Result result = setsid_finished.Read(); + !result.ok() || *result != kSetSidFinished) { + if (!result.ok()) { + return Error() << "Waiting for setsid() failed: " << result.error(); + } else { + return Error() << "Waiting for setsid() failed: " << static_cast(*result) + << " <> " << static_cast(kSetSidFinished); + } + } + } + + setsid_finished.Close(); + + NotifyStateChange("running"); + reboot_on_failure.Disable(); + + LOG(INFO) << "... started service '" << name_ << "' has pid " << pid_; + + return {}; +} + +// Set mount namespace for the service. +// The reason why remember the mount namespace: +// If this service is started before APEXes and corresponding linker configuration +// get available, mark it as pre-apexd one. Note that this marking is +// permanent. So for example, if the service is re-launched (e.g., due +// to crash), it is still recognized as pre-apexd... for consistency. +void Service::SetMountNamespace() { + // APEXd is always started in the "current" namespace because it is the process to set up + // the current namespace. So, leave mount_namespace_ as empty. + if (args_[0] == "/system/bin/apexd") { + return; + } + // Services in the following list start in the "default" mount namespace. + // Note that they should use bootstrap bionic if they start before APEXes are ready. + static const std::set kUseDefaultMountNamespace = { + "ueventd", // load firmwares from APEXes + "hwservicemanager", // load VINTF fragments from APEXes + "servicemanager", // load VINTF fragments from APEXes + }; + if (kUseDefaultMountNamespace.find(name_) != kUseDefaultMountNamespace.end()) { + mount_namespace_ = NS_DEFAULT; + return; + } + // Use the "default" mount namespace only if it's ready + mount_namespace_ = IsDefaultMountNamespaceReady() ? NS_DEFAULT : NS_BOOTSTRAP; +} + +static int ThreadCount() { + std::unique_ptr dir(opendir("/proc/self/task"), closedir); + if (!dir) { + return -1; + } + + int count = 0; + dirent* entry; + while ((entry = readdir(dir.get())) != nullptr) { + if (entry->d_name[0] != '.') { + count++; + } + } + return count; +} + +// Must be called BEFORE any threads are created. See also the sigprocmask() man page. +unique_fd Service::CreateSigchldFd() { + CHECK_EQ(ThreadCount(), 1); + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) { + PLOG(FATAL) << "Failed to block SIGCHLD"; + } + + return unique_fd(signalfd(-1, &mask, SFD_CLOEXEC)); +} + +void Service::SetStartedInFirstStage(pid_t pid) { + LOG(INFO) << "adding first-stage service '" << name_ << "'..."; + + time_started_ = boot_clock::now(); // not accurate, but doesn't matter here + pid_ = pid; + flags_ |= SVC_RUNNING; + start_order_ = next_start_order_++; + + NotifyStateChange("running"); +} + +void Service::ResetFlagsForStart() { + // Starting a service removes it from the disabled or reset state and + // immediately takes it out of the restarting state if it was in there. + flags_ &= ~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START); +} + +Result Service::StartIfNotDisabled() { + if (!(flags_ & SVC_DISABLED)) { + return Start(); + } else { + flags_ |= SVC_DISABLED_START; + } + return {}; +} + +Result Service::Enable() { + flags_ &= ~(SVC_DISABLED | SVC_RC_DISABLED); + if (flags_ & SVC_DISABLED_START) { + return Start(); + } + return {}; +} + +void Service::Reset() { + StopOrReset(SVC_RESET); +} + +void Service::Stop() { + StopOrReset(SVC_DISABLED); +} + +void Service::Terminate() { + flags_ &= ~(SVC_RESTARTING | SVC_DISABLED_START); + flags_ |= SVC_DISABLED; + if (pid_) { + KillProcessGroup(SIGTERM); + NotifyStateChange("stopping"); + } +} + +void Service::Timeout() { + // All process state flags will be taken care of in Reap(), we really just want to kill the + // process here when it times out. Oneshot processes will transition to be disabled, and + // all other processes will transition to be restarting. + LOG(INFO) << "Service '" << name_ << "' expired its timeout of " << timeout_period_->count() + << " seconds and will now be killed"; + if (pid_) { + KillProcessGroup(SIGKILL); + NotifyStateChange("stopping"); + } +} + +void Service::Restart() { + if (flags_ & SVC_RUNNING) { + /* Stop, wait, then start the service. */ + StopOrReset(SVC_RESTART); + } else if (!(flags_ & SVC_RESTARTING)) { + /* Just start the service since it's not running. */ + if (auto result = Start(); !result.ok()) { + LOG(ERROR) << "Could not restart '" << name_ << "': " << result.error(); + } + } /* else: Service is restarting anyways. */ +} + +// The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART. +void Service::StopOrReset(int how) { + // The service is still SVC_RUNNING until its process exits, but if it has + // already exited it shoudn't attempt a restart yet. + flags_ &= ~(SVC_RESTARTING | SVC_DISABLED_START); + + if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) { + // An illegal flag: default to SVC_DISABLED. + LOG(ERROR) << "service '" << name_ << "' requested unknown flag " << how + << ", defaulting to disabling it."; + how = SVC_DISABLED; + } + + // If the service has not yet started, prevent it from auto-starting with its class. + if (how == SVC_RESET) { + flags_ |= (flags_ & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET; + } else { + flags_ |= how; + } + // Make sure it's in right status when a restart immediately follow a + // stop/reset or vice versa. + if (how == SVC_RESTART) { + flags_ &= (~(SVC_DISABLED | SVC_RESET)); + } else { + flags_ &= (~SVC_RESTART); + } + + if (pid_) { + if (flags_ & SVC_GENTLE_KILL) { + KillProcessGroup(SIGTERM); + if (!process_cgroup_empty()) std::this_thread::sleep_for(200ms); + } + KillProcessGroup(SIGKILL); + NotifyStateChange("stopping"); + } else { + NotifyStateChange("stopped"); + } +} + +Result> Service::MakeTemporaryOneshotService( + const std::vector& args) { + // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS... + // SECLABEL can be a - to denote default + std::size_t command_arg = 1; + for (std::size_t i = 1; i < args.size(); ++i) { + if (args[i] == "--") { + command_arg = i + 1; + break; + } + } + if (command_arg > 4 + NR_SVC_SUPP_GIDS) { + return Error() << "exec called with too many supplementary group ids"; + } + + if (command_arg >= args.size()) { + return Error() << "exec called without command"; + } + std::vector str_args(args.begin() + command_arg, args.end()); + + static size_t exec_count = 0; + exec_count++; + std::string name = "exec " + std::to_string(exec_count) + " (" + Join(str_args, " ") + ")"; + + unsigned flags = SVC_ONESHOT | SVC_TEMPORARY; + unsigned namespace_flags = 0; + + std::string seclabel = ""; + if (command_arg > 2 && args[1] != "-") { + seclabel = args[1]; + } + Result uid = 0; + if (command_arg > 3) { + uid = DecodeUid(args[2]); + if (!uid.ok()) { + return Error() << "Unable to decode UID for '" << args[2] << "': " << uid.error(); + } + } + Result gid = 0; + std::vector supp_gids; + if (command_arg > 4) { + gid = DecodeUid(args[3]); + if (!gid.ok()) { + return Error() << "Unable to decode GID for '" << args[3] << "': " << gid.error(); + } + std::size_t nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */; + for (size_t i = 0; i < nr_supp_gids; ++i) { + auto supp_gid = DecodeUid(args[4 + i]); + if (!supp_gid.ok()) { + return Error() << "Unable to decode GID for '" << args[4 + i] + << "': " << supp_gid.error(); + } + supp_gids.push_back(*supp_gid); + } + } + + return std::make_unique(name, flags, *uid, *gid, supp_gids, namespace_flags, seclabel, + nullptr, /*filename=*/"", str_args); +} + +// This is used for snapuserd_proxy, which hands off a socket to snapuserd. It's +// a special case to support the daemon launched in first-stage init. The persist +// feature is not part of the init language and is only used here. +bool Service::MarkSocketPersistent(const std::string& socket_name) { + for (auto& socket : sockets_) { + if (socket.name == socket_name) { + socket.persist = true; + return true; + } + } + return false; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/subcontext.cpp b/aosp/system/core/init/subcontext.cpp new file mode 100644 index 000000000..3ef4eca1f --- /dev/null +++ b/aosp/system/core/init/subcontext.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "subcontext.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "action.h" +#include "builtins.h" +#include "mount_namespace.h" +#include "proto_utils.h" +#include "util.h" + +#ifdef INIT_FULL_SOURCES +#include +#include "property_service.h" +#include "selabel.h" +#include "selinux.h" +#else +#include "host_init_stubs.h" +#endif + +using android::base::GetExecutablePath; +using android::base::GetProperty; +using android::base::Join; +using android::base::Socketpair; +using android::base::Split; +using android::base::StartsWith; +using android::base::unique_fd; + +namespace android { +namespace init { +namespace { + +std::string shutdown_command; +static bool subcontext_terminated_by_shutdown; +static std::unique_ptr subcontext; + +class SubcontextProcess { + public: + SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd) + : function_map_(function_map), context_(std::move(context)), init_fd_(init_fd){}; + void MainLoop(); + + private: + void RunCommand(const SubcontextCommand::ExecuteCommand& execute_command, + SubcontextReply* reply) const; + void ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command, + SubcontextReply* reply) const; + + const BuiltinFunctionMap* function_map_; + const std::string context_; + const int init_fd_; +}; + +void SubcontextProcess::RunCommand(const SubcontextCommand::ExecuteCommand& execute_command, + SubcontextReply* reply) const { + // Need to use ArraySplice instead of this code. + auto args = std::vector(); + for (const auto& string : execute_command.args()) { + args.emplace_back(string); + } + + auto map_result = function_map_->Find(args); + Result result; + if (!map_result.ok()) { + result = Error() << "Cannot find command: " << map_result.error(); + } else { + result = RunBuiltinFunction(map_result->function, args, context_); + } + + if (result.ok()) { + reply->set_success(true); + } else { + auto* failure = reply->mutable_failure(); + failure->set_error_string(result.error().message()); + failure->set_error_errno(result.error().code()); + } +} + +void SubcontextProcess::ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command, + SubcontextReply* reply) const { + for (const auto& arg : expand_args_command.args()) { + auto expanded_arg = ExpandProps(arg); + if (!expanded_arg.ok()) { + auto* failure = reply->mutable_failure(); + failure->set_error_string(expanded_arg.error().message()); + failure->set_error_errno(0); + return; + } else { + auto* expand_args_reply = reply->mutable_expand_args_reply(); + expand_args_reply->add_expanded_args(*expanded_arg); + } + } +} + +void SubcontextProcess::MainLoop() { + pollfd ufd[1]; + ufd[0].events = POLLIN; + ufd[0].fd = init_fd_; + + while (true) { + ufd[0].revents = 0; + int nr = TEMP_FAILURE_RETRY(poll(ufd, arraysize(ufd), -1)); + if (nr == 0) continue; + if (nr < 0) { + PLOG(FATAL) << "poll() of subcontext socket failed, continuing"; + } + + auto init_message = ReadMessage(init_fd_); + if (!init_message.ok()) { + if (init_message.error().code() == 0) { + // If the init file descriptor was closed, let's exit quietly. If + // this was accidental, init will restart us. If init died, this + // avoids calling abort(3) unnecessarily. + return; + } + LOG(FATAL) << "Could not read message from init: " << init_message.error(); + } + + auto subcontext_command = SubcontextCommand(); + if (!subcontext_command.ParseFromString(*init_message)) { + LOG(FATAL) << "Unable to parse message from init"; + } + + auto reply = SubcontextReply(); + switch (subcontext_command.command_case()) { + case SubcontextCommand::kExecuteCommand: { + RunCommand(subcontext_command.execute_command(), &reply); + break; + } + case SubcontextCommand::kExpandArgsCommand: { + ExpandArgs(subcontext_command.expand_args_command(), &reply); + break; + } + default: + LOG(FATAL) << "Unknown message type from init: " + << subcontext_command.command_case(); + } + + if (!shutdown_command.empty()) { + reply.set_trigger_shutdown(shutdown_command); + shutdown_command.clear(); + } + + if (auto result = SendMessage(init_fd_, reply); !result.ok()) { + LOG(FATAL) << "Failed to send message to init: " << result.error(); + } + } +} + +} // namespace + +int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map) { + if (argc < 4) LOG(FATAL) << "Fewer than 4 args specified to subcontext (" << argc << ")"; + + auto context = std::string(argv[2]); + auto init_fd = std::atoi(argv[3]); + + SelabelInitialize(); + + trigger_shutdown = [](const std::string& command) { shutdown_command = command; }; + + auto subcontext_process = SubcontextProcess(function_map, context, init_fd); + // Restore prio before main loop + setpriority(PRIO_PROCESS, 0, 0); + subcontext_process.MainLoop(); + return 0; +} + +void Subcontext::Fork() { + unique_fd subcontext_socket; + if (!Socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, &socket_, &subcontext_socket)) { + LOG(FATAL) << "Could not create socket pair to communicate to subcontext"; + return; + } + + auto result = fork(); + + if (result == -1) { + LOG(FATAL) << "Could not fork subcontext"; + } else if (result == 0) { + socket_.reset(); + + // We explicitly do not use O_CLOEXEC here, such that we can reference this FD by number + // in the subcontext process after we exec. + int child_fd = dup(subcontext_socket.get()); // NOLINT(android-cloexec-dup) + if (child_fd < 0) { + PLOG(FATAL) << "Could not dup child_fd"; + } + + // We don't switch contexts if we're running the unit tests. We don't use std::optional, + // since we still need a real context string to pass to the builtin functions. + if (context_ != kTestContext) { + if (setexeccon(context_.c_str()) < 0) { + PLOG(FATAL) << "Could not set execcon for '" << context_ << "'"; + } + } +#if defined(__ANDROID__) + // subcontext init runs in "default" mount namespace + // so that it can access /apex/* + if (auto result = SwitchToMountNamespaceIfNeeded(NS_DEFAULT); !result.ok()) { + LOG(FATAL) << "Could not switch to \"default\" mount namespace: " << result.error(); + } +#endif + auto init_path = GetExecutablePath(); + auto child_fd_string = std::to_string(child_fd); + const char* args[] = {init_path.c_str(), "subcontext", context_.c_str(), + child_fd_string.c_str(), nullptr}; + execv(init_path.data(), const_cast(args)); + + PLOG(FATAL) << "Could not execv subcontext init"; + } else { + subcontext_socket.reset(); + pid_ = result; + LOG(INFO) << "Forked subcontext for '" << context_ << "' with pid " << pid_; + } +} + +void Subcontext::Restart() { + LOG(ERROR) << "Restarting subcontext '" << context_ << "'"; + if (pid_) { + kill(pid_, SIGKILL); + } + pid_ = 0; + socket_.reset(); + Fork(); +} + +bool Subcontext::PathMatchesSubcontext(const std::string& path) const { + auto apex_name = GetApexNameFromFileName(path); + if (!apex_name.empty()) { + return std::find(apex_list_.begin(), apex_list_.end(), apex_name) != apex_list_.end(); + } + for (const auto& prefix : path_prefixes_) { + if (StartsWith(path, prefix)) { + return true; + } + } + return false; +} + +void Subcontext::SetApexList(std::vector&& apex_list) { + apex_list_ = std::move(apex_list); +} + +Result Subcontext::TransmitMessage(const SubcontextCommand& subcontext_command) { + if (auto result = SendMessage(socket_.get(), subcontext_command); !result.ok()) { + Restart(); + return ErrnoError() << "Failed to send message to subcontext"; + } + + auto subcontext_message = ReadMessage(socket_.get()); + if (!subcontext_message.ok()) { + Restart(); + return Error() << "Failed to receive result from subcontext: " << subcontext_message.error(); + } + + auto subcontext_reply = SubcontextReply{}; + if (!subcontext_reply.ParseFromString(*subcontext_message)) { + Restart(); + return Error() << "Unable to parse message from subcontext"; + } + + if (subcontext_reply.has_trigger_shutdown()) { + trigger_shutdown(subcontext_reply.trigger_shutdown()); + } + + return subcontext_reply; +} + +Result Subcontext::Execute(const std::vector& args) { + auto subcontext_command = SubcontextCommand(); + std::copy( + args.begin(), args.end(), + RepeatedPtrFieldBackInserter(subcontext_command.mutable_execute_command()->mutable_args())); + + auto subcontext_reply = TransmitMessage(subcontext_command); + if (!subcontext_reply.ok()) { + return subcontext_reply.error(); + } + + if (subcontext_reply->reply_case() == SubcontextReply::kFailure) { + auto& failure = subcontext_reply->failure(); + return ResultError<>(failure.error_string(), failure.error_errno()); + } + + if (subcontext_reply->reply_case() != SubcontextReply::kSuccess) { + return Error() << "Unexpected message type from subcontext: " + << subcontext_reply->reply_case(); + } + + return {}; +} + +Result> Subcontext::ExpandArgs(const std::vector& args) { + auto subcontext_command = SubcontextCommand{}; + std::copy(args.begin(), args.end(), + RepeatedPtrFieldBackInserter( + subcontext_command.mutable_expand_args_command()->mutable_args())); + + auto subcontext_reply = TransmitMessage(subcontext_command); + if (!subcontext_reply.ok()) { + return subcontext_reply.error(); + } + + if (subcontext_reply->reply_case() == SubcontextReply::kFailure) { + auto& failure = subcontext_reply->failure(); + return ResultError<>(failure.error_string(), failure.error_errno()); + } + + if (subcontext_reply->reply_case() != SubcontextReply::kExpandArgsReply) { + return Error() << "Unexpected message type from subcontext: " + << subcontext_reply->reply_case(); + } + + auto& reply = subcontext_reply->expand_args_reply(); + auto expanded_args = std::vector{}; + for (const auto& string : reply.expanded_args()) { + expanded_args.emplace_back(string); + } + return expanded_args; +} + +void InitializeSubcontext() { + aosp_hack(); + if (IsMicrodroid()) { + LOG(INFO) << "Not using subcontext for microdroid"; + return; + } + + if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_P__) { + subcontext.reset( + new Subcontext(std::vector{"/vendor", "/odm"}, kVendorContext)); + } +} +void InitializeHostSubcontext(std::vector vendor_prefixes) { + subcontext.reset(new Subcontext(vendor_prefixes, kVendorContext, /*host=*/true)); +} + +Subcontext* GetSubcontext() { + return subcontext.get(); +} + +bool SubcontextChildReap(pid_t pid) { + if (!subcontext) { + return false; + } + if (subcontext->pid() == pid) { + if (!subcontext_terminated_by_shutdown) { + subcontext->Restart(); + } + return true; + } + return false; +} + +void SubcontextTerminate() { + if (!subcontext) { + return; + } + subcontext_terminated_by_shutdown = true; + kill(subcontext->pid(), SIGTERM); +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/init/util.cpp b/aosp/system/core/init/util.cpp new file mode 100644 index 000000000..58d45fe84 --- /dev/null +++ b/aosp/system/core/init/util.cpp @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2008 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. + */ + +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__ANDROID__) +#include +#endif + +#ifdef INIT_FULL_SOURCES +#include +#include + +#include "reboot_utils.h" +#include "selabel.h" +#include "selinux.h" +#else +#include "host_init_stubs.h" +#endif + +using android::base::boot_clock; +using android::base::StartsWith; +using namespace std::literals::string_literals; + +namespace android { +namespace init { + +const std::string kDataDirPrefix("/data/"); + +void (*trigger_shutdown)(const std::string& command) = nullptr; + +// DecodeUid() - decodes and returns the given string, which can be either the +// numeric or name representation, into the integer uid or gid. +Result DecodeUid(const std::string& name) { + if (isalpha(name[0])) { + passwd* pwd = getpwnam(name.c_str()); + if (!pwd) return ErrnoError() << "getpwnam failed"; + + return pwd->pw_uid; + } + + errno = 0; + uid_t result = static_cast(strtoul(name.c_str(), 0, 0)); + if (errno) return ErrnoError() << "strtoul failed"; + + return result; +} + +/* + * CreateSocket - creates a Unix domain socket in ANDROID_SOCKET_DIR + * ("/dev/socket") as dictated in init.rc. This socket is inherited by the + * daemon. We communicate the file descriptor's value via the environment + * variable ANDROID_SOCKET_ENV_PREFIX ("ANDROID_SOCKET_foo"). + */ +Result CreateSocket(const std::string& name, int type, bool passcred, bool should_listen, + mode_t perm, uid_t uid, gid_t gid, const std::string& socketcon) { +#if 0 + if (!socketcon.empty()) { + if (setsockcreatecon(socketcon.c_str()) == -1) { + return ErrnoError() << "setsockcreatecon(\"" << socketcon << "\") failed"; + } + } +#endif + android::base::unique_fd fd(socket(PF_UNIX, type, 0)); + if (fd < 0) { + return ErrnoError() << "Failed to open socket '" << name << "'"; + } + + // if (!socketcon.empty()) setsockcreatecon(nullptr); + + struct sockaddr_un addr; + memset(&addr, 0 , sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR "/%s", name.c_str()); + + if ((unlink(addr.sun_path) != 0) && (errno != ENOENT)) { + return ErrnoError() << "Failed to unlink old socket '" << name << "'"; + } +#if 0 + std::string secontext; + if (SelabelLookupFileContext(addr.sun_path, S_IFSOCK, &secontext) && !secontext.empty()) { + setfscreatecon(secontext.c_str()); + } +#endif + if (passcred) { + int on = 1; + if (setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) { + return ErrnoError() << "Failed to set SO_PASSCRED '" << name << "'"; + } + } + + int ret = bind(fd.get(), (struct sockaddr*)&addr, sizeof(addr)); + int savederrno = errno; +#if 0 + if (!secontext.empty()) { + setfscreatecon(nullptr); + } +#endif + auto guard = android::base::make_scope_guard([&addr] { unlink(addr.sun_path); }); + + if (ret) { + errno = savederrno; + return ErrnoError() << "Failed to bind socket '" << name << "'"; + } + + if (lchown(addr.sun_path, uid, gid)) { + return ErrnoError() << "Failed to lchown socket '" << addr.sun_path << "'"; + } + if (fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW)) { + return ErrnoError() << "Failed to fchmodat socket '" << addr.sun_path << "'"; + } + if (should_listen && listen(fd.get(), /* use OS maximum */ 1 << 30)) { + return ErrnoError() << "Failed to listen on socket '" << addr.sun_path << "'"; + } + + LOG(INFO) << "Created socket '" << addr.sun_path << "'" + << ", mode " << std::oct << perm << std::dec + << ", user " << uid + << ", group " << gid; + + guard.Disable(); + return fd.release(); +} + +Result ReadFile(const std::string& path) { + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); + if (fd == -1) { + return ErrnoError() << "open() failed"; + } + + // For security reasons, disallow world-writable + // or group-writable files. + struct stat sb; + if (fstat(fd.get(), &sb) == -1) { + return ErrnoError() << "fstat failed()"; + } + +#if 0 + if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) { + return Error() << "Skipping insecure file"; + } +#endif + + std::string content; + if (!android::base::ReadFdToString(fd, &content)) { + return ErrnoError() << "Unable to read file contents"; + } + return content; +} + +static int OpenFile(const std::string& path, int flags, mode_t mode) { + return open(path.c_str(), flags, mode); +#if 0 + std::string secontext; + if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) { + setfscreatecon(secontext.c_str()); + } + + int rc = open(path.c_str(), flags, mode); + + if (!secontext.empty()) { + int save_errno = errno; + setfscreatecon(nullptr); + errno = save_errno; + } + + return rc; +#endif +} + +Result WriteFile(const std::string& path, const std::string& content) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + OpenFile(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600))); + if (fd == -1) { + return ErrnoError() << "open() failed"; + } + if (!android::base::WriteStringToFd(content, fd)) { + return ErrnoError() << "Unable to write file contents"; + } + return {}; +} + +bool mkdir_recursive(const std::string& path, mode_t mode) { + std::string::size_type slash = 0; + while ((slash = path.find('/', slash + 1)) != std::string::npos) { + auto directory = path.substr(0, slash); + struct stat info; + if (stat(directory.c_str(), &info) != 0) { + auto ret = make_dir(directory, mode); + if (!ret && errno != EEXIST) return false; + } + } + auto ret = make_dir(path, mode); + if (!ret && errno != EEXIST) return false; + return true; +} + +int wait_for_file(const char* filename, std::chrono::nanoseconds timeout) { + android::base::Timer t; + while (t.duration() < timeout) { + struct stat sb; + if (stat(filename, &sb) != -1) { + LOG(INFO) << "wait for '" << filename << "' took " << t; + return 0; + } + std::this_thread::sleep_for(10ms); + } + LOG(WARNING) << "wait for '" << filename << "' timed out and took " << t; + return -1; +} + +bool make_dir(const std::string& path, mode_t mode) { + int rc = mkdir(path.c_str(), mode); + return rc == 0; +#if 0 + std::string secontext; + if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) { + setfscreatecon(secontext.c_str()); + } + + int rc = mkdir(path.c_str(), mode); + + if (!secontext.empty()) { + int save_errno = errno; + setfscreatecon(nullptr); + errno = save_errno; + } + + return rc == 0; +#endif +} + +/* + * Returns true is pathname is a directory + */ +bool is_dir(const char* pathname) { + struct stat info; + if (stat(pathname, &info) == -1) { + return false; + } + return S_ISDIR(info.st_mode); +} + +Result ExpandProps(const std::string& src) { + const char* src_ptr = src.c_str(); + + std::string dst; + + /* - variables can either be $x.y or ${x.y}, in case they are only part + * of the string. + * - will accept $$ as a literal $. + * - no nested property expansion, i.e. ${foo.${bar}} is not supported, + * bad things will happen + * - ${x.y:-default} will return default value if property empty. + */ + while (*src_ptr) { + const char* c; + + c = strchr(src_ptr, '$'); + if (!c) { + dst.append(src_ptr); + return dst; + } + + dst.append(src_ptr, c); + c++; + + if (*c == '$') { + dst.push_back(*(c++)); + src_ptr = c; + continue; + } else if (*c == '\0') { + return dst; + } + + std::string prop_name; + std::string def_val; + if (*c == '{') { + c++; + const char* end = strchr(c, '}'); + if (!end) { + // failed to find closing brace, abort. + return Error() << "unexpected end of string in '" << src << "', looking for }"; + } + prop_name = std::string(c, end); + c = end + 1; + size_t def = prop_name.find(":-"); + if (def < prop_name.size()) { + def_val = prop_name.substr(def + 2); + prop_name = prop_name.substr(0, def); + } + } else { + prop_name = c; + if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) { + return Error() << "using deprecated syntax for specifying property '" << c + << "', use ${name} instead"; + } else { + LOG(ERROR) << "using deprecated syntax for specifying property '" << c + << "', use ${name} instead"; + } + c += prop_name.size(); + } + + if (prop_name.empty()) { + return Error() << "invalid zero-length property name in '" << src << "'"; + } + + std::string prop_val = android::base::GetProperty(prop_name, ""); + if (prop_val.empty()) { + if (def_val.empty()) { + return Error() << "property '" << prop_name << "' doesn't exist while expanding '" + << src << "'"; + } + prop_val = def_val; + } + + dst.append(prop_val); + src_ptr = c; + } + + return dst; +} + +// Reads the content of device tree file under the platform's Android DT directory. +// Returns true if the read is success, false otherwise. +bool read_android_dt_file(const std::string& sub_path, std::string* dt_content) { +#if defined(__ANDROID__) + const std::string file_name = android::fs_mgr::GetAndroidDtDir() + sub_path; + if (android::base::ReadFileToString(file_name, dt_content)) { + if (!dt_content->empty()) { + dt_content->pop_back(); // Trims the trailing '\0' out. + return true; + } + } +#endif + return false; +} + +bool is_android_dt_value_expected(const std::string& sub_path, const std::string& expected_content) { + std::string dt_content; + if (read_android_dt_file(sub_path, &dt_content)) { + if (dt_content == expected_content) { + return true; + } + } + return false; +} + +bool IsLegalPropertyName(const std::string& name) { + size_t namelen = name.size(); + + if (namelen < 1) return false; + if (name[0] == '.') return false; + if (name[namelen - 1] == '.') return false; + + /* Only allow alphanumeric, plus '.', '-', '@', ':', or '_' */ + /* Don't allow ".." to appear in a property name */ + for (size_t i = 0; i < namelen; i++) { + if (name[i] == '.') { + // i=0 is guaranteed to never have a dot. See above. + if (name[i - 1] == '.') return false; + continue; + } + if (name[i] == '_' || name[i] == '-' || name[i] == '@' || name[i] == ':') continue; + if (name[i] >= 'a' && name[i] <= 'z') continue; + if (name[i] >= 'A' && name[i] <= 'Z') continue; + if (name[i] >= '0' && name[i] <= '9') continue; + return false; + } + + return true; +} + +Result IsLegalPropertyValue(const std::string& name, const std::string& value) { + if (value.size() >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) { + return Error() << "Property value too long"; + } + + if (mbstowcs(nullptr, value.data(), 0) == static_cast(-1)) { + return Error() << "Value is not a UTF8 encoded string"; + } + + return {}; +} + +// Remove unnecessary slashes so that any later checks (e.g., the check for +// whether the path is a top-level directory in /data) don't get confused. +std::string CleanDirPath(const std::string& path) { + std::string result; + result.reserve(path.length()); + // Collapse duplicate slashes, e.g. //data//foo// => /data/foo/ + for (char c : path) { + if (c != '/' || result.empty() || result.back() != '/') { + result += c; + } + } + // Remove trailing slash, e.g. /data/foo/ => /data/foo + if (result.length() > 1 && result.back() == '/') { + result.pop_back(); + } + return result; +} + +Result ParseMkdir(const std::vector& args) { + std::string path = CleanDirPath(args[1]); + const bool is_toplevel_data_dir = + StartsWith(path, kDataDirPrefix) && + path.find_first_of('/', kDataDirPrefix.size()) == std::string::npos; + FscryptAction fscrypt_action = + is_toplevel_data_dir ? FscryptAction::kRequire : FscryptAction::kNone; + mode_t mode = 0755; + Result uid = -1; + Result gid = -1; + std::string ref_option = "ref"; + bool set_option_encryption = false; + bool set_option_key = false; + + for (size_t i = 2; i < args.size(); i++) { + switch (i) { + case 2: + mode = std::strtoul(args[2].c_str(), 0, 8); + break; + case 3: + uid = DecodeUid(args[3]); + if (!uid.ok()) { + return Error() + << "Unable to decode UID for '" << args[3] << "': " << uid.error(); + } + break; + case 4: + gid = DecodeUid(args[4]); + if (!gid.ok()) { + return Error() + << "Unable to decode GID for '" << args[4] << "': " << gid.error(); + } + break; + default: + auto parts = android::base::Split(args[i], "="); + if (parts.size() != 2) { + return Error() << "Can't parse option: '" << args[i] << "'"; + } + auto optname = parts[0]; + auto optval = parts[1]; + if (optname == "encryption") { + if (set_option_encryption) { + return Error() << "Duplicated option: '" << optname << "'"; + } + if (optval == "Require") { + fscrypt_action = FscryptAction::kRequire; + } else if (optval == "None") { + fscrypt_action = FscryptAction::kNone; + } else if (optval == "Attempt") { + fscrypt_action = FscryptAction::kAttempt; + } else if (optval == "DeleteIfNecessary") { + fscrypt_action = FscryptAction::kDeleteIfNecessary; + } else { + return Error() << "Unknown encryption option: '" << optval << "'"; + } + set_option_encryption = true; + } else if (optname == "key") { + if (set_option_key) { + return Error() << "Duplicated option: '" << optname << "'"; + } + if (optval == "ref" || optval == "per_boot_ref") { + ref_option = optval; + } else { + return Error() << "Unknown key option: '" << optval << "'"; + } + set_option_key = true; + } else { + return Error() << "Unknown option: '" << args[i] << "'"; + } + } + } + if (set_option_key && fscrypt_action == FscryptAction::kNone) { + return Error() << "Key option set but encryption action is none"; + } + if (is_toplevel_data_dir) { + if (!set_option_encryption) { + LOG(WARNING) << "Top-level directory needs encryption action, eg mkdir " << path + << " encryption=Require"; + } + if (fscrypt_action == FscryptAction::kNone) { + LOG(INFO) << "Not setting encryption policy on: " << path; + } + } + + return MkdirOptions{path, mode, *uid, *gid, fscrypt_action, ref_option}; +} + +Result ParseMountAll(const std::vector& args) { + bool compat_mode = false; + bool import_rc = false; + if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) { + if (args.size() <= 1) { + return Error() << "mount_all requires at least 1 argument"; + } + compat_mode = true; + import_rc = true; + } + + std::size_t first_option_arg = args.size(); + enum mount_mode mode = MOUNT_MODE_DEFAULT; + + // If we are <= Q, then stop looking for non-fstab arguments at slot 2. + // Otherwise, stop looking at slot 1 (as the fstab path argument is optional >= R). + for (std::size_t na = args.size() - 1; na > (compat_mode ? 1 : 0); --na) { + if (args[na] == "--early") { + first_option_arg = na; + mode = MOUNT_MODE_EARLY; + } else if (args[na] == "--late") { + first_option_arg = na; + mode = MOUNT_MODE_LATE; + import_rc = false; + } + } + + std::string fstab_path; + if (first_option_arg > 1) { + fstab_path = args[1]; + } else if (compat_mode) { + return Error() << "mount_all argument 1 must be the fstab path"; + } + + std::vector rc_paths; + for (std::size_t na = 2; na < first_option_arg; ++na) { + rc_paths.push_back(args[na]); + } + + return MountAllOptions{rc_paths, fstab_path, mode, import_rc}; +} + +Result>> ParseRestorecon( + const std::vector& args) { + struct flag_type { + const char* name; + int value; + }; + static const flag_type flags[] = { + {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE}, + {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE}, + {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS}, + {0, 0}}; + + int flag = 0; + std::vector paths; + + bool in_flags = true; + for (size_t i = 1; i < args.size(); ++i) { + if (android::base::StartsWith(args[i], "--")) { + if (!in_flags) { + return Error() << "flags must precede paths"; + } + bool found = false; + for (size_t j = 0; flags[j].name; ++j) { + if (args[i] == flags[j].name) { + flag |= flags[j].value; + found = true; + break; + } + } + if (!found) { + return Error() << "bad flag " << args[i]; + } + } else { + in_flags = false; + paths.emplace_back(args[i]); + } + } + return std::pair(flag, paths); +} + +Result ParseSwaponAll(const std::vector& args) { + if (args.size() <= 1) { + if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) { + return Error() << "swapon_all requires at least 1 argument"; + } + return {}; + } + return args[1]; +} + +Result ParseUmountAll(const std::vector& args) { + if (args.size() <= 1) { + if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) { + return Error() << "umount_all requires at least 1 argument"; + } + return {}; + } + return args[1]; +} + +static void InitAborter(const char* abort_message) { + // When init forks, it continues to use this aborter for LOG(FATAL), but we want children to + // simply abort instead of trying to reboot the system. + if (getpid() != 1) { + android::base::DefaultAborter(abort_message); + return; + } + + InitFatalReboot(SIGABRT); +} + +// The kernel opens /dev/console and uses that fd for stdin/stdout/stderr if there is a serial +// console enabled and no initramfs, otherwise it does not provide any fds for stdin/stdout/stderr. +// SetStdioToDevNull() is used to close these existing fds if they exist and replace them with +// /dev/null regardless. +// +// In the case that these fds are provided by the kernel, the exec of second stage init causes an +// SELinux denial as it does not have access to /dev/console. In the case that they are not +// provided, exec of any further process is potentially dangerous as the first fd's opened by that +// process will take the stdin/stdout/stderr fileno's, which can cause issues if printf(), etc is +// then used by that process. +// +// Lastly, simply calling SetStdioToDevNull() in first stage init is not enough, since first +// stage init still runs in kernel context, future child processes will not have permissions to +// access any fds that it opens, including the one opened below for /dev/null. Therefore, +// SetStdioToDevNull() must be called again in second stage init. +void SetStdioToDevNull(char** argv) { + // Make stdin/stdout/stderr all point to /dev/null. + int fd = open("/dev/null", O_RDWR); // NOLINT(android-cloexec-open) + if (fd == -1) { + int saved_errno = errno; + android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter); + errno = saved_errno; + PLOG(FATAL) << "Couldn't open /dev/null"; + } + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) close(fd); +} + +void InitKernelLogging(char** argv) { + SetFatalRebootTarget(); + android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter); +} + +bool IsRecoveryMode() { + return access("/system/bin/recovery", F_OK) == 0; +} + +// Check if default mount namespace is ready to be used with APEX modules +static bool is_default_mount_namespace_ready = false; + +bool IsDefaultMountNamespaceReady() { + return is_default_mount_namespace_ready; +} + +void SetDefaultMountNamespaceReady() { + is_default_mount_namespace_ready = true; +} + +bool Has32BitAbi() { + static bool has = !android::base::GetProperty("ro.product.cpu.abilist32", "").empty(); + return has; +} + +std::string GetApexNameFromFileName(const std::string& path) { + static const std::string kApexDir = "/apex/"; + if (StartsWith(path, kApexDir)) { + auto begin = kApexDir.size(); + auto end = path.find('/', begin); + return path.substr(begin, end - begin); + } + return ""; +} + +std::vector FilterVersionedConfigs(const std::vector& configs, + int active_sdk) { + std::vector filtered_configs; + + std::map> script_map; + for (const auto& c : configs) { + int sdk = 0; + const std::vector parts = android::base::Split(c, "."); + std::string base; + if (parts.size() < 2) { + continue; + } + + // parts[size()-1], aka the suffix, should be "rc" or "#rc" + // any other pattern gets discarded + + const auto& suffix = parts[parts.size() - 1]; + if (suffix == "rc") { + sdk = 0; + } else { + char trailer[9] = {0}; + int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer); + if (r != 2) { + continue; + } + if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) { + continue; + } + } + + if (sdk < 0 || sdk > active_sdk) { + continue; + } + + base = parts[0]; + for (unsigned int i = 1; i < parts.size() - 1; i++) { + base = base + "." + parts[i]; + } + + // is this preferred over what we already have + auto it = script_map.find(base); + if (it == script_map.end() || it->second.second < sdk) { + script_map[base] = std::make_pair(c, sdk); + } + } + + for (const auto& m : script_map) { + filtered_configs.push_back(m.second.first); + } + return filtered_configs; +} + +} // namespace init +} // namespace android diff --git a/aosp/system/core/libprocessgroup/processgroup.cpp b/aosp/system/core/libprocessgroup/processgroup.cpp new file mode 100644 index 000000000..6c1930e9a --- /dev/null +++ b/aosp/system/core/libprocessgroup/processgroup.cpp @@ -0,0 +1,762 @@ +/* + * Copyright 2014 Google, Inc + * + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "libprocessgroup" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using android::base::GetBoolProperty; +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::WriteStringToFile; + +using namespace std::chrono_literals; + +#define PROCESSGROUP_CGROUP_PROCS_FILE "cgroup.procs" +#define PROCESSGROUP_CGROUP_KILL_FILE "cgroup.kill" +#define PROCESSGROUP_CGROUP_EVENTS_FILE "cgroup.events" + +bool CgroupsAvailable() { + static bool cgroups_available = access("/proc/cgroups", F_OK) == 0; + return cgroups_available; +} + +bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path) { + auto controller = CgroupMap::GetInstance().FindController(cgroup_name); + + if (!controller.HasValue()) { + return false; + } + + if (path) { + *path = controller.path(); + } + + return true; +} + +static std::string ConvertUidToPath(const char* cgroup, uid_t uid) { + return StringPrintf("%s/uid_%u", cgroup, uid); +} + +static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, pid_t pid) { + return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid); +} + +static bool CgroupKillAvailable() { + static std::once_flag f; + static bool cgroup_kill_available = false; + std::call_once(f, []() { + std::string cg_kill; + CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg_kill); + // cgroup.kill is not on the root cgroup, so check a non-root cgroup that should always + // exist + cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT) + '/' + PROCESSGROUP_CGROUP_KILL_FILE; + cgroup_kill_available = access(cg_kill.c_str(), F_OK) == 0; + }); + + return cgroup_kill_available; +} + +static bool CgroupGetMemcgAppsPath(std::string* path) { + CgroupController controller = CgroupMap::GetInstance().FindController("memory"); + + if (!controller.HasValue()) { + return false; + } + + if (path) { + *path = controller.path(); + if (controller.version() == 1) { + *path += "/apps"; + } + } + + return true; +} + +bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name) { + auto controller = CgroupMap::GetInstance().FindControllerByPath(path); + + if (!controller.HasValue()) { + return false; + } + + if (cgroup_name) { + *cgroup_name = controller.name(); + } + + return true; +} + +bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) { + const TaskProfiles& tp = TaskProfiles::GetInstance(); + const IProfileAttribute* attr = tp.GetAttribute(attr_name); + + if (attr == nullptr) { + return false; + } + + if (path) { + *path = StringPrintf("%s/%s", attr->controller()->path(), attr->file_name().c_str()); + } + + return true; +} + +bool CgroupGetAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path) { + const TaskProfiles& tp = TaskProfiles::GetInstance(); + const IProfileAttribute* attr = tp.GetAttribute(attr_name); + + if (attr == nullptr) { + return false; + } + + if (!attr->GetPathForTask(tid, path)) { + LOG(ERROR) << "Failed to find cgroup for tid " << tid; + return false; + } + + return true; +} + +bool UsePerAppMemcg() { + bool low_ram_device = GetBoolProperty("ro.config.low_ram", false); + return GetBoolProperty("ro.config.per_app_memcg", low_ram_device); +} + +static bool isMemoryCgroupSupported() { + static bool memcg_supported = CgroupMap::GetInstance().FindController("memory").IsUsable(); + + return memcg_supported; +} + +void DropTaskProfilesResourceCaching() { + TaskProfiles::GetInstance().DropResourceCaching(ProfileAction::RCT_TASK); + TaskProfiles::GetInstance().DropResourceCaching(ProfileAction::RCT_PROCESS); +} + +bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles) { + return TaskProfiles::GetInstance().SetProcessProfiles( + uid, pid, std::span(profiles), false); +} + +bool SetProcessProfiles(uid_t uid, pid_t pid, std::initializer_list profiles) { + return TaskProfiles::GetInstance().SetProcessProfiles( + uid, pid, std::span(profiles), false); +} + +bool SetProcessProfiles(uid_t uid, pid_t pid, std::span profiles) { + return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, false); +} + +bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector& profiles) { + return TaskProfiles::GetInstance().SetProcessProfiles( + uid, pid, std::span(profiles), true); +} + +bool SetTaskProfiles(pid_t tid, const std::vector& profiles, bool use_fd_cache) { + return TaskProfiles::GetInstance().SetTaskProfiles(tid, std::span(profiles), + use_fd_cache); +} + +bool SetTaskProfiles(pid_t tid, std::initializer_list profiles, + bool use_fd_cache) { + return TaskProfiles::GetInstance().SetTaskProfiles( + tid, std::span(profiles), use_fd_cache); +} + +bool SetTaskProfiles(pid_t tid, std::span profiles, bool use_fd_cache) { + return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache); +} + +// C wrapper for SetProcessProfiles. +// No need to have this in the header file because this function is specifically for crosvm. Crosvm +// which is written in Rust has its own declaration of this foreign function and doesn't rely on the +// header. See +// https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3574427/5/src/linux/android.rs#12 +extern "C" bool android_set_process_profiles(uid_t uid, pid_t pid, size_t num_profiles, + const char* profiles[]) { + std::vector profiles_; + profiles_.reserve(num_profiles); + for (size_t i = 0; i < num_profiles; i++) { + profiles_.emplace_back(profiles[i]); + } + return SetProcessProfiles(uid, pid, std::span(profiles_)); +} + +bool SetUserProfiles(uid_t uid, const std::vector& profiles) { + return TaskProfiles::GetInstance().SetUserProfiles(uid, std::span(profiles), + false); +} + +static int RemoveCgroup(const char* cgroup, uid_t uid, pid_t pid) { + auto path = ConvertUidPidToPath(cgroup, uid, pid); + int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str())); + + if (!ret && uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) { + // Isolated UIDs are unlikely to be reused soon after removal, + // so free up the kernel resources for the UID level cgroup. + path = ConvertUidToPath(cgroup, uid); + ret = TEMP_FAILURE_RETRY(rmdir(path.c_str())); + } + + if (ret < 0 && errno == ENOENT) { + // This function is idempoetent, but still warn here. + LOG(WARNING) << "RemoveCgroup: " << path << " does not exist."; + ret = 0; + } + + return ret; +} + +static bool RemoveEmptyUidCgroups(const std::string& uid_path) { + std::unique_ptr uid(opendir(uid_path.c_str()), closedir); + bool empty = true; + if (uid != NULL) { + dirent* dir; + while ((dir = readdir(uid.get())) != nullptr) { + if (dir->d_type != DT_DIR) { + continue; + } + + if (!StartsWith(dir->d_name, "pid_")) { + continue; + } + + auto path = StringPrintf("%s/%s", uid_path.c_str(), dir->d_name); + LOG(VERBOSE) << "Removing " << path; + if (rmdir(path.c_str()) == -1) { + if (errno != EBUSY) { + PLOG(WARNING) << "Failed to remove " << path; + } + empty = false; + } + } + } + return empty; +} + +void removeAllEmptyProcessGroups() { + LOG(VERBOSE) << "removeAllEmptyProcessGroups()"; + + std::vector cgroups; + std::string path, memcg_apps_path; + + if (CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &path)) { + cgroups.push_back(path); + } + if (CgroupGetMemcgAppsPath(&memcg_apps_path) && memcg_apps_path != path) { + cgroups.push_back(memcg_apps_path); + } + + for (std::string cgroup_root_path : cgroups) { + std::unique_ptr root(opendir(cgroup_root_path.c_str()), closedir); + if (root == NULL) { + PLOG(ERROR) << __func__ << " failed to open " << cgroup_root_path; + } else { + dirent* dir; + while ((dir = readdir(root.get())) != nullptr) { + if (dir->d_type != DT_DIR) { + continue; + } + + if (!StartsWith(dir->d_name, "uid_")) { + continue; + } + + auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name); + if (!RemoveEmptyUidCgroups(path)) { + LOG(VERBOSE) << "Skip removing " << path; + continue; + } + LOG(VERBOSE) << "Removing " << path; + if (rmdir(path.c_str()) == -1 && errno != EBUSY) { + PLOG(WARNING) << "Failed to remove " << path; + } + } + } + } +} + +/** + * Process groups are primarily created by the Zygote, meaning that uid/pid groups are created by + * the user root. Ownership for the newly created cgroup and all of its files must thus be + * transferred for the user/group passed as uid/gid before system_server can properly access them. + */ +static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) { + if (mkdir(path.c_str(), mode) == -1) { + if (errno == EEXIST) { + // Directory already exists and permissions have been set at the time it was created + return true; + } + return false; + } + + auto dir = std::unique_ptr(opendir(path.c_str()), closedir); + + if (dir == NULL) { + PLOG(ERROR) << "opendir failed for " << path; + goto err; + } + + struct dirent* dir_entry; + while ((dir_entry = readdir(dir.get()))) { + if (!strcmp("..", dir_entry->d_name)) { + continue; + } + + std::string file_path = path + "/" + dir_entry->d_name; + + if (lchown(file_path.c_str(), uid, gid) < 0) { + PLOG(ERROR) << "lchown failed for " << file_path; + goto err; + } + + if (fchmodat(AT_FDCWD, file_path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) { + PLOG(ERROR) << "fchmodat failed for " << file_path; + goto err; + } + } + + return true; +err: + int saved_errno = errno; + rmdir(path.c_str()); + errno = saved_errno; + + return false; +} + +bool sendSignalToProcessGroup(uid_t uid, pid_t initialPid, int signal) { + std::set pgids, pids; + + if (CgroupsAvailable()) { + std::string hierarchy_root_path, cgroup_v2_path; + CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path); + cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid); + + if (signal == SIGKILL && CgroupKillAvailable()) { + LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_KILL_FILE << " to SIGKILL " + << cgroup_v2_path; + + // We need to kill the process group in addition to the cgroup. For normal apps they + // should completely overlap, but system_server kills depend on process group kills to + // take down apps which are in their own cgroups and not individually targeted. + if (kill(-initialPid, signal) == -1 && errno != ESRCH) { + PLOG(WARNING) << "kill(" << -initialPid << ", " << signal << ") failed"; + } + + const std::string killfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_KILL_FILE; + if (WriteStringToFile("1", killfilepath)) { + return true; + } else { + PLOG(ERROR) << "Failed to write 1 to " << killfilepath; + // Fallback to cgroup.procs below + } + } + + // Since cgroup.kill only sends SIGKILLs, we read cgroup.procs to find each process to + // signal individually. This is more costly than using cgroup.kill for SIGKILLs. + LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_PROCS_FILE << " to signal (" << signal + << ") " << cgroup_v2_path; + + // We separate all of the pids in the cgroup into those pids that are also the leaders of + // process groups (stored in the pgids set) and those that are not (stored in the pids set). + const auto procsfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE; + std::unique_ptr fp(fopen(procsfilepath.c_str(), "re"), fclose); + if (!fp) { + // This should only happen if the cgroup has already been removed with a successful call + // to killProcessGroup. Callers should only retry sendSignalToProcessGroup or + // killProcessGroup calls if they fail without ENOENT. + PLOG(ERROR) << "Failed to open " << procsfilepath; + kill(-initialPid, signal); + return false; + } + + pid_t pid; + bool file_is_empty = true; + while (fscanf(fp.get(), "%d\n", &pid) == 1 && pid >= 0) { + file_is_empty = false; + if (pid == 0) { + // Should never happen... but if it does, trying to kill this + // will boomerang right back and kill us! Let's not let that happen. + LOG(WARNING) + << "Yikes, we've been told to kill pid 0! How about we don't do that?"; + continue; + } + pid_t pgid = getpgid(pid); + if (pgid == -1) PLOG(ERROR) << "getpgid(" << pid << ") failed"; + if (pgid == pid) { + pgids.emplace(pid); + } else { + pids.emplace(pid); + } + } + if (!file_is_empty) { + // Erase all pids that will be killed when we kill the process groups. + for (auto it = pids.begin(); it != pids.end();) { + pid_t pgid = getpgid(*it); + if (pgids.count(pgid) == 1) { + it = pids.erase(it); + } else { + ++it; + } + } + } + } + + pgids.emplace(initialPid); + + // Kill all process groups. + for (const auto pgid : pgids) { + LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid + << " as part of process cgroup " << initialPid; + + if (kill(-pgid, signal) == -1 && errno != ESRCH) { + PLOG(WARNING) << "kill(" << -pgid << ", " << signal << ") failed"; + } + } + + // Kill remaining pids. + for (const auto pid : pids) { + LOG(VERBOSE) << "Killing pid " << pid << " in uid " << uid << " as part of process cgroup " + << initialPid; + + if (kill(pid, signal) == -1 && errno != ESRCH) { + PLOG(WARNING) << "kill(" << pid << ", " << signal << ") failed"; + } + } + + return true; +} + +template +static std::chrono::milliseconds toMillisec(T&& duration) { + return std::chrono::duration_cast(duration); +} + +enum class populated_status +{ + populated, + not_populated, + error +}; + +static populated_status cgroupIsPopulated(int events_fd) { + const std::string POPULATED_KEY("populated "); + const std::string::size_type MAX_EVENTS_FILE_SIZE = 32; + + std::string buf; + buf.resize(MAX_EVENTS_FILE_SIZE); + ssize_t len = TEMP_FAILURE_RETRY(pread(events_fd, buf.data(), buf.size(), 0)); + if (len == -1) { + PLOG(ERROR) << "Could not read cgroup.events: "; + // Potentially ENODEV if the cgroup has been removed since we opened this file, but that + // shouldn't have happened yet. + return populated_status::error; + } + + if (len == 0) { + LOG(ERROR) << "cgroup.events EOF"; + return populated_status::error; + } + + buf.resize(len); + + const std::string::size_type pos = buf.find(POPULATED_KEY); + if (pos == std::string::npos) { + LOG(ERROR) << "Could not find populated key in cgroup.events"; + return populated_status::error; + } + + if (pos + POPULATED_KEY.size() + 1 > len) { + LOG(ERROR) << "Partial read of cgroup.events"; + return populated_status::error; + } + + return buf[pos + POPULATED_KEY.size()] == '1' ? + populated_status::populated : populated_status::not_populated; +} + +// The default timeout of 2200ms comes from the default number of retries in a previous +// implementation of this function. The default retry value was 40 for killing and 400 for cgroup +// removal with 5ms sleeps between each retry. +static int KillProcessGroup( + uid_t uid, pid_t initialPid, int signal, bool once = false, + std::chrono::steady_clock::time_point until = std::chrono::steady_clock::now() + 2200ms) { + CHECK_GE(uid, 0); + CHECK_GT(initialPid, 0); + + // Always attempt to send a kill signal to at least the initialPid, at least once, regardless of + // whether its cgroup exists or not. This should only be necessary if a bug results in the + // migration of the targeted process out of its cgroup, which we will also attempt to kill. + const bool signal_ret = sendSignalToProcessGroup(uid, initialPid, signal); + + if (!CgroupsAvailable() || !signal_ret) return signal_ret ? 0 : -1; + + std::string hierarchy_root_path; + CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path); + + const std::string cgroup_v2_path = + ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid); + + const std::string eventsfile = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_EVENTS_FILE; + android::base::unique_fd events_fd(open(eventsfile.c_str(), O_RDONLY)); + if (events_fd.get() == -1) { + PLOG(WARNING) << "Error opening " << eventsfile << " for KillProcessGroup"; + return -1; + } + + struct pollfd fds = { + .fd = events_fd, + .events = POLLPRI, + }; + + const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + + // The primary reason to loop here is to capture any new forks or migrations that could occur + // after we send signals to the original set of processes, but before all of those processes + // exit and the cgroup becomes unpopulated, or before we remove the cgroup. We try hard to + // ensure this completes successfully to avoid permanent memory leaks, but we still place a + // large default upper bound on the amount of time we spend in this loop. The amount of CPU + // contention, and the amount of work that needs to be done in do_exit for each process + // determines how long this will take. + int ret; + do { + populated_status populated; + while ((populated = cgroupIsPopulated(events_fd.get())) == populated_status::populated && + std::chrono::steady_clock::now() < until) { + + sendSignalToProcessGroup(uid, initialPid, signal); + if (once) { + populated = cgroupIsPopulated(events_fd.get()); + break; + } + + const std::chrono::steady_clock::time_point poll_start = + std::chrono::steady_clock::now(); + + if (poll_start < until) + ret = TEMP_FAILURE_RETRY(poll(&fds, 1, toMillisec(until - poll_start).count())); + + if (ret == -1) { + // Fallback to 5ms sleeps if poll fails + PLOG(ERROR) << "Poll on " << eventsfile << "failed"; + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if (now < until) + std::this_thread::sleep_for(std::min(5ms, toMillisec(until - now))); + } + + LOG(VERBOSE) << "Waited " + << toMillisec(std::chrono::steady_clock::now() - poll_start).count() + << " ms for " << eventsfile << " poll"; + } + + const std::chrono::milliseconds kill_duration = + toMillisec(std::chrono::steady_clock::now() - start); + + if (populated == populated_status::populated) { + LOG(WARNING) << "Still waiting on process(es) to exit for cgroup " << cgroup_v2_path + << " after " << kill_duration.count() << " ms"; + // We'll still try the cgroup removal below which we expect to log an error. + } else if (populated == populated_status::not_populated) { + LOG(VERBOSE) << "Killed all processes under cgroup " << cgroup_v2_path + << " after " << kill_duration.count() << " ms"; + } + + ret = RemoveCgroup(hierarchy_root_path.c_str(), uid, initialPid); + if (ret) + PLOG(ERROR) << "Unable to remove cgroup " << cgroup_v2_path; + else + LOG(INFO) << "Removed cgroup " << cgroup_v2_path; + + if (isMemoryCgroupSupported() && UsePerAppMemcg()) { + // This per-application memcg v1 case should eventually be removed after migration to + // memcg v2. + std::string memcg_apps_path; + if (CgroupGetMemcgAppsPath(&memcg_apps_path) && + (ret = RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid)) < 0) { + const auto memcg_v1_cgroup_path = + ConvertUidPidToPath(memcg_apps_path.c_str(), uid, initialPid); + PLOG(ERROR) << "Unable to remove memcg v1 cgroup " << memcg_v1_cgroup_path; + } + } + + if (once) break; + if (std::chrono::steady_clock::now() >= until) break; + } while (ret && errno == EBUSY); + + return ret; +} + +int killProcessGroup(uid_t uid, pid_t initialPid, int signal) { + return KillProcessGroup(uid, initialPid, signal); +} + +int killProcessGroupOnce(uid_t uid, pid_t initialPid, int signal) { + return KillProcessGroup(uid, initialPid, signal, true); +} + +static int createProcessGroupInternal(uid_t uid, pid_t initialPid, std::string cgroup, + bool activate_controllers) { + auto uid_path = ConvertUidToPath(cgroup.c_str(), uid); + + struct stat cgroup_stat; + mode_t cgroup_mode = 0750; + uid_t cgroup_uid = AID_SYSTEM; + gid_t cgroup_gid = AID_SYSTEM; + int ret = 0; + + if (stat(cgroup.c_str(), &cgroup_stat) < 0) { + PLOG(ERROR) << "Failed to get stats for " << cgroup; + } else { + cgroup_mode = cgroup_stat.st_mode; + cgroup_uid = cgroup_stat.st_uid; + cgroup_gid = cgroup_stat.st_gid; + } + + if (!MkdirAndChown(uid_path, cgroup_mode, cgroup_uid, cgroup_gid)) { + PLOG(ERROR) << "Failed to make and chown " << uid_path; + return -errno; + } + if (activate_controllers) { + ret = CgroupMap::GetInstance().ActivateControllers(uid_path); + if (ret) { + LOG(ERROR) << "Failed to activate controllers in " << uid_path; + return ret; + } + } + + auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid); + + if (!MkdirAndChown(uid_pid_path, cgroup_mode, cgroup_uid, cgroup_gid)) { + PLOG(ERROR) << "Failed to make and chown " << uid_pid_path; + return -errno; + } + + auto uid_pid_procs_file = uid_pid_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE; + + if (!WriteStringToFile(std::to_string(initialPid), uid_pid_procs_file)) { + ret = -errno; + PLOG(ERROR) << "Failed to write '" << initialPid << "' to " << uid_pid_procs_file; + } + + return ret; +} + +int createProcessGroup(uid_t uid, pid_t initialPid, bool memControl) { + CHECK_GE(uid, 0); + CHECK_GT(initialPid, 0); + + if (memControl && !UsePerAppMemcg()) { + LOG(ERROR) << "service memory controls are used without per-process memory cgroup support"; + return -EINVAL; + } + + if (std::string memcg_apps_path; + isMemoryCgroupSupported() && UsePerAppMemcg() && CgroupGetMemcgAppsPath(&memcg_apps_path)) { + // Note by bvanassche: passing 'false' as fourth argument below implies that the v1 + // hierarchy is used. It is not clear to me whether the above conditions guarantee that the + // v1 hierarchy is used. + int ret = createProcessGroupInternal(uid, initialPid, memcg_apps_path, false); + if (ret != 0) { + return ret; + } + } + + std::string cgroup; + CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cgroup); + return createProcessGroupInternal(uid, initialPid, cgroup, true); +} + +static bool SetProcessGroupValue(pid_t tid, const std::string& attr_name, int64_t value) { + if (!isMemoryCgroupSupported()) { + LOG(ERROR) << "Memcg is not mounted."; + return false; + } + + std::string path; + if (!CgroupGetAttributePathForTask(attr_name, tid, &path)) { + LOG(ERROR) << "Failed to find attribute '" << attr_name << "'"; + return false; + } + + if (!WriteStringToFile(std::to_string(value), path)) { + PLOG(ERROR) << "Failed to write '" << value << "' to " << path; + return false; + } + return true; +} + +bool setProcessGroupSwappiness(uid_t, pid_t pid, int swappiness) { + aosp_hack_p(false); + return SetProcessGroupValue(pid, "MemSwappiness", swappiness); +} + +bool setProcessGroupSoftLimit(uid_t, pid_t pid, int64_t soft_limit_in_bytes) { + aosp_hack_p(false); + return SetProcessGroupValue(pid, "MemSoftLimit", soft_limit_in_bytes); +} + +bool setProcessGroupLimit(uid_t, pid_t pid, int64_t limit_in_bytes) { + aosp_hack_p(false); + return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes); +} + +bool getAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path) { + return CgroupGetAttributePathForTask(attr_name, tid, path); +} + +bool isProfileValidForProcess(const std::string& profile_name, uid_t uid, pid_t pid) { + const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name); + + if (tp == nullptr) { + return false; + } + + return tp->IsValidForProcess(uid, pid); +} diff --git a/aosp/system/core/libprocessgroup/setup/cgroup_map_write.cpp b/aosp/system/core/libprocessgroup/setup/cgroup_map_write.cpp new file mode 100644 index 000000000..fff5c90e5 --- /dev/null +++ b/aosp/system/core/libprocessgroup/setup/cgroup_map_write.cpp @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2019 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "libprocessgroup" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cgroup_descriptor.h" + +using android::base::GetUintProperty; +using android::base::StringPrintf; +using android::base::unique_fd; + +namespace android { +namespace cgrouprc { + +static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json"; +static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json"; + +static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json"; + +static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid, + const std::string& gid, bool permissive_mode = false) { + uid_t pw_uid = -1; + gid_t gr_gid = -1; + + if (!uid.empty()) { + passwd* uid_pwd = getpwnam(uid.c_str()); + if (!uid_pwd) { + PLOG(ERROR) << "Unable to decode UID for '" << uid << "'"; + return false; + } + + pw_uid = uid_pwd->pw_uid; + gr_gid = -1; + + if (!gid.empty()) { + group* gid_pwd = getgrnam(gid.c_str()); + if (!gid_pwd) { + PLOG(ERROR) << "Unable to decode GID for '" << gid << "'"; + return false; + } + gr_gid = gid_pwd->gr_gid; + } + } + + auto dir = std::unique_ptr(opendir(path.c_str()), closedir); + + if (dir == NULL) { + PLOG(ERROR) << "opendir failed for " << path; + return false; + } + + struct dirent* dir_entry; + while ((dir_entry = readdir(dir.get()))) { + if (!strcmp("..", dir_entry->d_name)) { + continue; + } + + std::string file_path = path + "/" + dir_entry->d_name; + + if (pw_uid != -1 && lchown(file_path.c_str(), pw_uid, gr_gid) < 0) { + PLOG(ERROR) << "lchown() failed for " << file_path; + return false; + } + + if (fchmodat(AT_FDCWD, file_path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0 && + (errno != EROFS || !permissive_mode)) { + PLOG(ERROR) << "fchmodat() failed for " << path; + return false; + } + } + + return true; +} + +static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid, + const std::string& gid) { + bool permissive_mode = false; + + if (mode == 0) { + /* Allow chmod to fail */ + permissive_mode = true; + mode = 0755; + } + + if (mkdir(path.c_str(), mode) != 0) { + // /acct is a special case when the directory already exists + if (errno != EEXIST) { + PLOG(ERROR) << "mkdir() failed for " << path; + return false; + } else { + permissive_mode = true; + } + } + + if (uid.empty() && permissive_mode) { + return true; + } + + if (!ChangeDirModeAndOwner(path, mode, uid, gid, permissive_mode)) { + PLOG(ERROR) << "change of ownership or mode failed for " << path; + return false; + } + + return true; +} + +static void MergeCgroupToDescriptors(std::map* descriptors, + const Json::Value& cgroup, const std::string& name, + const std::string& root_path, int cgroups_version) { + const std::string cgroup_path = cgroup["Path"].asString(); + std::string path; + + if (!root_path.empty()) { + path = root_path; + if (cgroup_path != ".") { + path += "/"; + path += cgroup_path; + } + } else { + path = cgroup_path; + } + + uint32_t controller_flags = 0; + + if (cgroup["NeedsActivation"].isBool() && cgroup["NeedsActivation"].asBool()) { + controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION; + } + + if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) { + controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL; + } + + CgroupDescriptor descriptor( + cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8), + cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags); + + auto iter = descriptors->find(name); + if (iter == descriptors->end()) { + descriptors->emplace(name, descriptor); + } else { + iter->second = descriptor; + } +} + +static bool ReadDescriptorsFromFile(const std::string& file_name, + std::map* descriptors) { + std::vector result; + std::string json_doc; + + if (!android::base::ReadFileToString(file_name, &json_doc)) { + PLOG(ERROR) << "Failed to read task profiles from " << file_name; + return false; + } + + Json::CharReaderBuilder builder; + std::unique_ptr reader(builder.newCharReader()); + Json::Value root; + std::string errorMessage; + if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) { + LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage; + return false; + } + + if (root.isMember("Cgroups")) { + const Json::Value& cgroups = root["Cgroups"]; + for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) { + std::string name = cgroups[i]["Controller"].asString(); + MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1); + } + } + + if (root.isMember("Cgroups2")) { + const Json::Value& cgroups2 = root["Cgroups2"]; + std::string root_path = cgroups2["Path"].asString(); + MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2); + + const Json::Value& childGroups = cgroups2["Controllers"]; + for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) { + std::string name = childGroups[i]["Controller"].asString(); + MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2); + } + } + + return true; +} + +static bool ReadDescriptors(std::map* descriptors) { + // load system cgroup descriptors + if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) { + return false; + } + + // load API-level specific system cgroups descriptors if available + unsigned int api_level = GetUintProperty("ro.product.first_api_level", 0); + if (api_level > 0) { + std::string api_cgroups_path = + android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level); + if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) { + if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) { + return false; + } + } + } + + // load vendor cgroup descriptors if the file exists + if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) && + !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) { + return false; + } + + return true; +} + +// To avoid issues in sdk_mac build +#if defined(__ANDROID__) + +static bool IsOptionalController(const format::CgroupController* controller) { + return controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL; +} + +static bool MountV2CgroupController(const CgroupDescriptor& descriptor) { + const format::CgroupController* controller = descriptor.controller(); + + // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions, + // try to create again in case the mount point is changed + if (!Mkdir(controller->path(), 0, "", "")) { + LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup"; + return false; + } + + // The memory_recursiveprot mount option has been introduced by kernel commit + // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to + // mount with that option enabled. If mounting fails because the kernel is too old, + // retry without that mount option. + if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID, + "memory_recursiveprot") < 0) { + LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without."; + if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID, + nullptr) < 0) { + PLOG(ERROR) << "Failed to mount cgroup v2"; + return IsOptionalController(controller); + } + } + + // selinux permissions change after mounting, so it's ok to change mode and owner now + if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(), + descriptor.gid())) { + PLOG(ERROR) << "Change of ownership or mode failed for controller " << controller->name(); + return IsOptionalController(controller); + } + + return true; +} + +static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) { + const format::CgroupController* controller = descriptor.controller(); + + if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) { + LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup"; + return false; + } + + if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) { + std::string str = "+"; + str += controller->name(); + std::string path = controller->path(); + path += "/cgroup.subtree_control"; + + if (!base::WriteStringToFile(str, path)) { + if (IsOptionalController(controller)) { + PLOG(INFO) << "Failed to activate optional controller " << controller->name(); + return true; + } + PLOG(ERROR) << "Failed to activate controller " << controller->name(); + return false; + } + } + + return true; +} + +static bool MountV1CgroupController(const CgroupDescriptor& descriptor) { + const format::CgroupController* controller = descriptor.controller(); + + // mkdir [mode] [owner] [group] + if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) { + LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup"; + return false; + } + + // Unfortunately historically cpuset controller was mounted using a mount command + // different from all other controllers. This results in controller attributes not + // to be prepended with controller name. For example this way instead of + // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what + // the system currently expects. + int res; + if (!strcmp(controller->name(), "cpuset")) { + // mount cpuset none /dev/cpuset nodev noexec nosuid + res = mount("none", controller->path(), controller->name(), + MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr); + } else { + // mount cgroup none nodev noexec nosuid + res = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID, + controller->name()); + } + if (res != 0) { + if (IsOptionalController(controller)) { + PLOG(INFO) << "Failed to mount optional controller " << controller->name(); + return true; + } + PLOG(ERROR) << "Failed to mount controller " << controller->name(); + return false; + } + return true; +} + +static bool SetupCgroup(const CgroupDescriptor& descriptor) { + const format::CgroupController* controller = descriptor.controller(); + + if (controller->version() == 2) { + if (!strcmp(controller->name(), CGROUPV2_HIERARCHY_NAME)) { + return MountV2CgroupController(descriptor); + } else { + return ActivateV2CgroupController(descriptor); + } + } else { + return MountV1CgroupController(descriptor); + } +} + +#else + +// Stubs for non-Android targets. +static bool SetupCgroup(const CgroupDescriptor&) { + return false; +} + +#endif + +static bool WriteRcFile(const std::map& descriptors) { + unique_fd fd(TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IRGRP | S_IROTH))); + if (fd < 0) { + PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH; + return false; + } + + format::CgroupFile fl; + fl.version_ = format::CgroupFile::FILE_CURR_VERSION; + fl.controller_count_ = descriptors.size(); + int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl))); + if (ret < 0) { + PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH; + return false; + } + + for (const auto& [name, descriptor] : descriptors) { + ret = TEMP_FAILURE_RETRY( + write(fd, descriptor.controller(), sizeof(format::CgroupController))); + if (ret < 0) { + PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH; + return false; + } + } + + return true; +} + +CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name, + const std::string& path, mode_t mode, const std::string& uid, + const std::string& gid, uint32_t flags = 0) + : controller_(version, flags, name, path), mode_(mode), uid_(uid), gid_(gid) {} + +void CgroupDescriptor::set_mounted(bool mounted) { + uint32_t flags = controller_.flags(); + if (mounted) { + flags |= CGROUPRC_CONTROLLER_FLAG_MOUNTED; + } else { + flags &= ~CGROUPRC_CONTROLLER_FLAG_MOUNTED; + } + controller_.set_flags(flags); +} + +} // namespace cgrouprc +} // namespace android + +bool CgroupSetup() { + using namespace android::cgrouprc; + + std::map descriptors; +#if 0 + if (getpid() != 1) { + LOG(ERROR) << "Cgroup setup can be done only by init process"; + return false; + } +#endif + // Make sure we do this only one time. No need for std::call_once because + // init is a single-threaded process + if (access(CGROUPS_RC_PATH, F_OK) == 0) { + LOG(WARNING) << "Attempt to call CgroupSetup() more than once"; + return true; + } + + // load cgroups.json file + if (!ReadDescriptors(&descriptors)) { + LOG(ERROR) << "Failed to load cgroup description file"; + return false; + } + + // setup cgroups + for (auto& [name, descriptor] : descriptors) { + if (SetupCgroup(descriptor)) { + descriptor.set_mounted(true); + } else { + // issue a warning and proceed with the next cgroup + LOG(WARNING) << "Failed to setup " << name << " cgroup"; + } + } + + // mkdir 0711 system system + if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) { + LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file"; + return false; + } + + // Generate file which can be directly mmapped into + // process memory. This optimizes performance, memory usage + // and limits infrormation shared with unprivileged processes + // to the minimum subset of information from cgroups.json + if (!WriteRcFile(descriptors)) { + LOG(ERROR) << "Failed to write " << CGROUPS_RC_PATH << " file"; + return false; + } + + // chmod 0644 + if (fchmodat(AT_FDCWD, CGROUPS_RC_PATH, 0644, AT_SYMLINK_NOFOLLOW) < 0) { + PLOG(ERROR) << "fchmodat() failed"; + return false; + } + + return true; +} diff --git a/aosp/system/core/libsuspend/autosuspend.c b/aosp/system/core/libsuspend/autosuspend.c new file mode 100644 index 000000000..3ce1f6e2e --- /dev/null +++ b/aosp/system/core/libsuspend/autosuspend.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 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. + */ + +#define LOG_TAG "libsuspend" + +#include + +#include + +#include + +#include "autosuspend_ops.h" + +static struct autosuspend_ops* autosuspend_ops = NULL; +static bool autosuspend_enabled; + +static int autosuspend_init(void) { + aosp_hack_p(0); + if (autosuspend_ops != NULL) { + return 0; + } + + autosuspend_ops = autosuspend_wakeup_count_init(); + if (autosuspend_ops == NULL) { + ALOGE("failed to initialize autosuspend"); + return -1; + } + + ALOGV("autosuspend initialized"); + return 0; +} + +int autosuspend_enable(void) { + aosp_hack_p(0); + int ret; + + ret = autosuspend_init(); + if (ret) { + return ret; + } + + ALOGV("autosuspend_enable"); + + if (autosuspend_enabled) { + return 0; + } + + ret = autosuspend_ops->enable(); + if (ret) { + return ret; + } + + autosuspend_enabled = true; + return 0; +} + +int autosuspend_disable(void) { + aosp_hack_p(0); + int ret; + + ret = autosuspend_init(); + if (ret) { + return ret; + } + + ALOGV("autosuspend_disable"); + + if (!autosuspend_enabled) { + return 0; + } + + ret = autosuspend_ops->disable(); + if (ret) { + return ret; + } + + autosuspend_enabled = false; + return 0; +} + +int autosuspend_force_suspend(int timeout_ms) { + aosp_hack_p(0); + int ret; + + ret = autosuspend_init(); + if (ret) { + return ret; + } + + ALOGV("autosuspend_force_suspend"); + + return autosuspend_ops->force_suspend(timeout_ms); +} + +void autosuspend_set_wakeup_callback(void (*func)(bool success)) { + aosp_hack(); + int ret; + + ret = autosuspend_init(); + if (ret) { + return; + } + + ALOGV("set_wakeup_callback"); + + autosuspend_ops->set_wakeup_callback(func); +} diff --git a/aosp/system/core/rootdir/init.rc b/aosp/system/core/rootdir/init.rc new file mode 100644 index 000000000..d4b8bb0ba --- /dev/null +++ b/aosp/system/core/rootdir/init.rc @@ -0,0 +1,1360 @@ +# Copyright (C) 2012 The Android Open Source Project +# +# IMPORTANT: Do not create world writable files or directories. +# This is a common source of Android security bugs. +# + +import /init.environ.rc +import /system/etc/init/hw/init.usb.rc +import /init.${ro.hardware}.rc +import /vendor/etc/init/hw/init.${ro.hardware}.rc +# import /system/etc/init/hw/init.usb.configfs.rc +import /system/etc/init/hw/init.${ro.zygote}.rc + +# Cgroups are mounted right before early-init using list from /etc/cgroups.json +on early-init + # Disable sysrq from keyboard + write /proc/sys/kernel/sysrq 0 + + # Android doesn't need kernel module autoloading, and it causes SELinux + # denials. So disable it by setting modprobe to the empty string. Note: to + # explicitly set a sysctl to an empty string, a trailing newline is needed. + write /proc/sys/kernel/modprobe \n + + # Set the security context of /adb_keys if present. + restorecon /adb_keys + + # Set the security context of /postinstall if present. + restorecon /postinstall + + mkdir /acct/uid + + # memory.pressure_level used by lmkd + chown root system /dev/memcg/memory.pressure_level + chmod 0040 /dev/memcg/memory.pressure_level + # app mem cgroups, used by activity manager, lmkd and zygote + mkdir /dev/memcg/apps/ 0755 system system + # cgroup for system_server and surfaceflinger + mkdir /dev/memcg/system 0550 system system + + # symlink the Android specific /dev/tun to Linux expected /dev/net/tun + mkdir /dev/net 0755 root root + symlink ../tun /dev/net/tun + + # set RLIMIT_NICE to allow priorities from 19 to -20 + setrlimit nice 40 40 + + # Allow up to 32K FDs per process + setrlimit nofile 32768 32768 + + # set RLIMIT_MEMLOCK to 64KB + setrlimit memlock 65536 65536 + + # Set up linker config subdirectories based on mount namespaces + mkdir /linkerconfig/bootstrap 0755 + mkdir /linkerconfig/default 0755 + + # Disable dm-verity hash prefetching, since it doesn't help performance + # Read more in b/136247322 + write /sys/module/dm_verity/parameters/prefetch_cluster 0 + + # Generate empty ld.config.txt for early executed processes which rely on + # /system/lib libraries. + write /linkerconfig/bootstrap/ld.config.txt \# + write /linkerconfig/default/ld.config.txt \# + chmod 644 /linkerconfig/bootstrap/ld.config.txt + chmod 644 /linkerconfig/default/ld.config.txt + + # Mount bootstrap linker configuration as current + mount none /linkerconfig/bootstrap /linkerconfig bind rec + + start ueventd + + # Run apexd-bootstrap so that APEXes that provide critical libraries + # become available. Note that this is executed as exec_start to ensure that + # the libraries are available to the processes started after this statement. + exec_start apexd-bootstrap + perform_apex_config --bootstrap + + # These must already exist by the time boringssl_self_test32 / boringssl_self_test64 run. + mkdir /dev/boringssl 0755 root root + mkdir /dev/boringssl/selftest 0755 root root + + # Mount tracefs (with GID=AID_READTRACEFS) + mount tracefs tracefs /sys/kernel/tracing gid=3012 + + # create sys dirctory + mkdir /dev/sys 0755 system system + mkdir /dev/sys/fs 0755 system system + mkdir /dev/sys/block 0755 system system + + # Create location for fs_mgr to store abbreviated output from filesystem + # checker programs. + mkdir /dev/fscklogs 0770 root system + + # Create tmpfs for use by the shell user. + mount tmpfs tmpfs /tmp + restorecon /tmp + chown shell shell /tmp + chmod 0771 /tmp + +on init + sysclktz 0 + + # Mix device-specific information into the entropy pool + copy /proc/cmdline /dev/urandom + copy /system/etc/prop.default /dev/urandom + + symlink /proc/self/fd/0 /dev/stdin + symlink /proc/self/fd/1 /dev/stdout + symlink /proc/self/fd/2 /dev/stderr + + # Create socket dir for ot-daemon + mkdir /dev/socket/ot-daemon 0770 thread_network thread_network + + # Create energy-aware scheduler tuning nodes + mkdir /dev/stune/foreground + mkdir /dev/stune/background + mkdir /dev/stune/top-app + mkdir /dev/stune/rt + chown system system /dev/stune + chown system system /dev/stune/foreground + chown system system /dev/stune/background + chown system system /dev/stune/top-app + chown system system /dev/stune/rt + chown system system /dev/stune/tasks + chown system system /dev/stune/foreground/tasks + chown system system /dev/stune/background/tasks + chown system system /dev/stune/top-app/tasks + chown system system /dev/stune/rt/tasks + chown system system /dev/stune/cgroup.procs + chown system system /dev/stune/foreground/cgroup.procs + chown system system /dev/stune/background/cgroup.procs + chown system system /dev/stune/top-app/cgroup.procs + chown system system /dev/stune/rt/cgroup.procs + chmod 0664 /dev/stune/tasks + chmod 0664 /dev/stune/foreground/tasks + chmod 0664 /dev/stune/background/tasks + chmod 0664 /dev/stune/top-app/tasks + chmod 0664 /dev/stune/rt/tasks + chmod 0664 /dev/stune/cgroup.procs + chmod 0664 /dev/stune/foreground/cgroup.procs + chmod 0664 /dev/stune/background/cgroup.procs + chmod 0664 /dev/stune/top-app/cgroup.procs + chmod 0664 /dev/stune/rt/cgroup.procs + + # cpuctl hierarchy for devices using utilclamp + mkdir /dev/cpuctl/foreground + mkdir /dev/cpuctl/background + mkdir /dev/cpuctl/top-app + mkdir /dev/cpuctl/rt + mkdir /dev/cpuctl/system + mkdir /dev/cpuctl/system-background + mkdir /dev/cpuctl/dex2oat + chown system system /dev/cpuctl + chown system system /dev/cpuctl/foreground + chown system system /dev/cpuctl/background + chown system system /dev/cpuctl/top-app + chown system system /dev/cpuctl/rt + chown system system /dev/cpuctl/system + chown system system /dev/cpuctl/system-background + chown system system /dev/cpuctl/dex2oat + chown system system /dev/cpuctl/tasks + chown system system /dev/cpuctl/foreground/tasks + chown system system /dev/cpuctl/background/tasks + chown system system /dev/cpuctl/top-app/tasks + chown system system /dev/cpuctl/rt/tasks + chown system system /dev/cpuctl/system/tasks + chown system system /dev/cpuctl/system-background/tasks + chown system system /dev/cpuctl/dex2oat/tasks + chown system system /dev/cpuctl/cgroup.procs + chown system system /dev/cpuctl/foreground/cgroup.procs + chown system system /dev/cpuctl/background/cgroup.procs + chown system system /dev/cpuctl/top-app/cgroup.procs + chown system system /dev/cpuctl/rt/cgroup.procs + chown system system /dev/cpuctl/system/cgroup.procs + chown system system /dev/cpuctl/system-background/cgroup.procs + chown system system /dev/cpuctl/dex2oat/cgroup.procs + chmod 0664 /dev/cpuctl/tasks + chmod 0664 /dev/cpuctl/foreground/tasks + chmod 0664 /dev/cpuctl/background/tasks + chmod 0664 /dev/cpuctl/top-app/tasks + chmod 0664 /dev/cpuctl/rt/tasks + chmod 0664 /dev/cpuctl/system/tasks + chmod 0664 /dev/cpuctl/system-background/tasks + chmod 0664 /dev/cpuctl/dex2oat/tasks + chmod 0664 /dev/cpuctl/cgroup.procs + chmod 0664 /dev/cpuctl/foreground/cgroup.procs + chmod 0664 /dev/cpuctl/background/cgroup.procs + chmod 0664 /dev/cpuctl/top-app/cgroup.procs + chmod 0664 /dev/cpuctl/rt/cgroup.procs + chmod 0664 /dev/cpuctl/system/cgroup.procs + chmod 0664 /dev/cpuctl/system-background/cgroup.procs + chmod 0664 /dev/cpuctl/dex2oat/cgroup.procs + + # Create a cpu group for NNAPI HAL processes + mkdir /dev/cpuctl/nnapi-hal + chown system system /dev/cpuctl/nnapi-hal + chown system system /dev/cpuctl/nnapi-hal/tasks + chown system system /dev/cpuctl/nnapi-hal/cgroup.procs + chmod 0664 /dev/cpuctl/nnapi-hal/tasks + chmod 0664 /dev/cpuctl/nnapi-hal/cgroup.procs + write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1 + write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1 + + # Create a cpu group for camera daemon processes + mkdir /dev/cpuctl/camera-daemon + chown system system /dev/cpuctl/camera-daemon + chown system system /dev/cpuctl/camera-daemon/tasks + chown system system /dev/cpuctl/camera-daemon/cgroup.procs + chmod 0664 /dev/cpuctl/camera-daemon/tasks + chmod 0664 /dev/cpuctl/camera-daemon/cgroup.procs + + # Create an stune group for camera-specific processes + mkdir /dev/stune/camera-daemon + chown system system /dev/stune/camera-daemon + chown system system /dev/stune/camera-daemon/tasks + chown system system /dev/stune/camera-daemon/cgroup.procs + chmod 0664 /dev/stune/camera-daemon/tasks + chmod 0664 /dev/stune/camera-daemon/cgroup.procs + + # Create an stune group for NNAPI HAL processes + mkdir /dev/stune/nnapi-hal + chown system system /dev/stune/nnapi-hal + chown system system /dev/stune/nnapi-hal/tasks + chown system system /dev/stune/nnapi-hal/cgroup.procs + chmod 0664 /dev/stune/nnapi-hal/tasks + chmod 0664 /dev/stune/nnapi-hal/cgroup.procs + write /dev/stune/nnapi-hal/schedtune.boost 1 + write /dev/stune/nnapi-hal/schedtune.prefer_idle 1 + + # Create blkio group and apply initial settings. + # This feature needs kernel to support it, and the + # device's init.rc must actually set the correct values. + mkdir /dev/blkio/background + chown system system /dev/blkio + chown system system /dev/blkio/background + chown system system /dev/blkio/tasks + chown system system /dev/blkio/background/tasks + chown system system /dev/blkio/cgroup.procs + chown system system /dev/blkio/background/cgroup.procs + chmod 0664 /dev/blkio/tasks + chmod 0664 /dev/blkio/background/tasks + chmod 0664 /dev/blkio/cgroup.procs + chmod 0664 /dev/blkio/background/cgroup.procs + write /dev/blkio/blkio.weight 1000 + write /dev/blkio/background/blkio.weight 200 + write /dev/blkio/background/blkio.bfq.weight 10 + write /dev/blkio/blkio.group_idle 0 + write /dev/blkio/background/blkio.group_idle 0 + write /dev/blkio/background/blkio.prio.class restrict-to-be + + restorecon_recursive /mnt + + mount configfs none /config nodev noexec nosuid + chmod 0770 /config/sdcardfs + chown system package_info /config/sdcardfs + + # Mount binderfs + mkdir /dev/binderfs + mount binder binder /dev/binderfs stats=global + chmod 0755 /dev/binderfs + + # Mount fusectl + # mount fusectl none /sys/fs/fuse/connections + + symlink /dev/binderfs/binder /dev/binder + symlink /dev/binderfs/hwbinder /dev/hwbinder + symlink /dev/binderfs/vndbinder /dev/vndbinder + + chmod 0666 /dev/binderfs/hwbinder + chmod 0666 /dev/binderfs/binder + chmod 0666 /dev/binderfs/vndbinder + + mkdir /mnt/secure 0700 root root + mkdir /mnt/secure/asec 0700 root root + mkdir /mnt/asec 0755 root system + mkdir /mnt/obb 0755 root system + mkdir /mnt/media_rw 0750 root external_storage + mkdir /mnt/user 0755 root root + mkdir /mnt/user/0 0755 root root + mkdir /mnt/user/0/self 0755 root root + mkdir /mnt/user/0/emulated 0755 root root + mkdir /mnt/user/0/emulated/0 0755 root root + + # Prepare directories for pass through processes + mkdir /mnt/pass_through 0700 root root + mkdir /mnt/pass_through/0 0710 root media_rw + mkdir /mnt/pass_through/0/self 0710 root media_rw + mkdir /mnt/pass_through/0/emulated 0710 root media_rw + mkdir /mnt/pass_through/0/emulated/0 0710 root media_rw + + mkdir /mnt/expand 0771 system system + mkdir /mnt/appfuse 0711 root root + + # Storage views to support runtime permissions + mkdir /mnt/runtime 0700 root root + mkdir /mnt/runtime/default 0755 root root + mkdir /mnt/runtime/default/self 0755 root root + mkdir /mnt/runtime/read 0755 root root + mkdir /mnt/runtime/read/self 0755 root root + mkdir /mnt/runtime/write 0755 root root + mkdir /mnt/runtime/write/self 0755 root root + mkdir /mnt/runtime/full 0755 root root + mkdir /mnt/runtime/full/self 0755 root root + + # Symlink to keep legacy apps working in multi-user world + symlink /storage/self/primary /mnt/sdcard + symlink /mnt/user/0/primary /mnt/runtime/default/self/primary + + write /proc/sys/kernel/panic_on_oops 1 + write /proc/sys/kernel/hung_task_timeout_secs 0 + write /proc/cpu/alignment 4 + + # scheduler tunables + # Disable auto-scaling of scheduler tunables with hotplug. The tunables + # will vary across devices in unpredictable ways if allowed to scale with + # cpu cores. + write /proc/sys/kernel/sched_tunable_scaling 0 + write /proc/sys/kernel/sched_latency_ns 10000000 + write /proc/sys/kernel/sched_wakeup_granularity_ns 2000000 + write /proc/sys/kernel/sched_child_runs_first 0 + + write /proc/sys/kernel/randomize_va_space 2 + write /proc/sys/vm/mmap_min_addr 32768 + write /proc/sys/net/ipv4/ping_group_range "0 2147483647" + write /proc/sys/net/unix/max_dgram_qlen 2400 + + # Assign reasonable ceiling values for socket rcv/snd buffers. + # These should almost always be overridden by the target per the + # the corresponding technology maximums. + write /proc/sys/net/core/rmem_max 262144 + write /proc/sys/net/core/wmem_max 262144 + + # reflect fwmark from incoming packets onto generated replies + write /proc/sys/net/ipv4/fwmark_reflect 1 + write /proc/sys/net/ipv6/fwmark_reflect 1 + + # set fwmark on accepted sockets + write /proc/sys/net/ipv4/tcp_fwmark_accept 1 + + # disable icmp redirects + write /proc/sys/net/ipv4/conf/all/accept_redirects 0 + write /proc/sys/net/ipv6/conf/all/accept_redirects 0 + + # /proc/net/fib_trie leaks interface IP addresses + chmod 0400 /proc/net/fib_trie + + # sets up initial cpusets for ActivityManager + # this ensures that the cpusets are present and usable, but the device's + # init.rc must actually set the correct cpus + mkdir /dev/cpuset/foreground + copy /dev/cpuset/cpus /dev/cpuset/foreground/cpus + copy /dev/cpuset/mems /dev/cpuset/foreground/mems + mkdir /dev/cpuset/background + copy /dev/cpuset/cpus /dev/cpuset/background/cpus + copy /dev/cpuset/mems /dev/cpuset/background/mems + + # system-background is for system tasks that should only run on + # little cores, not on bigs + mkdir /dev/cpuset/system-background + copy /dev/cpuset/cpus /dev/cpuset/system-background/cpus + copy /dev/cpuset/mems /dev/cpuset/system-background/mems + + # restricted is for system tasks that are being throttled + # due to screen off. + mkdir /dev/cpuset/restricted + copy /dev/cpuset/cpus /dev/cpuset/restricted/cpus + copy /dev/cpuset/mems /dev/cpuset/restricted/mems + + mkdir /dev/cpuset/top-app + copy /dev/cpuset/cpus /dev/cpuset/top-app/cpus + copy /dev/cpuset/mems /dev/cpuset/top-app/mems + + # create a cpuset for camera daemon processes + mkdir /dev/cpuset/camera-daemon + copy /dev/cpuset/cpus /dev/cpuset/camera-daemon/cpus + copy /dev/cpuset/mems /dev/cpuset/camera-daemon/mems + + # change permissions for all cpusets we'll touch at runtime + chown system system /dev/cpuset + chown system system /dev/cpuset/foreground + chown system system /dev/cpuset/background + chown system system /dev/cpuset/system-background + chown system system /dev/cpuset/top-app + chown system system /dev/cpuset/restricted + chown system system /dev/cpuset/camera-daemon + chown system system /dev/cpuset/tasks + chown system system /dev/cpuset/foreground/tasks + chown system system /dev/cpuset/background/tasks + chown system system /dev/cpuset/system-background/tasks + chown system system /dev/cpuset/top-app/tasks + chown system system /dev/cpuset/restricted/tasks + chown system system /dev/cpuset/camera-daemon/tasks + chown system system /dev/cpuset/cgroup.procs + chown system system /dev/cpuset/foreground/cgroup.procs + chown system system /dev/cpuset/background/cgroup.procs + chown system system /dev/cpuset/system-background/cgroup.procs + chown system system /dev/cpuset/top-app/cgroup.procs + chown system system /dev/cpuset/restricted/cgroup.procs + chown system system /dev/cpuset/camera-daemon/cgroup.procs + + # set system-background to 0775 so SurfaceFlinger can touch it + chmod 0775 /dev/cpuset/system-background + + chmod 0664 /dev/cpuset/foreground/tasks + chmod 0664 /dev/cpuset/background/tasks + chmod 0664 /dev/cpuset/system-background/tasks + chmod 0664 /dev/cpuset/top-app/tasks + chmod 0664 /dev/cpuset/restricted/tasks + chmod 0664 /dev/cpuset/tasks + chmod 0664 /dev/cpuset/camera-daemon/tasks + chmod 0664 /dev/cpuset/foreground/cgroup.procs + chmod 0664 /dev/cpuset/background/cgroup.procs + chmod 0664 /dev/cpuset/system-background/cgroup.procs + chmod 0664 /dev/cpuset/top-app/cgroup.procs + chmod 0664 /dev/cpuset/restricted/cgroup.procs + chmod 0664 /dev/cpuset/cgroup.procs + chmod 0664 /dev/cpuset/camera-daemon/cgroup.procs + + # make the PSI monitor accessible to others + chown system system /proc/pressure/memory + chmod 0664 /proc/pressure/memory + + # qtaguid will limit access to specific data based on group memberships. + # net_bw_acct grants impersonation of socket owners. + # net_bw_stats grants access to other apps' detailed tagged-socket stats. + chown root net_bw_acct /proc/net/xt_qtaguid/ctrl + chown root net_bw_stats /proc/net/xt_qtaguid/stats + + # Allow everybody to read the xt_qtaguid resource tracking misc dev. + # This is needed by any process that uses socket tagging. + chmod 0644 /dev/xt_qtaguid + + mount bpf bpf /sys/fs/bpf nodev noexec nosuid + + # pstore/ramoops previous console log + mount pstore pstore /sys/fs/pstore nodev noexec nosuid + chown system log /sys/fs/pstore + chmod 0550 /sys/fs/pstore + chown system log /sys/fs/pstore/console-ramoops + chmod 0440 /sys/fs/pstore/console-ramoops + chown system log /sys/fs/pstore/console-ramoops-0 + chmod 0440 /sys/fs/pstore/console-ramoops-0 + chown system log /sys/fs/pstore/pmsg-ramoops-0 + chmod 0440 /sys/fs/pstore/pmsg-ramoops-0 + + # enable armv8_deprecated instruction hooks + write /proc/sys/abi/swp 1 + + # Linux's execveat() syscall may construct paths containing /dev/fd + # expecting it to point to /proc/self/fd + symlink /proc/self/fd /dev/fd + + export DOWNLOAD_CACHE /data/cache + + # This allows the ledtrig-transient properties to be created here so + # that they can be chown'd to system:system later on boot + write /sys/class/leds/vibrator/trigger "transient" + + # This is used by Bionic to select optimized routines. + write /dev/cpu_variant:${ro.bionic.arch} ${ro.bionic.cpu_variant} + chmod 0444 /dev/cpu_variant:${ro.bionic.arch} + write /dev/cpu_variant:${ro.bionic.2nd_arch} ${ro.bionic.2nd_cpu_variant} + chmod 0444 /dev/cpu_variant:${ro.bionic.2nd_arch} + + # Allow system processes to read / write power state. + chown system system /sys/power/state + chown system system /sys/power/wakeup_count + chmod 0660 /sys/power/state + + chown radio wakelock /sys/power/wake_lock + chown radio wakelock /sys/power/wake_unlock + chmod 0660 /sys/power/wake_lock + chmod 0660 /sys/power/wake_unlock + + # Start logd before any other services run to ensure we capture all of their logs. + start logd + # Start lmkd before any other services run so that it can register them + write /proc/sys/vm/watermark_boost_factor 0 + chown root system /sys/module/lowmemorykiller/parameters/adj + chmod 0664 /sys/module/lowmemorykiller/parameters/adj + chown root system /sys/module/lowmemorykiller/parameters/minfree + chmod 0664 /sys/module/lowmemorykiller/parameters/minfree + start lmkd + + # Start essential services. + start servicemanager + start hwservicemanager + start vndservicemanager + +# Run boringssl self test for each ABI. Any failures trigger reboot to firmware. +import /system/etc/init/hw/init.boringssl.${ro.zygote}.rc + +service boringssl_self_test32 /system/bin/boringssl_self_test32 + reboot_on_failure reboot,boringssl-self-check-failed + stdio_to_kmsg + # Explicitly specify that boringssl_self_test32 doesn't require any capabilities + capabilities + user nobody + +service boringssl_self_test64 /system/bin/boringssl_self_test64 + reboot_on_failure reboot,boringssl-self-check-failed + stdio_to_kmsg + # Explicitly specify that boringssl_self_test64 doesn't require any capabilities + capabilities + user nobody + +service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32 + reboot_on_failure reboot,boringssl-self-check-failed + stdio_to_kmsg + # Explicitly specify that boringssl_self_test_apex32 doesn't require any capabilities + capabilities + user nobody + +service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64 + reboot_on_failure reboot,boringssl-self-check-failed + stdio_to_kmsg + # Explicitly specify that boringssl_self_test_apex64 doesn't require any capabilities + capabilities + user nobody + +# Healthd can trigger a full boot from charger mode by signaling this +# property when the power button is held. +on property:sys.boot_from_charger_mode=1 + class_stop charger + trigger late-init + +# Indicate to fw loaders that the relevant mounts are up. +on firmware_mounts_complete + rm /dev/.booting + +# Mount filesystems and start core system services. +on late-init + trigger early-fs + + # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter + # '--early' can be specified to skip entries with 'latemount'. + # /system and /vendor must be mounted by the end of the fs stage, + # while /data is optional. + trigger fs + trigger post-fs + + # Mount fstab in init.{$device}.rc by mount_all with '--late' parameter + # to only mount entries with 'latemount'. This is needed if '--early' is + # specified in the previous mount_all command on the fs stage. + # With /system mounted and properties form /system + /factory available, + # some services can be started. + trigger late-fs + + # Now we can mount /data. File encryption requires keymaster to decrypt + # /data, which in turn can only be loaded when system properties are present. + trigger post-fs-data + + # Should be before netd, but after apex, properties and logging is available. + trigger load_bpf_programs + + # Now we can start zygote. + trigger zygote-start + + # Remove a file to wake up anything waiting for firmware. + trigger firmware_mounts_complete + + trigger early-boot + trigger boot + +on early-fs + # Once metadata has been mounted, we'll need vold to deal with userdata checkpointing + start vold + +on post-fs + exec - system system -- /system/bin/vdc checkpoint markBootAttempt + + # Once everything is setup, no need to modify /. + # The bind+remount combination allows this to work in containers. + mount rootfs rootfs / remount bind ro nodev + + # Mount default storage into root namespace + mount none /mnt/user/0 /storage bind rec + mount none none /storage slave rec + + # Make sure /sys/kernel/debug (if present) is labeled properly + # Note that tracefs may be mounted under debug, so we need to cross filesystems + restorecon --recursive --cross-filesystems /sys/kernel/debug + + # We chown/chmod /cache again so because mount is run as root + defaults + chown system cache /cache + chmod 0770 /cache + # We restorecon /cache in case the cache partition has been reset. + restorecon_recursive /cache + + # Create /cache/recovery in case it's not there. It'll also fix the odd + # permissions if created by the recovery system. + mkdir /cache/recovery 0770 system cache + + # Backup/restore mechanism uses the cache partition + mkdir /cache/backup_stage 0700 system system + mkdir /cache/backup 0700 system system + + #change permissions on vmallocinfo so we can grab it from bugreports + chown root log /proc/vmallocinfo + chmod 0440 /proc/vmallocinfo + + chown root log /proc/slabinfo + chmod 0440 /proc/slabinfo + + chown root log /proc/pagetypeinfo + chmod 0440 /proc/pagetypeinfo + + #change permissions on kmsg & sysrq-trigger so bugreports can grab kthread stacks + chown root system /proc/kmsg + chmod 0440 /proc/kmsg + chown root system /proc/sysrq-trigger + chmod 0220 /proc/sysrq-trigger + chown system log /proc/last_kmsg + chmod 0440 /proc/last_kmsg + + # make the selinux kernel policy world-readable + chmod 0444 /sys/fs/selinux/policy + + # create the lost+found directories, so as to enforce our permissions + mkdir /cache/lost+found 0770 root root + + restorecon_recursive /metadata + mkdir /metadata/vold + chmod 0700 /metadata/vold + mkdir /metadata/password_slots 0771 root system + mkdir /metadata/bootstat 0750 system log + mkdir /metadata/ota 0750 root system + mkdir /metadata/ota/snapshots 0750 root system + mkdir /metadata/userspacereboot 0770 root system + mkdir /metadata/watchdog 0770 root system + + mkdir /metadata/apex 0700 root system + mkdir /metadata/apex/sessions 0700 root system + # On some devices we see a weird behaviour in which /metadata/apex doesn't + # have a correct label. To workaround this bug, explicitly call restorecon + # on /metadata/apex. For most of the boot sequences /metadata/apex will + # already have a correct selinux label, meaning that this call will be a + # no-op. + restorecon_recursive /metadata/apex + + mkdir /metadata/staged-install 0770 root system + + mkdir /metadata/aconfig 0775 root system + mkdir /metadata/aconfig/flags 0770 root system + mkdir /metadata/aconfig/boot 0775 root system + +on late-fs + # Ensure that tracefs has the correct permissions. + # This does not work correctly if it is called in post-fs. + chmod 0755 /sys/kernel/tracing + chmod 0755 /sys/kernel/debug/tracing + + # HALs required before storage encryption can get unlocked (FBE) + class_start early_hal + + # Load trusted keys from dm-verity protected partitions + exec -- /system/bin/fsverity_init --load-verified-keys + +# Only enable the bootreceiver tracing instance for kernels 5.10 and above. +on late-fs && property:ro.kernel.version=4.9 + setprop bootreceiver.enable 0 +on late-fs && property:ro.kernel.version=4.14 + setprop bootreceiver.enable 0 +on late-fs && property:ro.kernel.version=4.19 + setprop bootreceiver.enable 0 +on late-fs && property:ro.kernel.version=5.4 + setprop bootreceiver.enable 0 +on late-fs + # Bootreceiver tracing instance is enabled by default. + setprop bootreceiver.enable ${bootreceiver.enable:-1} + +on property:ro.product.cpu.abilist64=* && property:bootreceiver.enable=1 + # Set up a tracing instance for system_server to monitor error_report_end events. + # These are sent by kernel tools like KASAN and KFENCE when a memory corruption + # is detected. This is only needed for 64-bit systems. + mkdir /sys/kernel/tracing/instances/bootreceiver 0700 system system + restorecon_recursive /sys/kernel/tracing/instances/bootreceiver + write /sys/kernel/tracing/instances/bootreceiver/buffer_size_kb 1 + write /sys/kernel/tracing/instances/bootreceiver/trace_options disable_on_free + write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1 + +on post-fs-data + + mark_post_data + + # Start checkpoint before we touch data + exec - system system -- /system/bin/vdc checkpoint prepareCheckpoint + + # We chown/chmod /data again so because mount is run as root + defaults + chown system system /data + chmod 0771 /data + # We restorecon /data in case the userdata partition has been reset. + restorecon /data + + # Make sure we have the device encryption key. + installkey /data + + # Start bootcharting as soon as possible after the data partition is + # mounted to collect more data. + mkdir /data/bootchart 0755 shell shell encryption=Require + bootchart start + + # Avoid predictable entropy pool. Carry over entropy from previous boot. + copy /data/system/entropy.dat /dev/urandom + + mkdir /data/vendor 0771 root root encryption=Require + mkdir /data/vendor/hardware 0771 root root + + # Start tombstoned early to be able to store tombstones. + mkdir /data/anr 0775 system system encryption=Require + mkdir /data/tombstones 0775 system system encryption=Require + mkdir /data/vendor/tombstones 0771 root root + mkdir /data/vendor/tombstones/wifi 0771 wifi wifi + start tombstoned + + # Make sure that apexd is started in the default namespace + enter_default_mount_ns + + # set up keystore directory structure first so that we can end early boot + # and start apexd + mkdir /data/misc 01771 system misc encryption=Require + mkdir /data/misc/keystore 0700 keystore keystore + # work around b/183668221 + restorecon /data/misc /data/misc/keystore + + # Boot level 30 + # odsign signing keys have MAX_BOOT_LEVEL=30 + # This is currently the earliest boot level, but we start at 30 + # to leave room for earlier levels. + setprop keystore.boot_level 30 + + # Now that /data is mounted and we have created /data/misc/keystore, + # we can tell keystore to stop allowing use of early-boot keys, + # and access its database for the first time to support creation and + # use of MAX_BOOT_LEVEL keys. + exec - system system -- /system/bin/vdc keymaster earlyBootEnded + + # Multi-installed APEXes are selected using persist props. + # Load persist properties and override properties (if enabled) from /data, + # before starting apexd. + # /data/property should be created before `load_persist_props` + mkdir /data/property 0700 root root encryption=Require + load_persist_props + + start logd + start logd-reinit + + # Some existing vendor rc files use 'on load_persist_props_action' to know + # when persist props are ready. These are difficult to change due to GRF, + # so continue triggering this action here even though props are already loaded + # by the 'load_persist_props' call above. + trigger load_persist_props_action + + # /data/apex is now available. Start apexd to scan and activate APEXes. + # + # To handle userspace reboots, make sure that apexd is started cleanly here + # (set apexd.status="") and that it is restarted if it's already running. + # + # /data/apex uses encryption=None because direct I/O support is needed on + # APEX files, but some devices don't support direct I/O on encrypted files. + # Also, APEXes are public information, similar to the system image. + # /data/apex/decompressed and /data/apex/ota_reserved override this setting; + # they are encrypted so that files in them can be hard-linked into + # /data/rollback which is encrypted. + mkdir /data/apex 0755 root system encryption=None + mkdir /data/apex/active 0755 root system + mkdir /data/apex/backup 0700 root system + mkdir /data/apex/decompressed 0755 root system encryption=Require + mkdir /data/apex/hashtree 0700 root system + mkdir /data/apex/sessions 0700 root system + mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary + mkdir /data/apex/ota_reserved 0700 root system encryption=Require + setprop apexd.status "" + restart apexd + + # create rest of basic filesystem structure + mkdir /data/misc/recovery 0770 system log + copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1 + chmod 0440 /data/misc/recovery/ro.build.fingerprint.1 + chown system log /data/misc/recovery/ro.build.fingerprint.1 + write /data/misc/recovery/ro.build.fingerprint ${ro.build.fingerprint} + chmod 0440 /data/misc/recovery/ro.build.fingerprint + chown system log /data/misc/recovery/ro.build.fingerprint + mkdir /data/misc/recovery/proc 0770 system log + copy /data/misc/recovery/proc/version /data/misc/recovery/proc/version.1 + chmod 0440 /data/misc/recovery/proc/version.1 + chown system log /data/misc/recovery/proc/version.1 + copy /proc/version /data/misc/recovery/proc/version + chmod 0440 /data/misc/recovery/proc/version + chown system log /data/misc/recovery/proc/version + mkdir /data/misc/bluedroid 02770 bluetooth bluetooth + # Fix the access permissions and group ownership for 'bt_config.conf' + chmod 0660 /data/misc/bluedroid/bt_config.conf + chown bluetooth bluetooth /data/misc/bluedroid/bt_config.conf + mkdir /data/misc/bluetooth 0770 bluetooth bluetooth + mkdir /data/misc/bluetooth/logs 0770 bluetooth bluetooth + mkdir /data/misc/nfc 0770 nfc nfc + mkdir /data/misc/nfc/logs 0770 nfc nfc + mkdir /data/misc/credstore 0700 credstore credstore + mkdir /data/misc/gatekeeper 0700 system system + mkdir /data/misc/keychain 0771 system system + mkdir /data/misc/net 0750 root shell + mkdir /data/misc/radio 0770 system radio + mkdir /data/misc/sms 0770 system radio + mkdir /data/misc/carrierid 0770 system radio + mkdir /data/misc/apns 0770 system radio + mkdir /data/misc/emergencynumberdb 0770 system radio + mkdir /data/misc/network_watchlist 0774 system system + mkdir /data/misc/telephonyconfig 0770 system radio + mkdir /data/misc/textclassifier 0771 system system + mkdir /data/misc/vpn 0770 system vpn + mkdir /data/misc/shared_relro 0771 shared_relro shared_relro + mkdir /data/misc/systemkeys 0700 system system + mkdir /data/misc/wifi 0770 wifi wifi + mkdir /data/misc/wifi/sockets 0770 wifi wifi + mkdir /data/misc/wifi/wpa_supplicant 0770 wifi wifi + mkdir /data/misc/ethernet 0770 system system + mkdir /data/misc/dhcp 0770 dhcp dhcp + mkdir /data/misc/user 0771 root root + # give system access to wpa_supplicant.conf for backup and restore + chmod 0660 /data/misc/wifi/wpa_supplicant.conf + mkdir /data/local 0751 root root encryption=Require + mkdir /data/misc/media 0700 media media + mkdir /data/misc/audioserver 0700 audioserver audioserver + mkdir /data/misc/cameraserver 0700 cameraserver cameraserver + mkdir /data/misc/vold 0700 root root + mkdir /data/misc/boottrace 0771 system shell + mkdir /data/misc/update_engine 0700 root root + mkdir /data/misc/update_engine_log 02750 root log + mkdir /data/misc/trace 0700 root root + # create location to store surface and window trace files + mkdir /data/misc/wmtrace 0700 system system + # create location to store accessibility trace files + mkdir /data/misc/a11ytrace 0700 system system + # profile file layout + mkdir /data/misc/profiles 0771 system system + mkdir /data/misc/profiles/cur 0771 system system + mkdir /data/misc/profiles/ref 0771 system system + mkdir /data/misc/profman 0770 system shell + mkdir /data/misc/gcov 0770 root root + mkdir /data/misc/installd 0700 root root + mkdir /data/misc/apexdata 0711 root root + mkdir /data/misc/apexrollback 0700 root root + mkdir /data/misc/appcompat/ 0700 system system + mkdir /data/misc/uprobestats-configs/ 0777 uprobestats uprobestats + mkdir /data/misc/snapshotctl_log 0755 root root + # create location to store pre-reboot information + mkdir /data/misc/prereboot 0700 system system + # directory used for on-device refresh metrics file. + mkdir /data/misc/odrefresh 0777 system system + # directory used for on-device signing key blob + mkdir /data/misc/odsign 0710 root system + # directory used for odsign metrics + mkdir /data/misc/odsign/metrics 0770 root system + + # Directory for VirtualizationService temporary image files. + # Delete any stale files owned by the old virtualizationservice uid (b/230056726). + chmod 0770 /data/misc/virtualizationservice + exec - virtualizationservice system -- /bin/rm -rf /data/misc/virtualizationservice + mkdir /data/misc/virtualizationservice 0771 system system + + # /data/preloads uses encryption=None because it only contains preloaded + # files that are public information, similar to the system image. + mkdir /data/preloads 0775 system system encryption=None + + # For security reasons, /data/local/tmp should always be empty. + # Do not place files or directories in /data/local/tmp + mkdir /data/local/tmp 0771 shell shell + mkdir /data/local/traces 0777 shell shell + mkdir /data/app-private 0771 system system encryption=Require + mkdir /data/app-ephemeral 0771 system system encryption=Require + mkdir /data/app-asec 0700 root root encryption=Require + mkdir /data/app-lib 0771 system system encryption=Require + mkdir /data/app 0771 system system encryption=Require + + # create directory for updated font files. + mkdir /data/fonts/ 0771 root root encryption=Require + mkdir /data/fonts/files 0771 system system + mkdir /data/fonts/config 0770 system system + + # Create directories to push tests to for each linker namespace. + # Create the subdirectories in case the first test is run as root + # so it doesn't end up owned by root. + # Set directories to be executable by any process so that debuggerd, + # aka crash_dump, can read any executables/shared libraries. + mkdir /data/local/tests 0701 shell shell + mkdir /data/local/tests/product 0701 shell shell + mkdir /data/local/tests/system 0701 shell shell + mkdir /data/local/tests/unrestricted 0701 shell shell + mkdir /data/local/tests/vendor 0701 shell shell + + # create dalvik-cache, so as to enforce our permissions + mkdir /data/dalvik-cache 0771 root root encryption=Require + # create the A/B OTA directory, so as to enforce our permissions + mkdir /data/ota 0771 root root encryption=Require + + # create the OTA package directory. It will be accessed by GmsCore (cache + # group), update_engine and update_verifier. + mkdir /data/ota_package 0770 system cache encryption=Require + + # create resource-cache and double-check the perms + mkdir /data/resource-cache 0771 system system encryption=Require + chown system system /data/resource-cache + chmod 0771 /data/resource-cache + + # Ensure that lost+found exists and has the correct permissions. Linux + # filesystems expect this directory to exist; it's where the fsck tool puts + # any recovered files that weren't present in any directory. It must be + # unencrypted, as fsck must be able to write to it. + mkdir /data/lost+found 0770 root root encryption=None + + # create directory for DRM plug-ins - give drm the read/write access to + # the following directory. + mkdir /data/drm 0770 drm drm encryption=Require + + # create directory for MediaDrm plug-ins - give drm the read/write access to + # the following directory. + mkdir /data/mediadrm 0770 mediadrm mediadrm encryption=Require + + # NFC: create data/nfc for nv storage + mkdir /data/nfc 0770 nfc nfc encryption=Require + mkdir /data/nfc/param 0770 nfc nfc + + # Create all remaining /data root dirs so that they are made through init + # and get proper encryption policy installed + mkdir /data/backup 0700 system system encryption=Require + mkdir /data/ss 0700 system system encryption=Require + + mkdir /data/system 0775 system system encryption=Require + mkdir /data/system/environ 0700 system system + # b/183861600 attempt to fix selinux label before running derive_classpath service + restorecon /data/system/environ + mkdir /data/system/dropbox 0700 system system + mkdir /data/system/heapdump 0700 system system + mkdir /data/system/users 0775 system system + # Mkdir and set SELinux security contexts for shutdown-checkpoints. + # TODO(b/270286197): remove these after couple releases. + mkdir /data/system/shutdown-checkpoints 0700 system system + restorecon_recursive /data/system/shutdown-checkpoints + + # Create the parent directories of the user CE and DE storage directories. + # These parent directories must use encryption=None, since each of their + # subdirectories uses a different encryption policy (a per-user one), and + # encryption policies apply recursively. These directories should never + # contain any subdirectories other than the per-user ones. /data/media/obb + # is an exception that exists for legacy reasons. + # + # Don't use any write mode bits (0222) for any of these directories, since + # the only process that should write to them directly is vold (since it + # needs to set up file-based encryption on the subdirectories), which runs + # as root with CAP_DAC_OVERRIDE. This is also fully enforced via the + # SELinux policy. But we also set the DAC file modes accordingly, to try to + # minimize differences in behavior if SELinux is set to permissive mode. + mkdir /data/media 0550 media_rw media_rw encryption=None + mkdir /data/misc_ce 0551 system misc encryption=None + mkdir /data/misc_de 0551 system misc encryption=None + mkdir /data/system_ce 0550 system system encryption=None + mkdir /data/system_de 0550 system system encryption=None + mkdir /data/user 0511 system system encryption=None + mkdir /data/user_de 0511 system system encryption=None + mkdir /data/vendor_ce 0551 root root encryption=None + mkdir /data/vendor_de 0551 root root encryption=None + + # Set the casefold flag on /data/media. For upgrades, a restorecon can be + # needed first to relabel the directory from media_rw_data_file. + restorecon /data/media + exec - media_rw media_rw -- /system/bin/chattr +F /data/media + + # A tmpfs directory, which will contain all apps and sdk sandbox CE and DE + # data directory that bind mount from the original source. + mount tmpfs tmpfs /data_mirror nodev noexec nosuid mode=0700,uid=0,gid=1000 + restorecon /data_mirror + mkdir /data_mirror/data_ce 0700 root root + mkdir /data_mirror/data_de 0700 root root + mkdir /data_mirror/misc_ce 0700 root root + mkdir /data_mirror/misc_de 0700 root root + + # Create CE and DE data directory for default volume + mkdir /data_mirror/data_ce/null 0700 root root + mkdir /data_mirror/data_de/null 0700 root root + mkdir /data_mirror/misc_ce/null 0700 root root + mkdir /data_mirror/misc_de/null 0700 root root + + # Bind mount CE and DE data directory to mirror's default volume directory. + # Note that because the /data mount has the "shared" propagation type, the + # later bind mount of /data/data onto /data/user/0 will automatically + # propagate to /data_mirror/data_ce/null/0 as well. + mount none /data/user /data_mirror/data_ce/null bind rec + mount none /data/user_de /data_mirror/data_de/null bind rec + mount none /data/misc_ce /data_mirror/misc_ce/null bind rec + mount none /data/misc_de /data_mirror/misc_de/null bind rec + + # Create mirror directory for jit profiles + mkdir /data_mirror/cur_profiles 0700 root root + mount none /data/misc/profiles/cur /data_mirror/cur_profiles bind rec + mkdir /data_mirror/ref_profiles 0700 root root + mount none /data/misc/profiles/ref /data_mirror/ref_profiles bind rec + + mkdir /data/cache 0770 system cache encryption=Require + mkdir /data/cache/recovery 0770 system cache + mkdir /data/cache/backup_stage 0700 system system + mkdir /data/cache/backup 0700 system system + + # Delete these if need be, per b/139193659 + mkdir /data/rollback 0700 system system encryption=DeleteIfNecessary + mkdir /data/rollback-observer 0700 system system encryption=DeleteIfNecessary + mkdir /data/rollback-history 0700 system system encryption=DeleteIfNecessary + + # Create root dir for Incremental Service + mkdir /data/incremental 0771 system system encryption=Require + + # Create directories for statsd + mkdir /data/misc/stats-active-metric/ 0770 statsd system + mkdir /data/misc/stats-data/ 0770 statsd system + mkdir /data/misc/stats-data/restricted-data 0770 statsd system + mkdir /data/misc/stats-metadata/ 0770 statsd system + mkdir /data/misc/stats-service/ 0770 statsd system + mkdir /data/misc/train-info/ 0770 statsd system + + # Wait for apexd to finish activating APEXes before starting more processes. + wait_for_prop apexd.status activated + perform_apex_config + + # Create directories for boot animation. + mkdir /data/misc/bootanim 0755 system system encryption=DeleteIfNecessary + + exec_start derive_sdk + + init_user0 + + # Set SELinux security contexts on upgrade or policy update. + restorecon --recursive --skip-ce /data + + # Define and export *CLASSPATH variables + # Must start before 'odsign', as odsign depends on *CLASSPATH variables + exec_start derive_classpath + load_exports /data/system/environ/classpath + + # Start ART's oneshot boot service to propagate boot experiment flags to + # dalvik.vm.*. This needs to be done before odsign since odrefresh uses and + # validates those properties against the signed cache-info.xml. + exec_start art_boot + + # Start the on-device signing daemon, and wait for it to finish, to ensure + # ART artifacts are generated if needed. + # Must start after 'derive_classpath' to have *CLASSPATH variables set. + start odsign + + # Wait for odsign to be done with the key. + wait_for_prop odsign.key.done 1 + + # Bump the boot level to 1000000000; this prevents further on-device signing. + # This is a special value that shuts down the thread which listens for + # further updates. + setprop keystore.boot_level 1000000000 + + # Allow apexd to snapshot and restore device encrypted apex data in the case + # of a rollback. This should be done immediately after DE_user data keys + # are loaded. APEXes should not access this data until this has been + # completed and apexd.status becomes "ready". + exec_start apexd-snapshotde + + # sys.memfd_use set to false by default, which keeps it disabled + # until it is confirmed that apps and vendor processes don't make + # IOCTLs on ashmem fds any more. + setprop sys.use_memfd false + + # Set fscklog permission + chown root system /dev/fscklogs/log + chmod 0770 /dev/fscklogs/log + + # Enable FUSE by default + setprop persist.sys.fuse true + + # Update dm-verity state and set partition.*.verified properties. + verity_update_state + +# It is recommended to put unnecessary data/ initialization from post-fs-data +# to start-zygote in device's init.rc to unblock zygote start. +on zygote-start + wait_for_prop odsign.verification.done 1 + # A/B update verifier that marks a successful boot. + exec_start update_verifier + start statsd + start netd + start zygote + start zygote_secondary + +on boot && property:ro.config.low_ram=true + # Tweak background writeout + write /proc/sys/vm/dirty_expire_centisecs 200 + write /proc/sys/vm/dirty_background_ratio 5 + +on boot && property:suspend.disable_sync_on_suspend=true + write /sys/power/sync_on_suspend 0 + +on boot + # basic network init + ifup lo + hostname localhost + domainname localdomain + + # IPsec SA default expiration length + write /proc/sys/net/core/xfrm_acq_expires 3600 + + # Memory management. Basic kernel parameters, and allow the high + # level system server to be able to adjust the kernel OOM driver + # parameters to match how it is managing things. + write /proc/sys/vm/overcommit_memory 1 + write /proc/sys/vm/min_free_order_shift 4 + + # System server manages zram writeback + chown root system /sys/block/zram0/idle + chmod 0664 /sys/block/zram0/idle + chown root system /sys/block/zram0/writeback + chmod 0664 /sys/block/zram0/writeback + + # to access F2FS sysfs on dm- directly + mkdir /dev/sys/fs/by-name 0755 system system + symlink /sys/fs/f2fs/${dev.mnt.dev.data} /dev/sys/fs/by-name/userdata + + # dev.mnt.dev.data=dm-N, dev.mnt.blk.data=sdaN/mmcblk0pN, dev.mnt.rootdisk.data=sda/mmcblk0, or + # dev.mnt.dev.data=sdaN/mmcblk0pN, dev.mnt.blk.data=sdaN/mmcblk0pN, dev.mnt.rootdisk.data=sda/mmcblk0 + mkdir /dev/sys/block/by-name 0755 system system + symlink /sys/class/block/${dev.mnt.dev.data} /dev/sys/block/by-name/userdata + symlink /sys/class/block/${dev.mnt.rootdisk.data} /dev/sys/block/by-name/rootdisk + + # F2FS tuning. Set cp_interval larger than dirty_expire_centisecs, 30 secs, + # to avoid power consumption when system becomes mostly idle. Be careful + # to make it too large, since it may bring userdata loss, if they + # are not aware of using fsync()/sync() to prepare sudden power-cut. + write /dev/sys/fs/by-name/userdata/cp_interval 200 + write /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time 50 + write /dev/sys/fs/by-name/userdata/iostat_period_ms 1000 + write /dev/sys/fs/by-name/userdata/iostat_enable 1 + + # set readahead multiplier for POSIX_FADV_SEQUENTIAL files + write /dev/sys/fs/by-name/userdata/seq_file_ra_mul 128 + + # limit discard size to 128MB in order to avoid long IO latency + # for filesystem tuning first (dm or sda) + # this requires enabling selinux entry for sda/mmcblk0 in vendor side + write /dev/sys/block/by-name/userdata/queue/discard_max_bytes 134217728 + write /dev/sys/block/by-name/rootdisk/queue/discard_max_bytes 134217728 + + # Permissions for System Server and daemons. + chown system system /sys/power/autosleep + + chown system system /sys/devices/system/cpu/cpufreq/interactive/timer_rate + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/timer_rate + chown system system /sys/devices/system/cpu/cpufreq/interactive/timer_slack + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/timer_slack + chown system system /sys/devices/system/cpu/cpufreq/interactive/min_sample_time + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/min_sample_time + chown system system /sys/devices/system/cpu/cpufreq/interactive/hispeed_freq + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/hispeed_freq + chown system system /sys/devices/system/cpu/cpufreq/interactive/target_loads + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/target_loads + chown system system /sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load + chown system system /sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay + chown system system /sys/devices/system/cpu/cpufreq/interactive/boost + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/boost + chown system system /sys/devices/system/cpu/cpufreq/interactive/boostpulse + chown system system /sys/devices/system/cpu/cpufreq/interactive/input_boost + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/input_boost + chown system system /sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration + chown system system /sys/devices/system/cpu/cpufreq/interactive/io_is_busy + chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/io_is_busy + + chown system system /sys/class/leds/vibrator/trigger + chown system system /sys/class/leds/vibrator/activate + chown system system /sys/class/leds/vibrator/brightness + chown system system /sys/class/leds/vibrator/duration + chown system system /sys/class/leds/vibrator/state + chown system system /sys/class/timed_output/vibrator/enable + chown system system /sys/class/leds/keyboard-backlight/brightness + chown system system /sys/class/leds/lcd-backlight/brightness + chown system system /sys/class/leds/button-backlight/brightness + chown system system /sys/class/leds/jogball-backlight/brightness + chown system system /sys/class/leds/red/brightness + chown system system /sys/class/leds/green/brightness + chown system system /sys/class/leds/blue/brightness + chown system system /sys/class/leds/red/device/grpfreq + chown system system /sys/class/leds/red/device/grppwm + chown system system /sys/class/leds/red/device/blink + chown system system /sys/module/sco/parameters/disable_esco + chown system system /sys/kernel/ipv4/tcp_wmem_min + chown system system /sys/kernel/ipv4/tcp_wmem_def + chown system system /sys/kernel/ipv4/tcp_wmem_max + chown system system /sys/kernel/ipv4/tcp_rmem_min + chown system system /sys/kernel/ipv4/tcp_rmem_def + chown system system /sys/kernel/ipv4/tcp_rmem_max + chown root radio /proc/cmdline + chown root system /proc/bootconfig + + # Define default initial receive window size in segments. + setprop net.tcp_def_init_rwnd 60 + + # Start standard binderized HAL daemons + class_start hal + + class_start core + +# on nonencrypted + class_start main + class_start late_start + +on property:sys.init_log_level=* + loglevel ${sys.init_log_level} + +on charger + class_start charger + +on property:sys.boot_completed=1 + bootchart stop + # Setup per_boot directory so other .rc could start to use it on boot_completed + exec - system system -- /bin/rm -rf /data/per_boot + mkdir /data/per_boot 0700 system system encryption=Require key=per_boot_ref + +# system server cannot write to /proc/sys files, +# and chown/chmod does not work for /proc/sys/ entries. +# So proxy writes through init. +on property:sys.sysctl.extra_free_kbytes=* + exec -- /system/bin/extra_free_kbytes.sh ${sys.sysctl.extra_free_kbytes} + +# Allow users to drop caches +on property:perf.drop_caches=3 + write /proc/sys/vm/drop_caches 3 + setprop perf.drop_caches 0 + +# "tcp_default_init_rwnd" Is too long! +on property:net.tcp_def_init_rwnd=* + write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd} + +# perf_event_open syscall security: +# Newer kernels have the ability to control the use of the syscall via SELinux +# hooks. init tests for this, and sets sys_init.perf_lsm_hooks to 1 if the +# kernel has the hooks. In this case, the system-wide perf_event_paranoid +# sysctl is set to -1 (unrestricted use), and the SELinux policy is used for +# controlling access. On older kernels, the paranoid value is the only means of +# controlling access. It is normally 3 (allow only root), but the shell user +# can lower it to 1 (allowing thread-scoped pofiling) via security.perf_harden. +on load_bpf_programs && property:sys.init.perf_lsm_hooks=1 + write /proc/sys/kernel/perf_event_paranoid -1 +on property:security.perf_harden=0 && property:sys.init.perf_lsm_hooks="" + write /proc/sys/kernel/perf_event_paranoid 1 +on property:security.perf_harden=1 && property:sys.init.perf_lsm_hooks="" + write /proc/sys/kernel/perf_event_paranoid 3 + +# Additionally, simpleperf profiler uses debug.* and security.perf_harden +# sysprops to be able to indirectly set these sysctls. +on property:security.perf_harden=0 + write /proc/sys/kernel/perf_event_max_sample_rate ${debug.perf_event_max_sample_rate:-100000} + write /proc/sys/kernel/perf_cpu_time_max_percent ${debug.perf_cpu_time_max_percent:-25} + write /proc/sys/kernel/perf_event_mlock_kb ${debug.perf_event_mlock_kb:-516} +# Default values. +on property:security.perf_harden=1 + write /proc/sys/kernel/perf_event_max_sample_rate 100000 + write /proc/sys/kernel/perf_cpu_time_max_percent 25 + write /proc/sys/kernel/perf_event_mlock_kb 516 + +# This property can be set only on userdebug/eng. See neverallow rule in +# /system/sepolicy/private/property.te . +on property:security.lower_kptr_restrict=1 + write /proc/sys/kernel/kptr_restrict 0 + +on property:security.lower_kptr_restrict=0 + write /proc/sys/kernel/kptr_restrict 2 + + +# on shutdown +# In device's init.rc, this trigger can be used to do device-specific actions +# before shutdown. e.g disable watchdog and mask error handling + +## Daemon processes to be run by init. +## +service ueventd /system/bin/ueventd + class core + critical + seclabel u:r:ueventd:s0 + user root + shutdown critical + +service console /system/bin/sh + class core + console + disabled + user shell + group shell log readproc + seclabel u:r:shell:s0 + setenv HOSTNAME console + shutdown critical + +on property:ro.debuggable=1 + # Give writes to the same group for the trace folder on debug builds, + # it's further protected by selinux policy. + # The folder is used to store method traces. + chmod 0773 /data/misc/trace + # Give writes and reads to anyone for the window trace folder on debug builds, + # it's further protected by selinux policy. + chmod 0777 /data/misc/wmtrace + # Give reads to anyone for the accessibility trace folder on debug builds. + chmod 0775 /data/misc/a11ytrace + +# on init && property:ro.debuggable=1 +# start console + +on userspace-reboot-requested + # TODO(b/135984674): reset all necessary properties here. + setprop sys.boot_completed "" + setprop dev.bootcomplete "" + setprop sys.init.updatable_crashing "" + setprop sys.init.updatable_crashing_process_name "" + setprop sys.user.0.ce_available "" + setprop sys.shutdown.requested "" + setprop service.bootanim.exit "" + setprop service.bootanim.progress "" + +on userspace-reboot-fs-remount + # Make sure that vold is running. + # This is mostly a precaution measure in case vold for some reason wasn't running when + # userspace reboot was initiated. + start vold + exec - system system -- /system/bin/vdc checkpoint resetCheckpoint + exec - system system -- /system/bin/vdc checkpoint markBootAttempt + # Unmount /data_mirror mounts in the reverse order of corresponding mounts. + umount /data_mirror/data_ce/null/0 + umount /data_mirror/data_ce/null + umount /data_mirror/data_de/null + umount /data_mirror/cur_profiles + umount /data_mirror/ref_profiles + umount /data_mirror + remount_userdata + start bootanim + +on userspace-reboot-resume + trigger userspace-reboot-fs-remount + trigger post-fs-data + trigger zygote-start + trigger early-boot + trigger boot + +on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1 + setprop sys.init.userspace_reboot.in_progress "" + +# Multi-Gen LRU Experiment +on property:persist.device_config.mglru_native.lru_gen_config=none + write /sys/kernel/mm/lru_gen/enabled 0 +on property:persist.device_config.mglru_native.lru_gen_config=core + write /sys/kernel/mm/lru_gen/enabled 1 +on property:persist.device_config.mglru_native.lru_gen_config=core_and_mm_walk + write /sys/kernel/mm/lru_gen/enabled 3 +on property:persist.device_config.mglru_native.lru_gen_config=core_and_nonleaf_young + write /sys/kernel/mm/lru_gen/enabled 5 +on property:persist.device_config.mglru_native.lru_gen_config=all + write /sys/kernel/mm/lru_gen/enabled 7 diff --git a/aosp/system/libbase/include/android-base/macros.h b/aosp/system/libbase/include/android-base/macros.h new file mode 100644 index 000000000..1d6072ae1 --- /dev/null +++ b/aosp/system/libbase/include/android-base/macros.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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. + */ + +#pragma once + +#include // for size_t +#include // for TEMP_FAILURE_RETRY + +#include + +#ifndef aosp_hack +#define aosp_hack() if (true) return +#endif +#ifndef aosp_hack_p +#define aosp_hack_p(p) if (true) return p +#endif + +// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) \ + ({ \ + decltype(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This must be placed in the private: declarations for a class. +// +// For disallowing only assign or copy, delete the relevant operator or +// constructor, for example: +// void operator=(const TypeName&) = delete; +// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken +// semantically, one should either use disallow both or neither. Try to +// avoid these in new code. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char(&ArraySizeHelper(T(&array)[N]))[N]; // NOLINT(readability/casting) + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +#define SIZEOF_MEMBER(t, f) sizeof(std::declval().f) + +// Changing this definition will cause you a lot of pain. A majority of +// vendor code defines LIKELY and UNLIKELY this way, and includes +// this header through an indirect path. +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +#define WARN_UNUSED __attribute__((warn_unused_result)) + +// A deprecated function to call to create a false use of the parameter, for +// example: +// int foo(int x) { UNUSED(x); return 10; } +// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED. +template +void UNUSED(const T&...) { +} + +// An attribute to place on a parameter to a function, for example: +// int foo(int x ATTRIBUTE_UNUSED) { return 10; } +// to avoid compiler warnings. +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) + +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels: +// switch (x) { +// case 40: +// case 41: +// if (truth_is_out_there) { +// ++x; +// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in +// // comments. +// } else { +// return x; +// } +// case 42: +// ... +// +// As shown in the example above, the FALLTHROUGH_INTENDED macro should be +// followed by a semicolon. It is designed to mimic control-flow statements +// like 'break;', so it can be placed in most places where 'break;' can, but +// only if there are no statements on the execution path between it and the +// next switch label. +// +// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to +// [[clang::fallthrough]] attribute, which is analysed when performing switch +// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang +// documentation on language extensions for details: +// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough +// +// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no +// effect on diagnostics. +// +// In either case this macro has no effect on runtime behavior and performance +// of code. +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED [[fallthrough]] // NOLINT +#endif + +// Current ABI string +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#define ABI_STRING "arm64" +#elif defined(__i386__) +#define ABI_STRING "x86" +#elif defined(__riscv) +#define ABI_STRING "riscv64" +#elif defined(__x86_64__) +#define ABI_STRING "x86_64" +#endif diff --git a/aosp/system/libhwbinder/Binder.cpp b/aosp/system/libhwbinder/Binder.cpp new file mode 100644 index 000000000..1a433acd6 --- /dev/null +++ b/aosp/system/libhwbinder/Binder.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2005 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace android { +namespace hardware { + +// --------------------------------------------------------------------------- + +IBinder::IBinder() + : RefBase() +{ +} + +IBinder::~IBinder() +{ +} + +// --------------------------------------------------------------------------- + +BHwBinder* IBinder::localBinder() +{ + return nullptr; +} + +BpHwBinder* IBinder::remoteBinder() +{ + return nullptr; +} + +bool IBinder::checkSubclass(const void* /*subclassID*/) const +{ + return false; +} + +// --------------------------------------------------------------------------- + +class BHwBinder::Extras +{ +public: + // unlocked objects + bool mRequestingSid = false; + + // for below objects + Mutex mLock; + BpHwBinder::ObjectManager mObjects; +}; + +// --------------------------------------------------------------------------- + +BHwBinder::BHwBinder() : mSchedPolicy(SCHED_NORMAL), mSchedPriority(0), mExtras(nullptr) +{ +} + +int BHwBinder::getMinSchedulingPolicy() { + return mSchedPolicy; +} + +int BHwBinder::getMinSchedulingPriority() { + return mSchedPriority; +} + +bool BHwBinder::isRequestingSid() { + Extras* e = mExtras.load(std::memory_order_acquire); + + return e && e->mRequestingSid; +} + +void BHwBinder::setRequestingSid(bool requestingSid) { + requestingSid = false; + Extras* e = mExtras.load(std::memory_order_acquire); + + if (!e) { + // default is false. Most things don't need sids, so avoiding allocations when possible. + if (!requestingSid) { + return; + } + + e = getOrCreateExtras(); + if (!e) return; // out of memory + } + + e->mRequestingSid = requestingSid; +} + +status_t BHwBinder::transact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags, TransactCallback callback) +{ + data.setDataPosition(0); + + if (reply != nullptr && (flags & FLAG_CLEAR_BUF)) { + reply->markSensitive(); + } + + // extra comment to try to force running all tests + if (UNLIKELY(code == HIDL_DEBUG_TRANSACTION)) { + uid_t uid = IPCThreadState::self()->getCallingUid(); + if (multiuser_get_app_id(uid) >= AID_APP_START) { + ALOGE("Can not call IBase::debug from apps"); + return PERMISSION_DENIED; + } + } + + return onTransact(code, data, reply, flags, [&](auto& replyParcel) { + replyParcel.setDataPosition(0); + if (callback != nullptr) { + callback(replyParcel); + } + }); +} + +status_t BHwBinder::linkToDeath( + const sp& /*recipient*/, void* /*cookie*/, + uint32_t /*flags*/) +{ + return INVALID_OPERATION; +} + +status_t BHwBinder::unlinkToDeath( + const wp& /*recipient*/, void* /*cookie*/, + uint32_t /*flags*/, wp* /*outRecipient*/) +{ + return INVALID_OPERATION; +} + +void BHwBinder::attachObject( + const void* objectID, void* object, void* cleanupCookie, + object_cleanup_func func) +{ + Extras* e = getOrCreateExtras(); + if (!e) return; // out of memory + + AutoMutex _l(e->mLock); + e->mObjects.attach(objectID, object, cleanupCookie, func); +} + +void* BHwBinder::findObject(const void* objectID) const +{ + Extras* e = mExtras.load(std::memory_order_acquire); + if (!e) return nullptr; + + AutoMutex _l(e->mLock); + return e->mObjects.find(objectID); +} + +void BHwBinder::detachObject(const void* objectID) +{ + Extras* e = mExtras.load(std::memory_order_acquire); + if (!e) return; + + AutoMutex _l(e->mLock); + e->mObjects.detach(objectID); +} + +BHwBinder* BHwBinder::localBinder() +{ + return this; +} + +BHwBinder::~BHwBinder() +{ + Extras* e = mExtras.load(std::memory_order_relaxed); + if (e) delete e; +} + + +status_t BHwBinder::onTransact( + uint32_t /*code*/, const Parcel& /*data*/, Parcel* /*reply*/, uint32_t /*flags*/, + TransactCallback /*callback*/) +{ + return UNKNOWN_TRANSACTION; +} + +BHwBinder::Extras* BHwBinder::getOrCreateExtras() +{ + Extras* e = mExtras.load(std::memory_order_acquire); + + if (!e) { + e = new Extras; + Extras* expected = nullptr; + if (!mExtras.compare_exchange_strong(expected, e, + std::memory_order_release, + std::memory_order_acquire)) { + delete e; + e = expected; // Filled in by CAS + } + if (e == nullptr) return nullptr; // out of memory + } + + return e; +} + +// --------------------------------------------------------------------------- + +enum { + // This is used to transfer ownership of the remote binder from + // the BpHwRefBase object holding it (when it is constructed), to the + // owner of the BpHwRefBase object when it first acquires that BpHwRefBase. + kRemoteAcquired = 0x00000001 +}; + +BpHwRefBase::BpHwRefBase(const sp& o) + : mRemote(o.get()), mRefs(nullptr), mState(0) +{ + if (mRemote) { + mRemote->incStrong(this); // Removed on first IncStrong(). + } +} + +BpHwRefBase::~BpHwRefBase() +{ + if (mRemote) { + if (!(mState.load(std::memory_order_relaxed)&kRemoteAcquired)) { + mRemote->decStrong(this); + } + } +} + +void BpHwRefBase::onFirstRef() +{ + mState.fetch_or(kRemoteAcquired, std::memory_order_relaxed); +} + +void BpHwRefBase::onLastStrongRef(const void* /*id*/) +{ + if (mRemote) { + mRemote->decStrong(this); + } +} + +bool BpHwRefBase::onIncStrongAttempted(uint32_t /*flags*/, const void* /*id*/) +{ + return false; +} + +// --------------------------------------------------------------------------- + +} // namespace hardware +} // namespace android diff --git a/aosp/system/libhwbinder/ProcessState.cpp b/aosp/system/libhwbinder/ProcessState.cpp new file mode 100644 index 000000000..a26d09c98 --- /dev/null +++ b/aosp/system/libhwbinder/ProcessState.cpp @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "hw-ProcessState" + +#include + +#include +#include +#include +#include +#include +#include + +#include "binder_kernel.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEFAULT_BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2) +#define DEFAULT_MAX_BINDER_THREADS 0 +#define DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION 1 + +// ------------------------------------------------------------------------- + +namespace android { +namespace hardware { + +class PoolThread : public Thread +{ +public: + explicit PoolThread(bool isMain) + : mIsMain(isMain) + { + } + +protected: + virtual bool threadLoop() + { + IPCThreadState::self()->joinThreadPool(mIsMain); + return false; + } + + const bool mIsMain; +}; + +sp ProcessState::self() +{ + return init(DEFAULT_BINDER_VM_SIZE, false /*requireMmapSize*/); +} + +sp ProcessState::selfOrNull() { + return init(0, false /*requireMmapSize*/); +} + +sp ProcessState::initWithMmapSize(size_t mmapSize) { + return init(mmapSize, true /*requireMmapSize*/); +} + +sp ProcessState::init(size_t mmapSize, bool requireMmapSize) { + [[clang::no_destroy]] static sp gProcess; + [[clang::no_destroy]] static std::mutex gProcessMutex; + + if (mmapSize == 0) { + std::lock_guard l(gProcessMutex); + return gProcess; + } + + [[clang::no_destroy]] static std::once_flag gProcessOnce; + std::call_once(gProcessOnce, [&](){ + std::lock_guard l(gProcessMutex); + gProcess = new ProcessState(mmapSize); + }); + + if (requireMmapSize) { + LOG_ALWAYS_FATAL_IF(mmapSize != gProcess->getMmapSize(), + "ProcessState already initialized with a different mmap size."); + } + + return gProcess; +} + +void ProcessState::startThreadPool() +{ + AutoMutex _l(mLock); + if (!mThreadPoolStarted) { + mThreadPoolStarted = true; + if (mSpawnThreadOnStart) { + spawnPooledThread(true); + } + } +} + +sp ProcessState::getContextObject(const sp& /*caller*/) +{ + return getStrongProxyForHandle(0); +} + +void ProcessState::becomeContextManager() +{ + AutoMutex _l(mLock); + +#if 0 + flat_binder_object obj { + .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX, + }; + + status_t result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj); +#endif + status_t result = 1; + + // fallback to original method + if (result != 0) { + android_errorWriteLog(0x534e4554, "121035042"); + + int unused = 0; + result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused); + } + + if (result == -1) { + ALOGE("Binder ioctl to become context manager failed: %s\n", strerror(errno)); + } +} + +// Get references to userspace objects held by the kernel binder driver +// Writes up to count elements into buf, and returns the total number +// of references the kernel has, which may be larger than count. +// buf may be NULL if count is 0. The pointers returned by this method +// should only be used for debugging and not dereferenced, they may +// already be invalid. +ssize_t ProcessState::getKernelReferences(size_t buf_count, uintptr_t* buf) { + binder_node_debug_info info = {}; + + uintptr_t* end = buf ? buf + buf_count : nullptr; + size_t count = 0; + + do { + status_t result = ioctl(mDriverFD, BINDER_GET_NODE_DEBUG_INFO, &info); + if (result < 0) { + return -1; + } + if (info.ptr != 0) { + if (buf && buf < end) *buf++ = info.ptr; + count++; + if (buf && buf < end) *buf++ = info.cookie; + count++; + } + } while (info.ptr != 0); + + return count; +} + +// Queries the driver for the current strong reference count of the node +// that the handle points to. Can only be used by the servicemanager. +// +// Returns -1 in case of failure, otherwise the strong reference count. +ssize_t ProcessState::getStrongRefCountForNodeByHandle(int32_t handle) { + binder_node_info_for_ref info; + memset(&info, 0, sizeof(binder_node_info_for_ref)); + + info.handle = handle; + + status_t result = ioctl(mDriverFD, BINDER_GET_NODE_INFO_FOR_REF, &info); + + if (result != OK) { + static bool logged = false; + if (!logged) { + ALOGW("Kernel does not support BINDER_GET_NODE_INFO_FOR_REF."); + logged = true; + } + return -1; + } + + return info.strong_count; +} + +size_t ProcessState::getMmapSize() { + return mMmapSize; +} + +void ProcessState::setCallRestriction(CallRestriction restriction) { + LOG_ALWAYS_FATAL_IF(IPCThreadState::selfOrNull() != nullptr, + "Call restrictions must be set before the threadpool is started."); + + mCallRestriction = restriction; +} + +ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle) +{ + const size_t N=mHandleToObject.size(); + if (N <= (size_t)handle) { + handle_entry e; + e.binder = nullptr; + e.refs = nullptr; + status_t err = mHandleToObject.insertAt(e, N, handle+1-N); + if (err < NO_ERROR) return nullptr; + } + return &mHandleToObject.editItemAt(handle); +} + +sp ProcessState::getStrongProxyForHandle(int32_t handle) +{ + sp result; + + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + if (e != nullptr) { + // We need to create a new BpHwBinder if there isn't currently one, OR we + // are unable to acquire a weak reference on this current one. See comment + // in getWeakProxyForHandle() for more info about this. + IBinder* b = e->binder; + if (b == nullptr || !e->refs->attemptIncWeak(this)) { + b = new BpHwBinder(handle); + e->binder = b; + if (b) e->refs = b->getWeakRefs(); + result = b; + } else { + // This little bit of nastyness is to allow us to add a primary + // reference to the remote proxy when this team doesn't have one + // but another team is sending the handle to us. + result.force_set(b); + e->refs->decWeak(this); + } + } + + return result; +} + +wp ProcessState::getWeakProxyForHandle(int32_t handle) +{ + wp result; + + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + if (e != nullptr) { + // We need to create a new BpHwBinder if there isn't currently one, OR we + // are unable to acquire a weak reference on this current one. The + // attemptIncWeak() is safe because we know the BpHwBinder destructor will always + // call expungeHandle(), which acquires the same lock we are holding now. + // We need to do this because there is a race condition between someone + // releasing a reference on this BpHwBinder, and a new reference on its handle + // arriving from the driver. + IBinder* b = e->binder; + if (b == nullptr || !e->refs->attemptIncWeak(this)) { + b = new BpHwBinder(handle); + result = b; + e->binder = b; + if (b) e->refs = b->getWeakRefs(); + } else { + result = b; + e->refs->decWeak(this); + } + } + + return result; +} + +void ProcessState::expungeHandle(int32_t handle, IBinder* binder) +{ + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + // This handle may have already been replaced with a new BpHwBinder + // (if someone failed the AttemptIncWeak() above); we don't want + // to overwrite it. + if (e && e->binder == binder) e->binder = nullptr; +} + +String8 ProcessState::makeBinderThreadName() { + int32_t s = android_atomic_add(1, &mThreadPoolSeq); + pid_t pid = getpid(); + String8 name; + name.appendFormat("HwBinder:%d_%X", pid, s); + return name; +} + +void ProcessState::spawnPooledThread(bool isMain) +{ + if (mThreadPoolStarted) { + String8 name = makeBinderThreadName(); + ALOGV("Spawning new pooled thread, name=%s\n", name.c_str()); + sp t = new PoolThread(isMain); + t->run(name.c_str()); + } +} + +status_t ProcessState::setThreadPoolConfiguration(size_t maxThreads, bool callerJoinsPool) { + LOG_ALWAYS_FATAL_IF(mThreadPoolStarted && maxThreads < mMaxThreads, + "Binder threadpool cannot be shrunk after starting"); + + // if the caller joins the pool, then there will be one thread which is impossible. + LOG_ALWAYS_FATAL_IF(maxThreads == 0 && callerJoinsPool, + "Binder threadpool must have a minimum of one thread if caller joins pool."); + + size_t threadsToAllocate = maxThreads; + + // If the caller is going to join the pool it will contribute one thread to the threadpool. + // This is part of the API's contract. + if (callerJoinsPool) threadsToAllocate--; + + // If we can, spawn one thread from userspace when the threadpool is started. This ensures + // that there is always a thread available to start more threads as soon as the threadpool + // is started. + bool spawnThreadOnStart = threadsToAllocate > 0; + if (spawnThreadOnStart) threadsToAllocate--; + + // the BINDER_SET_MAX_THREADS ioctl really tells the kernel how many threads + // it's allowed to spawn, *in addition* to any threads we may have already + // spawned locally. + size_t kernelMaxThreads = threadsToAllocate; + + AutoMutex _l(mLock); + if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &kernelMaxThreads) == -1) { + ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); + return -errno; + } + + mMaxThreads = maxThreads; + mSpawnThreadOnStart = spawnThreadOnStart; + + return NO_ERROR; +} + +status_t ProcessState::enableOnewaySpamDetection(bool enable) { + uint32_t enableDetection = enable ? 1 : 0; + if (ioctl(mDriverFD, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enableDetection) == -1) { + ALOGI("Binder ioctl to enable oneway spam detection failed: %s", strerror(errno)); + return -errno; + } + return NO_ERROR; +} + +size_t ProcessState::getMaxThreads() { + return mMaxThreads; +} + +void ProcessState::giveThreadPoolName() { + androidSetThreadName( makeBinderThreadName().c_str() ); +} + +static int open_driver() +{ + int fd = open("/dev/hwbinder", O_RDWR | O_CLOEXEC); + if (fd >= 0) { + int vers = 0; + status_t result = ioctl(fd, BINDER_VERSION, &vers); + if (result == -1) { + ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); + close(fd); + fd = -1; + } + if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) { + ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)!", vers, BINDER_CURRENT_PROTOCOL_VERSION); + close(fd); + fd = -1; + } + size_t maxThreads = DEFAULT_MAX_BINDER_THREADS; + result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); + if (result == -1) { + ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); + } + uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION; + result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable); + if (result == -1) { + ALOGV("Binder ioctl to enable oneway spam detection failed: %s", strerror(errno)); + } + } else { + ALOGW("Opening '/dev/hwbinder' failed: %s\n", strerror(errno)); + } + return fd; +} + +ProcessState::ProcessState(size_t mmapSize) + : mDriverFD(open_driver()) + , mVMStart(MAP_FAILED) + , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER) + , mExecutingThreadsCount(0) + , mMaxThreads(DEFAULT_MAX_BINDER_THREADS) + , mStarvationStartTimeMs(0) + , mThreadPoolStarted(false) + , mSpawnThreadOnStart(true) + , mThreadPoolSeq(1) + , mMmapSize(mmapSize) + , mCallRestriction(CallRestriction::NONE) +{ + if (mDriverFD >= 0) { + // mmap the binder, providing a chunk of virtual address space to receive transactions. + mVMStart = mmap(nullptr, mMmapSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); + if (mVMStart == MAP_FAILED) { + // *sigh* + ALOGE("Mmapping /dev/hwbinder failed: %s\n", strerror(errno)); + close(mDriverFD); + mDriverFD = -1; + } + } + +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating."); +#endif +} + +ProcessState::~ProcessState() +{ + if (mDriverFD >= 0) { + if (mVMStart != MAP_FAILED) { + munmap(mVMStart, mMmapSize); + } + close(mDriverFD); + } + mDriverFD = -1; +} + +} // namespace hardware +} // namespace android diff --git a/aosp/system/logging/liblog/include/android/log.h b/aosp/system/logging/liblog/include/android/log.h new file mode 100644 index 000000000..7fbf918cd --- /dev/null +++ b/aosp/system/logging/liblog/include/android/log.h @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2009 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. + */ + +#pragma once + +/** + * @addtogroup Logging + * @{ + */ + +/** + * \file + * + * Support routines to send messages to the Android log buffer, + * which can later be accessed through the `logcat` utility. + * + * Each log message must have + * - a priority + * - a log tag + * - some text + * + * The tag normally corresponds to the component that emits the log message, + * and should be reasonably small. + * + * Log message text may be truncated to less than an implementation-specific + * limit (1023 bytes). + * + * Note that a newline character ("\n") will be appended automatically to your + * log message, if not already there. It is not possible to send several + * messages and have them appear on a single line in logcat. + * + * Please use logging in moderation: + * + * - Sending log messages eats CPU and slow down your application and the + * system. + * + * - The circular log buffer is pretty small, so sending many messages + * will hide other important log messages. + * + * - In release builds, only send log messages to account for exceptional + * conditions. + */ + +#include +#include +#include +#include + +#ifndef aosp_hack +#define aosp_hack() if (true) return +#endif +#ifndef aosp_hack_p +#define aosp_hack_p(p) if (true) return p +#endif + +#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Android log priority values, in increasing order of priority. + */ +typedef enum android_LogPriority { + /** For internal use only. */ + ANDROID_LOG_UNKNOWN = 0, + /** The default priority, for internal use only. */ + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + /** Verbose logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_VERBOSE, + /** Debug logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_DEBUG, + /** Informational logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_INFO, + /** Warning logging. For use with recoverable failures. */ + ANDROID_LOG_WARN, + /** Error logging. For use with unrecoverable failures. */ + ANDROID_LOG_ERROR, + /** Fatal logging. For use when aborting. */ + ANDROID_LOG_FATAL, + /** For internal use only. */ + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ +} android_LogPriority; + +/** + * Writes the constant string `text` to the log, with priority `prio` and tag + * `tag`. + */ +int __android_log_write(int prio, const char* tag, const char* text); + +/** + * Writes a formatted string to the log, with priority `prio` and tag `tag`. + * The details of formatting are the same as for + * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html). + */ +int __android_log_print(int prio, const char* tag, const char* fmt, ...) + __attribute__((__format__(printf, 3, 4))); + +/** + * Equivalent to `__android_log_print`, but taking a `va_list`. + * (If `__android_log_print` is like `printf`, this is like `vprintf`.) + */ +int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap) + __attribute__((__format__(printf, 3, 0))); + +/** + * Writes an assertion failure to the log (as `ANDROID_LOG_FATAL`) and to + * stderr, before calling + * [abort(3)](http://man7.org/linux/man-pages/man3/abort.3.html). + * + * If `fmt` is non-null, `cond` is unused. If `fmt` is null, the string + * `Assertion failed: %s` is used with `cond` as the string argument. + * If both `fmt` and `cond` are null, a default string is provided. + * + * Most callers should use + * [assert(3)](http://man7.org/linux/man-pages/man3/assert.3.html) from + * `<assert.h>` instead, or the `__assert` and `__assert2` functions + * provided by bionic if more control is needed. They support automatically + * including the source filename and line number more conveniently than this + * function. + */ +void __android_log_assert(const char* cond, const char* tag, const char* fmt, ...) + __attribute__((__noreturn__)) __attribute__((__format__(printf, 3, 4))); + +/** + * Identifies a specific log buffer for __android_log_buf_write() + * and __android_log_buf_print(). + */ +typedef enum log_id { + LOG_ID_MIN = 0, + + /** The main log buffer. This is the only log buffer available to apps. */ + LOG_ID_MAIN = 0, + /** The radio log buffer. */ + LOG_ID_RADIO = 1, + /** The event log buffer. */ + LOG_ID_EVENTS = 2, + /** The system log buffer. */ + LOG_ID_SYSTEM = 3, + /** The crash log buffer. */ + LOG_ID_CRASH = 4, + /** The statistics log buffer. */ + LOG_ID_STATS = 5, + /** The security log buffer. */ + LOG_ID_SECURITY = 6, + /** The kernel log buffer. */ + LOG_ID_KERNEL = 7, + + LOG_ID_MAX, + + /** Let the logging function choose the best log target. */ + LOG_ID_DEFAULT = 0x7FFFFFFF +} log_id_t; + +/** + * Writes the constant string `text` to the log buffer `id`, + * with priority `prio` and tag `tag`. + * + * Apps should use __android_log_write() instead. + */ +int __android_log_buf_write(int bufID, int prio, const char* tag, const char* text); + +/** + * Writes a formatted string to log buffer `id`, + * with priority `prio` and tag `tag`. + * The details of formatting are the same as for + * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html). + * + * Apps should use __android_log_print() instead. + */ +int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...) + __attribute__((__format__(printf, 4, 5))); + +/** + * Logger data struct used for writing log messages to liblog via __android_log_write_logger_data() + * and sending log messages to user defined loggers specified in __android_log_set_logger(). + */ +struct __android_log_message { + /** Must be set to sizeof(__android_log_message) and is used for versioning. */ + size_t struct_size; + + /** {@link log_id_t} values. */ + int32_t buffer_id; + + /** {@link android_LogPriority} values. */ + int32_t priority; + + /** The tag for the log message. */ + const char* tag; + + /** Optional file name, may be set to nullptr. */ + const char* file; + + /** Optional line number, ignore if file is nullptr. */ + uint32_t line; + + /** The log message itself. */ + const char* message; +}; + +/** + * Prototype for the 'logger' function that is called for every log message. + */ +typedef void (*__android_logger_function)(const struct __android_log_message* log_message); +/** + * Prototype for the 'abort' function that is called when liblog will abort due to + * __android_log_assert() failures. + */ +typedef void (*__android_aborter_function)(const char* abort_message); + +/** + * Writes the log message specified by log_message. log_message includes additional file name and + * line number information that a logger may use. log_message is versioned for backwards + * compatibility. + * This assumes that loggability has already been checked through __android_log_is_loggable(). + * Higher level logging libraries, such as libbase, first check loggability, then format their + * buffers, then pass the message to liblog via this function, and therefore we do not want to + * duplicate the loggability check here. + * + * @param log_message the log message itself, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_write_log_message(struct __android_log_message* log_message) __INTRODUCED_IN(30); + +/** + * Sets a user defined logger function. All log messages sent to liblog will be set to the + * function pointer specified by logger for processing. It is not expected that log messages are + * already terminated with a new line. This function should add new lines if required for line + * separation. + * + * @param logger the new function that will handle log messages. + * + * Available since API level 30. + */ +void __android_log_set_logger(__android_logger_function logger) __INTRODUCED_IN(30); + +/** + * Writes the log message to logd. This is an __android_logger_function and can be provided to + * __android_log_set_logger(). It is the default logger when running liblog on a device. + * + * @param log_message the log message to write, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_logd_logger(const struct __android_log_message* log_message) __INTRODUCED_IN(30); + +/** + * Writes the log message to stderr. This is an __android_logger_function and can be provided to + * __android_log_set_logger(). It is the default logger when running liblog on host. + * + * @param log_message the log message to write, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_stderr_logger(const struct __android_log_message* log_message) + __INTRODUCED_IN(30); + +/** + * Sets a user defined aborter function that is called for __android_log_assert() failures. This + * user defined aborter function is highly recommended to abort and be noreturn, but is not strictly + * required to. + * + * @param aborter the new aborter function, see __android_aborter_function. + * + * Available since API level 30. + */ +void __android_log_set_aborter(__android_aborter_function aborter) __INTRODUCED_IN(30); + +/** + * Calls the stored aborter function. This allows for other logging libraries to use the same + * aborter function by calling this function in liblog. + * + * @param abort_message an additional message supplied when aborting, for example this is used to + * call android_set_abort_message() in __android_log_default_aborter(). + * + * Available since API level 30. + */ +void __android_log_call_aborter(const char* abort_message) __INTRODUCED_IN(30); + +/** + * Sets android_set_abort_message() on device then aborts(). This is the default aborter. + * + * @param abort_message an additional message supplied when aborting. This functions calls + * android_set_abort_message() with its contents. + * + * Available since API level 30. + */ +void __android_log_default_aborter(const char* abort_message) __attribute__((noreturn)) +__INTRODUCED_IN(30); + +/** + * Use the per-tag properties "log.tag." along with the minimum priority from + * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will + * be printed. A non-zero result indicates yes, zero indicates false. + * + * If both a priority for a tag and a minimum priority are set by + * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the + * minimum priority needed to log. If only one is set, then that value is used to determine the + * minimum priority needed. If none are set, then default_priority is used. + * + * @param prio the priority to test, takes android_LogPriority values. + * @param tag the tag to test. + * @param default_prio the default priority to use if no properties or minimum priority are set. + * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not. + * + * Available since API level 30. + */ +int __android_log_is_loggable(int prio, const char* tag, int default_prio) __INTRODUCED_IN(30); + +/** + * Use the per-tag properties "log.tag." along with the minimum priority from + * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will + * be printed. A non-zero result indicates yes, zero indicates false. + * + * If both a priority for a tag and a minimum priority are set by + * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the + * minimum priority needed to log. If only one is set, then that value is used to determine the + * minimum priority needed. If none are set, then default_priority is used. + * + * @param prio the priority to test, takes android_LogPriority values. + * @param tag the tag to test. + * @param len the length of the tag. + * @param default_prio the default priority to use if no properties or minimum priority are set. + * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not. + * + * Available since API level 30. + */ +int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio) + __INTRODUCED_IN(30); + +/** + * Sets the minimum priority that will be logged for this process. + * + * @param priority the new minimum priority to set, takes android_LogPriority values. + * @return the previous set minimum priority as android_LogPriority values, or + * ANDROID_LOG_DEFAULT if none was set. + * + * Available since API level 30. + */ +int32_t __android_log_set_minimum_priority(int32_t priority) __INTRODUCED_IN(30); + +/** + * Gets the minimum priority that will be logged for this process. If none has been set by a + * previous __android_log_set_minimum_priority() call, this returns ANDROID_LOG_DEFAULT. + * + * @return the current minimum priority as android_LogPriority values, or + * ANDROID_LOG_DEFAULT if none is set. + * + * Available since API level 30. + */ +int32_t __android_log_get_minimum_priority(void) __INTRODUCED_IN(30); + +/** + * Sets the default tag if no tag is provided when writing a log message. Defaults to + * getprogname(). This truncates tag to the maximum log message size, though appropriate tags + * should be much smaller. + * + * @param tag the new log tag. + * + * Available since API level 30. + */ +void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/aosp/system/logging/logd/main.cpp b/aosp/system/logging/logd/main.cpp new file mode 100644 index 000000000..ad8699f0f --- /dev/null +++ b/aosp/system/logging/logd/main.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2012-2013 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CommandListener.h" +#include "LogAudit.h" +#include "LogBuffer.h" +#include "LogKlog.h" +#include "LogListener.h" +#include "LogReader.h" +#include "LogStatistics.h" +#include "LogTags.h" +#include "LogUtils.h" +#include "SerializedLogBuffer.h" +#include "SimpleLogBuffer.h" +#include "TrustyLog.h" + +using android::base::GetBoolProperty; +using android::base::GetProperty; +using android::base::SetProperty; + +#define KMSG_PRIORITY(PRI) \ + '<', '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \ + '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, '>' + +// The service is designed to be run by init, it does not respond well to starting up manually. Init +// has a 'sigstop' feature that sends SIGSTOP to a service immediately before calling exec(). This +// allows debuggers, etc to be attached to logd at the very beginning, while still having init +// handle the user, groups, capabilities, files, etc setup. +static void DropPrivs(bool klogd, bool auditd) { + aosp_hack(); + if (set_sched_policy(0, SP_BACKGROUND) < 0) { + PLOG(FATAL) << "failed to set background scheduling policy"; + } + + if (!GetBoolProperty("ro.debuggable", false)) { + if (prctl(PR_SET_DUMPABLE, 0) == -1) { + PLOG(FATAL) << "failed to clear PR_SET_DUMPABLE"; + } + } + + std::unique_ptr caps(cap_init(), cap_free); + if (cap_clear(caps.get()) < 0) { + PLOG(FATAL) << "cap_clear() failed"; + } + if (klogd) { + cap_value_t cap_syslog = CAP_SYSLOG; + if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_syslog, CAP_SET) < 0 || + cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_syslog, CAP_SET) < 0) { + PLOG(FATAL) << "Failed to set CAP_SYSLOG"; + } + } + if (auditd) { + cap_value_t cap_audit_control = CAP_AUDIT_CONTROL; + if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_audit_control, CAP_SET) < 0 || + cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_audit_control, CAP_SET) < 0) { + PLOG(FATAL) << "Failed to set CAP_AUDIT_CONTROL"; + } + } + if (cap_set_proc(caps.get()) < 0) { + PLOG(FATAL) << "cap_set_proc() failed"; + } +} + +static void readDmesg(LogAudit* al, LogKlog* kl) { + if (!al && !kl) { + return; + } + + int rc = klogctl(KLOG_SIZE_BUFFER, nullptr, 0); + if (rc <= 0) { + return; + } + + // Margin for additional input race or trailing nul + ssize_t len = rc + 1024; + std::unique_ptr buf(new char[len]); + + // Drop old logs in /proc/kmsg to avoid duplicate print. + rc = klogctl(KLOG_SIZE_UNREAD, nullptr, 0); + if (rc > 0) + rc = klogctl(KLOG_READ, buf.get(), rc); + + + rc = klogctl(KLOG_READ_ALL, buf.get(), len); + if (rc <= 0) { + return; + } + + if (rc < len) { + len = rc + 1; + } + buf[--len] = '\0'; + + ssize_t sublen; + for (char *ptr = nullptr, *tok = buf.get(); + (rc >= 0) && !!(tok = android::log_strntok_r(tok, len, ptr, sublen)); + tok = nullptr) { + if ((sublen <= 0) || !*tok) continue; + if (al) { + rc = al->log(tok, sublen); + } + if (kl) { + rc = kl->log(tok, sublen); + } + } +} + +static int issueReinit() { + int sock = TEMP_FAILURE_RETRY(socket_local_client( + "logd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)); + if (sock < 0) return -errno; + + static const char reinitStr[] = "reinit"; + ssize_t ret = TEMP_FAILURE_RETRY(write(sock, reinitStr, sizeof(reinitStr))); + if (ret < 0) return -errno; + + struct pollfd p = {.fd = sock, .events = POLLIN}; + ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000)); + if (ret < 0) return -errno; + if ((ret == 0) || !(p.revents & POLLIN)) return -ETIME; + + static const char success[] = "success"; + char buffer[sizeof(success) - 1] = {}; + ret = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer))); + if (ret < 0) return -errno; + + return strncmp(buffer, success, sizeof(success) - 1) != 0; +} + +// Foreground waits for exit of the main persistent threads +// that are started here. The threads are created to manage +// UNIX domain client sockets for writing, reading and +// controlling the user space logger, and for any additional +// logging plugins like auditd and restart control. Additional +// transitory per-client threads are created for each reader. +int main(int argc, char* argv[]) { + // We want EPIPE when a reader disconnects, not to terminate logd. + signal(SIGPIPE, SIG_IGN); + // logd is written under the assumption that the timezone is UTC. + // If TZ is not set, persist.sys.timezone is looked up in some time utility + // libc functions, including mktime. It confuses the logd time handling, + // so here explicitly set TZ to UTC, which overrides the property. + setenv("TZ", "UTC", 1); + // issue reinit command. KISS argument parsing. + if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) { + return issueReinit(); + } + + android::base::InitLogging( + argv, [](android::base::LogId log_id, android::base::LogSeverity severity, + const char* tag, const char* file, unsigned int line, const char* message) { + if (tag && strcmp(tag, "logd") != 0) { + auto prefixed_message = android::base::StringPrintf("%s: %s", tag, message); + android::base::KernelLogger(log_id, severity, "logd", file, line, + prefixed_message.c_str()); + } else { + android::base::KernelLogger(log_id, severity, "logd", file, line, message); + } + }); + + static const char dev_kmsg[] = "/dev/kmsg"; + int fdDmesg = android_get_control_file(dev_kmsg); + if (fdDmesg < 0) { + fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC)); + } + + int fdPmesg = -1; + bool klogd_default = + GetBoolProperty("ro.debuggable", false) && !GetBoolProperty("ro.config.low_ram", false); + bool klogd = GetBoolProperty("ro.logd.kernel", klogd_default); + if (klogd) { + SetProperty("ro.logd.kernel", "true"); + static const char proc_kmsg[] = "/proc/kmsg"; + fdPmesg = android_get_control_file(proc_kmsg); + if (fdPmesg < 0) { + fdPmesg = TEMP_FAILURE_RETRY( + open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC)); + } + if (fdPmesg < 0) PLOG(ERROR) << "Failed to open " << proc_kmsg; + } + + bool auditd = GetBoolProperty("ro.logd.auditd", true); + DropPrivs(klogd, auditd); + + // A cache of event log tags + LogTags log_tags; + + // Pruning configuration. + PruneList prune_list; + + std::string buffer_type = GetProperty("logd.buffer_type", "serialized"); + + LogStatistics log_statistics(false, buffer_type == "serialized"); + + // Serves the purpose of managing the last logs times read on a socket connection, and as a + // reader lock on a range of log entries. + LogReaderList reader_list; + + // LogBuffer is the object which is responsible for holding all log entries. + LogBuffer* log_buffer = nullptr; + if (buffer_type == "serialized") { + log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics); + } else if (buffer_type == "simple") { + log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics); + } else { + LOG(FATAL) << "buffer_type must be one of 'serialized' or 'simple'"; + } + + // LogReader listens on /dev/socket/logdr. When a client + // connects, log entries in the LogBuffer are written to the client. + LogReader* reader = new LogReader(log_buffer, &reader_list); + if (reader->startListener()) { + return EXIT_FAILURE; + } + + // LogListener listens on /dev/socket/logdw for client + // initiated log messages. New log entries are added to LogBuffer + // and LogReader is notified to send updates to connected clients. + LogListener* swl = new LogListener(log_buffer); + if (!swl->StartListener()) { + return EXIT_FAILURE; + } + + // Command listener listens on /dev/socket/logd for incoming logd + // administrative commands. + CommandListener* cl = new CommandListener(log_buffer, &log_tags, &prune_list, &log_statistics); + if (cl->startListener()) { + return EXIT_FAILURE; + } + + // Notify that others can now interact with logd + SetProperty("logd.ready", "true"); + + // LogAudit listens on NETLINK_AUDIT socket for selinux + // initiated log messages. New log entries are added to LogBuffer + // and LogReader is notified to send updates to connected clients. + LogAudit* al = nullptr; + if (auditd) { + int dmesg_fd = GetBoolProperty("ro.logd.auditd.dmesg", true) ? fdDmesg : -1; + al = new LogAudit(log_buffer, dmesg_fd, &log_statistics); + } + + LogKlog* kl = nullptr; + if (klogd) { + kl = new LogKlog(log_buffer, fdDmesg, fdPmesg, al != nullptr, &log_statistics); + } + + readDmesg(al, kl); + + // failure is an option ... messages are in dmesg (required by standard) + if (kl && kl->startListener()) { + delete kl; + } + + if (al && al->startListener()) { + delete al; + } + + TrustyLog::create(log_buffer); + + TEMP_FAILURE_RETRY(pause()); + + return EXIT_SUCCESS; +} diff --git a/aosp/system/netd/server/BandwidthController.cpp b/aosp/system/netd/server/BandwidthController.cpp new file mode 100644 index 000000000..a75b2bb2a --- /dev/null +++ b/aosp/system/netd/server/BandwidthController.cpp @@ -0,0 +1,788 @@ +/* + * Copyright (C) 2011 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. + */ + +// #define LOG_NDEBUG 0 + +/* + * The CommandListener, FrameworkListener don't allow for + * multiple calls in parallel to reach the BandwidthController. + * If they ever were to allow it, then netd/ would need some tweaking. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define __STDC_FORMAT_MACROS 1 +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" +#define LOG_TAG "BandwidthController" +#include +#include + +#include +#include "BandwidthController.h" +#include "Controllers.h" +#include "FirewallController.h" /* For makeCriticalCommands */ +#include "Fwmark.h" +#include "NetdConstants.h" +#include "android/net/INetd.h" + +/* Alphabetical */ +#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s\n" +const char BandwidthController::LOCAL_INPUT[] = "bw_INPUT"; +const char BandwidthController::LOCAL_FORWARD[] = "bw_FORWARD"; +const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT"; +const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING"; +const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING"; +const char BandwidthController::LOCAL_GLOBAL_ALERT[] = "bw_global_alert"; + +auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput; + +using android::base::Join; +using android::base::StartsWith; +using android::base::StringAppendF; +using android::base::StringPrintf; +using android::net::FirewallController; +using android::net::INetd::CLAT_MARK; +using android::netdutils::StatusOr; +using android::netdutils::UniqueFile; + +namespace { + +const char ALERT_GLOBAL_NAME[] = "globalAlert"; +const std::string NEW_CHAIN_COMMAND = "-N "; + +/** + * Some comments about the rules: + * * Ordering + * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains. + * E.g. "-I bw_INPUT -i rmnet0 -j costly" + * - quota'd rules in the costly chain should be before bw_penalty_box lookups. + * - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains. + * + * * global quota vs per interface quota + * - global quota for all costly interfaces uses a single costly chain: + * . initial rules + * iptables -N bw_costly_shared + * iptables -I bw_INPUT -i iface0 -j bw_costly_shared + * iptables -I bw_OUTPUT -o iface0 -j bw_costly_shared + * iptables -I bw_costly_shared -m quota \! --quota 500000 \ + * -j REJECT --reject-with icmp-net-prohibited + * iptables -A bw_costly_shared -j bw_penalty_box + * iptables -A bw_penalty_box -j bw_happy_box + * iptables -A bw_happy_box -j bw_data_saver + * + * . adding a new iface to this, E.g.: + * iptables -I bw_INPUT -i iface1 -j bw_costly_shared + * iptables -I bw_OUTPUT -o iface1 -j bw_costly_shared + * + * - quota per interface. This is achieve by having "costly" chains per quota. + * E.g. adding a new costly interface iface0 with its own quota: + * iptables -N bw_costly_iface0 + * iptables -I bw_INPUT -i iface0 -j bw_costly_iface0 + * iptables -I bw_OUTPUT -o iface0 -j bw_costly_iface0 + * iptables -A bw_costly_iface0 -m quota \! --quota 500000 \ + * -j REJECT --reject-with icmp-port-unreachable + * iptables -A bw_costly_iface0 -j bw_penalty_box + * + * * Penalty box, happy box and data saver. + * - bw_penalty box is a denylist of apps that are rejected. + * - bw_happy_box is an allowlist of apps. It always includes all system apps + * - bw_data_saver implements data usage restrictions. + * - Via the UI the user can add and remove apps from the allowlist and + * denylist, and turn on/off data saver. + * - The denylist takes precedence over the allowlist and the allowlist + * takes precedence over data saver. + * + * * bw_penalty_box handling: + * - only one bw_penalty_box for all interfaces + * E.g Adding an app: + * iptables -I bw_penalty_box -m owner --uid-owner app_3 \ + * -j REJECT --reject-with icmp-port-unreachable + * + * * bw_happy_box handling: + * - The bw_happy_box comes after the penalty box. + * E.g Adding a happy app, + * iptables -I bw_happy_box -m owner --uid-owner app_3 \ + * -j RETURN + * + * * bw_data_saver handling: + * - The bw_data_saver comes after the happy box. + * Enable data saver: + * iptables -R 1 bw_data_saver -j REJECT --reject-with icmp-port-unreachable + * Disable data saver: + * iptables -R 1 bw_data_saver -j RETURN + */ + +const std::string COMMIT_AND_CLOSE = "COMMIT\n"; + +static const std::vector IPT_FLUSH_COMMANDS = { + /* + * Cleanup rules. + * Should normally include bw_costly_, but we rely on the way they are setup + * to allow coexistance. + */ + "*filter", + ":bw_INPUT -", + ":bw_OUTPUT -", + ":bw_FORWARD -", + ":bw_happy_box -", + ":bw_penalty_box -", + ":bw_data_saver -", + ":bw_costly_shared -", + ":bw_global_alert -", + "COMMIT", + "*raw", + ":bw_raw_PREROUTING -", + "COMMIT", + "*mangle", + ":bw_mangle_POSTROUTING -", + COMMIT_AND_CLOSE}; + +static const uint32_t uidBillingMask = Fwmark::getUidBillingMask(); + +/** + * Basic commands for creation of hooks into data accounting and data boxes. + * + * Included in these commands are rules to prevent the double-counting of IPsec + * packets. The general overview is as follows: + * > All interface counters (counted in PREROUTING, POSTROUTING) must be + * completely accurate, and count only the outer packet. As such, the inner + * packet must be ignored, which is done through the use of two rules: use + * of the policy module (for tunnel mode), and VTI interface checks (for + * tunnel or transport-in-tunnel mode). The VTI interfaces should be named + * ipsec* + * > Outbound UID billing can always be done with the outer packets, due to the + * ability to always find the correct UID (based on the skb->sk). As such, + * the inner packets should be ignored based on the policy module, or the + * output interface if a VTI (ipsec+) + * > Inbound UDP-encap-ESP packets can be correctly mapped to the UID that + * opened the encap socket, and as such, should be billed as early as + * possible (for transport mode; tunnel mode usage should be billed to + * sending/receiving application). Due to the inner packet being + * indistinguishable from the inner packet of ESP, a uidBillingDone mark + * has to be applied to prevent counting a second time. + * > Inbound ESP has no socket, and as such must be accounted later. ESP + * protocol packets are skipped via a blanket rule. + * > Note that this solution is asymmetrical. Adding the VTI or policy matcher + * ignore rule in the input chain would actually break the INPUT chain; + * Those rules are designed to ignore inner packets, and in the tunnel + * mode UDP, or any ESP case, we would not have billed the outer packet. + * + * See go/ipsec-data-accounting for more information. + */ + +std::vector getBasicAccountingCommands() { + // clang-format off + std::vector ipt_basic_accounting_commands = { + "*filter", + + "-A bw_INPUT -j bw_global_alert", + // Prevents IPSec double counting (ESP and UDP-encap-ESP respectively) + "-A bw_INPUT -p esp -j RETURN", + StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN", uidBillingMask, + uidBillingMask), + StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask), + "-A bw_OUTPUT -j bw_global_alert", + "-A bw_costly_shared -j bw_penalty_box", + ("-I bw_penalty_box -m bpf --object-pinned " XT_BPF_DENYLIST_PROG_PATH " -j REJECT"), + "-A bw_penalty_box -j bw_happy_box", + "-A bw_happy_box -j bw_data_saver", + "-A bw_data_saver -j RETURN", + ("-I bw_happy_box -m bpf --object-pinned " XT_BPF_ALLOWLIST_PROG_PATH " -j RETURN"), + "COMMIT", + + "*raw", + // Drop duplicate ingress clat packets + StringPrintf("-A bw_raw_PREROUTING -m mark --mark 0x%x -j DROP", CLAT_MARK), + // Prevents IPSec double counting (Tunnel mode and Transport mode, + // respectively) + ("-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN"), + "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN", + // This is ingress interface accounting. There is no need to do anything specific + // for 464xlat here, because we only ever account 464xlat traffic on the clat + // interface and later correct for overhead (+20 bytes/packet). + // + // Note: eBPF offloaded packets never hit base interface's ip6tables, and non + // offloaded packets are dropped up above due to being marked with CLAT_MARK + // + // Hence we will never double count and additional corrections are not needed. + // We can simply take the sum of base and stacked (+20B/pkt) interface counts. + ("-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH), + "COMMIT", + + "*mangle", + // Prevents IPSec double counting (Tunnel mode and Transport mode, + // respectively) + ("-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN"), + "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN", + // Clear the uid billing done (egress) mark before sending this packet + StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x", uidBillingMask), + // This is egress interface accounting: we account 464xlat traffic only on + // the clat interface (as offloaded packets never hit base interface's ip6tables) + // and later sum base and stacked with overhead (+20B/pkt) in higher layers + ("-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH), + COMMIT_AND_CLOSE}; + // clang-format on + return ipt_basic_accounting_commands; +} + +} // namespace + +BandwidthController::BandwidthController() { +} + +void BandwidthController::flushCleanTables(bool doClean) { + /* Flush and remove the bw_costly_ tables */ + flushExistingCostlyTables(doClean); + + std::string commands = Join(IPT_FLUSH_COMMANDS, '\n'); + iptablesRestoreFunction(V4V6, commands, nullptr); +} + +int BandwidthController::setupIptablesHooks() { + /* flush+clean is allowed to fail */ + flushCleanTables(true); + return 0; +} + +int BandwidthController::enableBandwidthControl() { + /* Let's pretend we started from scratch ... */ + mSharedQuotaIfaces.clear(); + mQuotaIfaces.clear(); + mGlobalAlertBytes = 0; + mSharedQuotaBytes = mSharedAlertBytes = 0; + + flushCleanTables(false); + + std::string commands = Join(getBasicAccountingCommands(), '\n'); + return iptablesRestoreFunction(V4V6, commands, nullptr); +} + +int BandwidthController::disableBandwidthControl() { + + flushCleanTables(false); + return 0; +} + +std::string BandwidthController::makeDataSaverCommand(IptablesTarget target, bool enable) { + std::string cmd; + const char *chainName = "bw_data_saver"; + const char *op = jumpToString(enable ? IptJumpReject : IptJumpReturn); + std::string criticalCommands = enable ? + FirewallController::makeCriticalCommands(target, chainName) : ""; + StringAppendF(&cmd, + "*filter\n" + ":%s -\n" + "%s" + "-A %s%s\n" + "COMMIT\n", chainName, criticalCommands.c_str(), chainName, op); + return cmd; +} + +int BandwidthController::enableDataSaver(bool enable) { + int ret = iptablesRestoreFunction(V4, makeDataSaverCommand(V4, enable), nullptr); + ret |= iptablesRestoreFunction(V6, makeDataSaverCommand(V6, enable), nullptr); + return ret; +} + +int BandwidthController::setInterfaceSharedQuota(const std::string& iface, int64_t maxBytes) { + int res = 0; + std::string quotaCmd; + constexpr char cost[] = "shared"; + constexpr char chain[] = "bw_costly_shared"; + + if (!maxBytes) { + /* Don't talk about -1, deprecate it. */ + ALOGE("Invalid bytes value. 1..max_int64."); + return -1; + } + if (!isIfaceName(iface)) + return -1; + + if (maxBytes == -1) { + return removeInterfaceSharedQuota(iface); + } + + auto it = mSharedQuotaIfaces.find(iface); + + if (it == mSharedQuotaIfaces.end()) { + const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1; + std::vector cmds = { + "*filter", + StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleInsertPos, iface.c_str(), chain), + StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleInsertPos, iface.c_str(), chain), + StringPrintf("-A bw_FORWARD -i %s -j %s", iface.c_str(), chain), + StringPrintf("-A bw_FORWARD -o %s -j %s", iface.c_str(), chain), + }; + if (mSharedQuotaIfaces.empty()) { + cmds.push_back(StringPrintf("-I %s -m quota2 ! --quota %" PRId64 " --name %s -j REJECT", + chain, maxBytes, cost)); + } + cmds.push_back("COMMIT\n"); + + res |= iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr); + if (res) { + ALOGE("Failed set quota rule"); + removeInterfaceSharedQuota(iface); + return -1; + } + mSharedQuotaBytes = maxBytes; + mSharedQuotaIfaces.insert(iface); + } + + if (maxBytes != mSharedQuotaBytes) { + res |= updateQuota(cost, maxBytes); + if (res) { + ALOGE("Failed update quota for %s", cost); + removeInterfaceSharedQuota(iface); + return -1; + } + mSharedQuotaBytes = maxBytes; + } + return 0; +} + +/* It will also cleanup any shared alerts */ +int BandwidthController::removeInterfaceSharedQuota(const std::string& iface) { + constexpr char cost[] = "shared"; + constexpr char chain[] = "bw_costly_shared"; + + if (!isIfaceName(iface)) + return -1; + + auto it = mSharedQuotaIfaces.find(iface); + + if (it == mSharedQuotaIfaces.end()) { + ALOGE("No such iface %s to delete", iface.c_str()); + return -1; + } + + std::vector cmds = { + "*filter", + StringPrintf("-D bw_INPUT -i %s -j %s", iface.c_str(), chain), + StringPrintf("-D bw_OUTPUT -o %s -j %s", iface.c_str(), chain), + StringPrintf("-D bw_FORWARD -i %s -j %s", iface.c_str(), chain), + StringPrintf("-D bw_FORWARD -o %s -j %s", iface.c_str(), chain), + }; + if (mSharedQuotaIfaces.size() == 1) { + cmds.push_back(StringPrintf("-D %s -m quota2 ! --quota %" PRIu64 " --name %s -j REJECT", + chain, mSharedQuotaBytes, cost)); + } + cmds.push_back("COMMIT\n"); + + if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) { + ALOGE("Failed to remove shared quota on %s", iface.c_str()); + return -1; + } + + int res = 0; + mSharedQuotaIfaces.erase(it); + if (mSharedQuotaIfaces.empty()) { + mSharedQuotaBytes = 0; + if (mSharedAlertBytes) { + res = removeSharedAlert(); + if (res == 0) { + mSharedAlertBytes = 0; + } + } + } + + return res; + +} + +int BandwidthController::setInterfaceQuota(const std::string& iface, int64_t maxBytes) { + const std::string& cost = iface; + + if (!isIfaceName(iface)) return -EINVAL; + + if (!maxBytes) { + ALOGE("Invalid bytes value. 1..max_int64."); + return -ERANGE; + } + if (maxBytes == -1) { + return removeInterfaceQuota(iface); + } + + /* Insert ingress quota. */ + auto it = mQuotaIfaces.find(iface); + + if (it != mQuotaIfaces.end()) { + if (int res = updateQuota(cost, maxBytes)) { + ALOGE("Failed update quota for %s", iface.c_str()); + removeInterfaceQuota(iface); + return res; + } + it->second.quota = maxBytes; + return 0; + } + + const std::string chain = "bw_costly_" + iface; + const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1; + std::vector cmds = { + "*filter", + StringPrintf(":%s -", chain.c_str()), + StringPrintf("-A %s -j bw_penalty_box", chain.c_str()), + StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleInsertPos, iface.c_str(), chain.c_str()), + StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleInsertPos, iface.c_str(), + chain.c_str()), + StringPrintf("-A bw_FORWARD -i %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-A bw_FORWARD -o %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-A %s -m quota2 ! --quota %" PRId64 " --name %s -j REJECT", chain.c_str(), + maxBytes, cost.c_str()), + "COMMIT\n", + }; + if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) { + ALOGE("Failed set quota rule"); + removeInterfaceQuota(iface); + return -EREMOTEIO; + } + + mQuotaIfaces[iface] = QuotaInfo{maxBytes, 0}; + return 0; +} + +int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) { + return getInterfaceQuota("shared", bytes); +} + +int BandwidthController::getInterfaceQuota(const std::string& iface, int64_t* bytes) { + const auto& sys = android::netdutils::sSyscalls.get(); + const std::string fname = "/proc/net/xt_quota/" + iface; + + if (!isIfaceName(iface)) return -1; + + StatusOr file = sys.fopen(fname, "re"); + if (!isOk(file)) { + ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(file).c_str()); + return -1; + } + auto rv = sys.fscanf(file.value().get(), "%" SCNd64, bytes); + if (!isOk(rv)) { + ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(rv).c_str()); + return -1; + } + ALOGV("Read quota res=%d bytes=%" PRId64, rv.value(), *bytes); + return rv.value() == 1 ? 0 : -1; +} + +int BandwidthController::removeInterfaceQuota(const std::string& iface) { + if (!isIfaceName(iface)) return -EINVAL; + + auto it = mQuotaIfaces.find(iface); + + if (it == mQuotaIfaces.end()) { + ALOGE("No such iface %s to delete", iface.c_str()); + return -ENODEV; + } + + const std::string chain = "bw_costly_" + iface; + std::vector cmds = { + "*filter", + StringPrintf("-D bw_INPUT -i %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-D bw_OUTPUT -o %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-D bw_FORWARD -i %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-D bw_FORWARD -o %s -j %s", iface.c_str(), chain.c_str()), + StringPrintf("-F %s", chain.c_str()), + StringPrintf("-X %s", chain.c_str()), + "COMMIT\n", + }; + + const int res = iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr); + + if (res == 0) { + mQuotaIfaces.erase(it); + } + + return res ? -EREMOTEIO : 0; +} + +int BandwidthController::updateQuota(const std::string& quotaName, int64_t bytes) { + const auto& sys = android::netdutils::sSyscalls.get(); + const std::string fname = "/proc/net/xt_quota/" + quotaName; + + if (!isIfaceName(quotaName)) { + ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName.c_str()); + return -EINVAL; + } + + StatusOr file = sys.fopen(fname, "we"); + if (!isOk(file)) { + int res = errno; + ALOGE("Updating quota %s failed (%s)", quotaName.c_str(), toString(file).c_str()); + res = 0; + return -res; + } + // TODO: should we propagate this error? + sys.fprintf(file.value().get(), "%" PRId64 "\n", bytes).ignoreError(); + return 0; +} + +int BandwidthController::runIptablesAlertCmd(IptOp op, const std::string& alertName, + int64_t bytes) { + const char *opFlag = opToString(op); + std::string alertQuotaCmd = "*filter\n"; + + // TODO: consider using an alternate template for the delete that does not include the --quota + // value. This code works because the --quota value is ignored by deletes + + /* + * Add alert rule in bw_global_alert chain, 3 chains might reference bw_global_alert. + * bw_INPUT, bw_OUTPUT (added by BandwidthController in enableBandwidthControl) + * bw_FORWARD (added by TetherController in setTetherGlobalAlertRule if nat enable/disable) + */ + StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, LOCAL_GLOBAL_ALERT, bytes, + alertName.c_str()); + StringAppendF(&alertQuotaCmd, "COMMIT\n"); + + return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr); +} + +int BandwidthController::setGlobalAlert(int64_t bytes) { + const char *alertName = ALERT_GLOBAL_NAME; + + if (!bytes) { + ALOGE("Invalid bytes value. 1..max_int64."); + return -ERANGE; + } + + int res = 0; + if (mGlobalAlertBytes) { + res = updateQuota(alertName, bytes); + } else { + res = runIptablesAlertCmd(IptOpInsert, alertName, bytes); + if (res) { + res = -EREMOTEIO; + } + } + mGlobalAlertBytes = bytes; + return res; +} + +int BandwidthController::removeGlobalAlert() { + + const char *alertName = ALERT_GLOBAL_NAME; + + if (!mGlobalAlertBytes) { + ALOGE("No prior alert set"); + return -1; + } + + int res = 0; + res = runIptablesAlertCmd(IptOpDelete, alertName, mGlobalAlertBytes); + mGlobalAlertBytes = 0; + return res; +} + +int BandwidthController::setSharedAlert(int64_t bytes) { + if (!mSharedQuotaBytes) { + ALOGE("Need to have a prior shared quota set to set an alert"); + return -1; + } + if (!bytes) { + ALOGE("Invalid bytes value. 1..max_int64."); + return -1; + } + return setCostlyAlert("shared", bytes, &mSharedAlertBytes); +} + +int BandwidthController::removeSharedAlert() { + return removeCostlyAlert("shared", &mSharedAlertBytes); +} + +int BandwidthController::setInterfaceAlert(const std::string& iface, int64_t bytes) { + if (!isIfaceName(iface)) { + ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface.c_str()); + return -EINVAL; + } + + if (!bytes) { + ALOGE("Invalid bytes value. 1..max_int64."); + return -ERANGE; + } + auto it = mQuotaIfaces.find(iface); + + if (it == mQuotaIfaces.end()) { + ALOGE("Need to have a prior interface quota set to set an alert"); + return -ENOENT; + } + + return setCostlyAlert(iface, bytes, &it->second.alert); +} + +int BandwidthController::removeInterfaceAlert(const std::string& iface) { + if (!isIfaceName(iface)) { + ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface.c_str()); + return -EINVAL; + } + + auto it = mQuotaIfaces.find(iface); + + if (it == mQuotaIfaces.end()) { + ALOGE("No prior alert set for interface %s", iface.c_str()); + return -ENOENT; + } + + return removeCostlyAlert(iface, &it->second.alert); +} + +int BandwidthController::setCostlyAlert(const std::string& costName, int64_t bytes, + int64_t* alertBytes) { + int res = 0; + + if (!isIfaceName(costName)) { + ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName.c_str()); + return -EINVAL; + } + + if (!bytes) { + ALOGE("Invalid bytes value. 1..max_int64."); + return -ERANGE; + } + + std::string alertName = costName + "Alert"; + std::string chainName = "bw_costly_" + costName; + if (*alertBytes) { + res = updateQuota(alertName, *alertBytes); + } else { + std::vector commands = { + "*filter\n", + StringPrintf(ALERT_IPT_TEMPLATE, "-A", chainName.c_str(), bytes, alertName.c_str()), + "COMMIT\n" + }; + res = iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr); + if (res) { + ALOGE("Failed to set costly alert for %s", costName.c_str()); + res = -EREMOTEIO; + } + } + if (res == 0) { + *alertBytes = bytes; + } + return res; +} + +int BandwidthController::removeCostlyAlert(const std::string& costName, int64_t* alertBytes) { + if (!isIfaceName(costName)) { + ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName.c_str()); + return -EINVAL; + } + + if (!*alertBytes) { + ALOGE("No prior alert set for %s alert", costName.c_str()); + return -ENOENT; + } + + std::string alertName = costName + "Alert"; + std::string chainName = "bw_costly_" + costName; + std::vector commands = { + "*filter\n", + StringPrintf(ALERT_IPT_TEMPLATE, "-D", chainName.c_str(), *alertBytes, alertName.c_str()), + "COMMIT\n" + }; + if (iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr) != 0) { + ALOGE("Failed to remove costly alert %s", costName.c_str()); + return -EREMOTEIO; + } + + *alertBytes = 0; + return 0; +} + +void BandwidthController::flushExistingCostlyTables(bool doClean) { + std::string fullCmd = "*filter\n-S\nCOMMIT\n"; + std::string ruleList; + + /* Only lookup ip4 table names as ip6 will have the same tables ... */ + if (int ret = iptablesRestoreFunction(V4, fullCmd, &ruleList)) { + ALOGE("Failed to list existing costly tables ret=%d", ret); + return; + } + /* ... then flush/clean both ip4 and ip6 iptables. */ + parseAndFlushCostlyTables(ruleList, doClean); +} + +void BandwidthController::parseAndFlushCostlyTables(const std::string& ruleList, bool doRemove) { + std::stringstream stream(ruleList); + std::string rule; + std::vector clearCommands = { "*filter" }; + std::string chainName; + + // Find and flush all rules starting with "-N bw_costly_" except "-N bw_costly_shared". + while (std::getline(stream, rule, '\n')) { + if (!StartsWith(rule, NEW_CHAIN_COMMAND)) continue; + chainName = rule.substr(NEW_CHAIN_COMMAND.size()); + ALOGV("parse chainName=<%s> orig line=<%s>", chainName.c_str(), rule.c_str()); + + if (!StartsWith(chainName, "bw_costly_") || chainName == std::string("bw_costly_shared")) { + continue; + } + + clearCommands.push_back(StringPrintf(":%s -", chainName.c_str())); + if (doRemove) { + clearCommands.push_back(StringPrintf("-X %s", chainName.c_str())); + } + } + + if (clearCommands.size() == 1) { + // No rules found. + return; + } + + clearCommands.push_back("COMMIT\n"); + iptablesRestoreFunction(V4V6, Join(clearCommands, '\n'), nullptr); +} + +inline const char *BandwidthController::opToString(IptOp op) { + switch (op) { + case IptOpInsert: + return "-I"; + case IptOpDelete: + return "-D"; + } +} + +inline const char *BandwidthController::jumpToString(IptJumpOp jumpHandling) { + /* + * Must be careful what one rejects with, as upper layer protocols will just + * keep on hammering the device until the number of retries are done. + * For port-unreachable (default), TCP should consider as an abort (RFC1122). + */ + switch (jumpHandling) { + case IptJumpReject: + return " -j REJECT"; + case IptJumpReturn: + return " -j RETURN"; + } +} diff --git a/aosp/system/netd/server/Controllers.cpp b/aosp/system/netd/server/Controllers.cpp new file mode 100644 index 000000000..9595c1fae --- /dev/null +++ b/aosp/system/netd/server/Controllers.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2016 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define LOG_TAG "Netd" +#include + +#include "ConnmarkFlags.h" +#include "Controllers.h" +#include "IdletimerController.h" +#include "NetworkController.h" +#include "RouteController.h" +#include "XfrmController.h" +#include "oem_iptables_hook.h" + +namespace android { +namespace net { + +using android::base::Join; +using android::base::StringAppendF; +using android::base::StringPrintf; +using android::netdutils::Stopwatch; + +auto Controllers::execIptablesRestore = ::execIptablesRestore; +auto Controllers::execIptablesRestoreWithOutput = ::execIptablesRestoreWithOutput; + +netdutils::Log gLog("netd"); +netdutils::Log gUnsolicitedLog("netdUnsolicited"); + +namespace { + +static constexpr char CONNMARK_MANGLE_INPUT[] = "connmark_mangle_INPUT"; +static constexpr char CONNMARK_MANGLE_OUTPUT[] = "connmark_mangle_OUTPUT"; + +/** + * List of module chains to be created, along with explicit ordering. ORDERING + * IS CRITICAL, AND SHOULD BE TRIPLE-CHECKED WITH EACH CHANGE. + */ +static const std::vector FILTER_INPUT = { + // Bandwidth should always be early in input chain, to make sure we + // correctly count incoming traffic against data plan. + BandwidthController::LOCAL_INPUT, + FirewallController::LOCAL_INPUT, +}; + +static const std::vector FILTER_FORWARD = { + OEM_IPTABLES_FILTER_FORWARD, + FirewallController::LOCAL_FORWARD, + BandwidthController::LOCAL_FORWARD, + TetherController::LOCAL_FORWARD, +}; + +static const std::vector FILTER_OUTPUT = { + OEM_IPTABLES_FILTER_OUTPUT, + FirewallController::LOCAL_OUTPUT, + StrictController::LOCAL_OUTPUT, + BandwidthController::LOCAL_OUTPUT, +}; + +static const std::vector RAW_PREROUTING = { + IdletimerController::LOCAL_RAW_PREROUTING, + BandwidthController::LOCAL_RAW_PREROUTING, + TetherController::LOCAL_RAW_PREROUTING, +}; + +static const std::vector MANGLE_POSTROUTING = { + OEM_IPTABLES_MANGLE_POSTROUTING, + BandwidthController::LOCAL_MANGLE_POSTROUTING, + IdletimerController::LOCAL_MANGLE_POSTROUTING, +}; + +static const std::vector MANGLE_INPUT = { + CONNMARK_MANGLE_INPUT, + WakeupController::LOCAL_MANGLE_INPUT, + RouteController::LOCAL_MANGLE_INPUT, +}; + +static const std::vector MANGLE_FORWARD = { + TetherController::LOCAL_MANGLE_FORWARD, +}; + +static const std::vector MANGLE_OUTPUT = { + CONNMARK_MANGLE_OUTPUT, +}; + +static const std::vector NAT_PREROUTING = { + OEM_IPTABLES_NAT_PREROUTING, +}; + +static const std::vector NAT_POSTROUTING = { + TetherController::LOCAL_NAT_POSTROUTING, +}; + +// Commands to create child chains and to match created chains in iptables -S output. Keep in sync. +static const char* CHILD_CHAIN_TEMPLATE = "-A %s -j %s\n"; +static const std::regex CHILD_CHAIN_REGEX("^-A ([^ ]+) -j ([^ ]+)$", + std::regex_constants::extended); + +} // namespace + +/* static */ +std::set Controllers::findExistingChildChains(const IptablesTarget target, + const char* table, + const char* parentChain) { + if (target == V4V6) { + ALOGE("findExistingChildChains only supports one protocol at a time"); + abort(); + } + + std::set existing; + + // List the current contents of parentChain. + // + // TODO: there is no guarantee that nothing else modifies the chain in the few milliseconds + // between when we list the existing rules and when we delete them. However: + // - Since this code is only run on startup, nothing else in netd will be running. + // - While vendor code is known to add its own rules to chains created by netd, it should never + // be modifying the rules in childChains or the rules that hook said chains into their parent + // chains. + std::string command = StringPrintf("*%s\n-S %s\nCOMMIT\n", table, parentChain); + std::string output; + if (Controllers::execIptablesRestoreWithOutput(target, command, &output) == -1) { + ALOGE("Error listing chain %s in table %s", parentChain, table); + return existing; + } + + // The only rules added by createChildChains are of the simple form "-A -j ". + // Find those rules and add each one's child chain to existing. + std::smatch matches; + std::stringstream stream(output); + std::string rule; + while (std::getline(stream, rule, '\n')) { + if (std::regex_search(rule, matches, CHILD_CHAIN_REGEX) && matches[1] == parentChain) { + existing.insert(matches[2]); + } + } + + return existing; +} + +/* static */ +void Controllers::createChildChains(IptablesTarget target, const char* table, + const char* parentChain, + const std::vector& childChains, + bool exclusive) { + std::string command = StringPrintf("*%s\n", table); + + // We cannot just clear all the chains we create because vendor code modifies filter OUTPUT and + // mangle POSTROUTING directly. So: + // + // - If we're the exclusive owner of this chain, simply clear it entirely. + // - If not, then list the chain's current contents to ensure that if we restart after a crash, + // we leave the existing rules alone in the positions they currently occupy. This is faster + // than blindly deleting our rules and recreating them, because deleting a rule that doesn't + // exists causes iptables-restore to quit, which takes ~30ms per delete. It's also more + // correct, because if we delete rules and re-add them, they'll be in the wrong position with + // regards to the vendor rules. + // + // TODO: Make all chains exclusive once vendor code uses the oem_* rules. + std::set existingChildChains; + if (exclusive) { + // Just running ":chain -" flushes user-defined chains, but not built-in chains like INPUT. + // Since at this point we don't know if parentChain is a built-in chain, do both. + StringAppendF(&command, ":%s -\n", parentChain); + StringAppendF(&command, "-F %s\n", parentChain); + } else { + existingChildChains = findExistingChildChains(target, table, parentChain); + } + + for (const auto& childChain : childChains) { + // Always clear the child chain. + StringAppendF(&command, ":%s -\n", childChain); + // But only add it to the parent chain if it's not already there. + if (existingChildChains.find(childChain) == existingChildChains.end()) { + StringAppendF(&command, CHILD_CHAIN_TEMPLATE, parentChain, childChain); + } + } + command += "COMMIT\n"; + execIptablesRestore(target, command); +} + +Controllers::Controllers() + : wakeupCtrl( + [this](const WakeupController::ReportArgs& args) { + const auto listener = eventReporter.getNetdEventListener(); + if (listener == nullptr) { + gLog.error("getNetdEventListener() returned nullptr. dropping wakeup event"); + return; + } + String16 prefix = String16(args.prefix.c_str()); + String16 srcIp = String16(args.srcIp.c_str()); + String16 dstIp = String16(args.dstIp.c_str()); + listener->onWakeupEvent(prefix, args.uid, args.ethertype, args.ipNextHeader, + args.dstHw, srcIp, dstIp, args.srcPort, args.dstPort, + args.timestampNs); + }, + &iptablesRestoreCtrl) { + InterfaceController::initializeAll(); +} + +void Controllers::initChildChains() { + /* + * This is the only time we touch top-level chains in iptables; controllers + * should only mutate rules inside of their children chains, as created by + * the constants above. + * + * Modules should never ACCEPT packets (except in well-justified cases); + * they should instead defer to any remaining modules using RETURN, or + * otherwise DROP/REJECT. + */ + + // Create chains for child modules. + createChildChains(V4V6, "filter", "INPUT", FILTER_INPUT, true); + createChildChains(V4V6, "filter", "FORWARD", FILTER_FORWARD, true); + createChildChains(V4V6, "raw", "PREROUTING", RAW_PREROUTING, true); + createChildChains(V4V6, "mangle", "FORWARD", MANGLE_FORWARD, true); + createChildChains(V4V6, "mangle", "INPUT", MANGLE_INPUT, true); + createChildChains(V4V6, "mangle", "OUTPUT", MANGLE_OUTPUT, true); + createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING, true); + createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING, true); + + createChildChains(V4, "filter", "OUTPUT", FILTER_OUTPUT, false); + createChildChains(V6, "filter", "OUTPUT", FILTER_OUTPUT, false); + createChildChains(V4, "mangle", "POSTROUTING", MANGLE_POSTROUTING, false); + createChildChains(V6, "mangle", "POSTROUTING", MANGLE_POSTROUTING, false); +} + +static void setupConnmarkIptablesHooks() { + // Create NFMASK alias to prevent further line breaks. + constexpr unsigned NFMASK = CONNMARK_FWMARK_MASK; // 0x000FFFFF; + + // Rules to store parts of the fwmark (namely: netId, explicitlySelected, protectedFromVpn, + // permission) in connmark. + // Only saves the mark if no mark has been set before. + const std::vector cmd = { + "*mangle", + StringPrintf("-A %s -m connmark --mark 0/0x%x " + "-j CONNMARK --save-mark --ctmask 0x%x --nfmask 0x%x", + CONNMARK_MANGLE_INPUT, NFMASK, ~NFMASK, NFMASK), + StringPrintf("-A %s -m connmark --mark 0/0x%x " + "-j CONNMARK --save-mark --ctmask 0x%x --nfmask 0x%x", + CONNMARK_MANGLE_OUTPUT, NFMASK, ~NFMASK, NFMASK), + "COMMIT\n", + }; + execIptablesRestore(V4V6, Join(cmd, '\n')); +} + +void Controllers::initIptablesRules() { + if (true) { + return; + } + + Stopwatch s; + initChildChains(); + gLog.info("Creating child chains: %" PRId64 "us", s.getTimeAndResetUs()); + + // Let each module setup their child chains + setupOemIptablesHook(); + gLog.info("Setting up OEM hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* When enabled, DROPs all packets except those matching rules. */ + firewallCtrl.setupIptablesHooks(); + gLog.info("Setting up FirewallController hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* Does DROPs in FORWARD by default */ + tetherCtrl.setupIptablesHooks(); + gLog.info("Setting up TetherController hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* + * Does REJECT in INPUT, OUTPUT. Does counting also. + * No DROP/REJECT allowed later in netfilter-flow hook order. + */ + bandwidthCtrl.setupIptablesHooks(); + gLog.info("Setting up BandwidthController hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* + * Counts in nat: PREROUTING, POSTROUTING. + * No DROP/REJECT allowed later in netfilter-flow hook order. + */ + idletimerCtrl.setupIptablesHooks(); + gLog.info("Setting up IdletimerController hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* + * Add rules for detecting IPv6/IPv4 TCP/UDP connections with TLS/DTLS header + */ + strictCtrl.setupIptablesHooks(); + gLog.info("Setting up StrictController hooks: %" PRId64 "us", s.getTimeAndResetUs()); + + /* + * Add rules for storing netid in connmark. + */ + setupConnmarkIptablesHooks(); + gLog.info("Setting up connmark hooks: %" PRId64 "us", s.getTimeAndResetUs()); +} + +void Controllers::init() { + initIptablesRules(); + Stopwatch s; + +#if 0 + if (int ret = bandwidthCtrl.enableBandwidthControl()) { + gLog.error("Failed to initialize BandwidthController (%s)", strerror(-ret)); + // A failure to init almost definitely means that iptables failed to load + // our static ruleset, which then basically means network accounting will not work. + // As such simply exit netd. This may crash loop the system, but by failing + // to bootup we will trigger rollback and thus this offers us protection against + // a mainline update breaking things. + exit(1); + } + gLog.info("Enabling bandwidth control: %" PRId64 "us", s.getTimeAndResetUs()); +#endif + + if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) { + gLog.error("Failed to initialize RouteController (%s)", strerror(-ret)); + } + gLog.info("Initializing RouteController: %" PRId64 "us", s.getTimeAndResetUs()); + + netdutils::Status xStatus = XfrmController::Init(); + if (!isOk(xStatus)) { + gLog.error("Failed to initialize XfrmController (%s)", netdutils::toString(xStatus).c_str()); + }; + gLog.info("Initializing XfrmController: %" PRId64 "us", s.getTimeAndResetUs()); +} + +Controllers* gCtls = nullptr; + +} // namespace net +} // namespace android diff --git a/aosp/system/netd/server/FwmarkServer.cpp b/aosp/system/netd/server/FwmarkServer.cpp new file mode 100644 index 000000000..5a664db24 --- /dev/null +++ b/aosp/system/netd/server/FwmarkServer.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "FwmarkServer.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include // NETID_UNSET + +#include "Fwmark.h" +#include "FwmarkCommand.h" +#include "NetdConstants.h" +#include "NetworkController.h" + +#include "NetdUpdatablePublic.h" + +using android::base::ReceiveFileDescriptorVector; +using android::base::unique_fd; +using android::net::metrics::INetdEventListener; + +namespace android { +namespace net { + +FwmarkServer::FwmarkServer(NetworkController* networkController, EventReporter* eventReporter) + : SocketListener(SOCKET_NAME, true), + mNetworkController(networkController), + mEventReporter(eventReporter) {} + +bool FwmarkServer::onDataAvailable(SocketClient* client) { + int socketFd = -1; + int error = processClient(client, &socketFd); + if (socketFd >= 0) { + close(socketFd); + } + + // Always send a response even if there were connection errors or read errors, so that we don't + // inadvertently cause the client to hang (which always waits for a response). + client->sendData(&error, sizeof(error)); + + // Always close the client connection (by returning false). This prevents a DoS attack where + // the client issues multiple commands on the same connection, never reading the responses, + // causing its receive buffer to fill up, and thus causing our client->sendData() to block. + return false; +} + +static bool hasDestinationAddress(FwmarkCommand::CmdId cmdId) { + switch (cmdId) { + case FwmarkCommand::ON_CONNECT: + case FwmarkCommand::ON_CONNECT_COMPLETE: + case FwmarkCommand::ON_SENDMSG: + case FwmarkCommand::ON_SENDMMSG: + case FwmarkCommand::ON_SENDTO: + return true; + default: + return false; + } +} + +int FwmarkServer::processClient(SocketClient* client, int* socketFd) { + struct { + FwmarkCommand command; + FwmarkConnectInfo connectInfo; + } buf; + + // make sure there is no spurious padding + static_assert(sizeof(buf) == sizeof(buf.command) + sizeof(buf.connectInfo)); + + std::vector received_fds; + ssize_t messageLength = + ReceiveFileDescriptorVector(client->getSocket(), &buf, sizeof(buf), 1, &received_fds); + + if (messageLength < 0) { + return -errno; + } else if (messageLength == 0) { + return -ESHUTDOWN; + } + + const FwmarkCommand &command = buf.command; + const FwmarkConnectInfo &connectInfo = buf.connectInfo; + + size_t expectedLen = sizeof(command); + if (hasDestinationAddress(command.cmdId)) { + expectedLen += sizeof(connectInfo); + } + + if (messageLength != static_cast(expectedLen)) { + return -EBADMSG; + } + + Permission permission = mNetworkController->getPermissionForUser(client->getUid()); + + if (command.cmdId == FwmarkCommand::QUERY_USER_ACCESS) { + if ((permission & PERMISSION_SYSTEM) != PERMISSION_SYSTEM) { + return -EPERM; + } + return mNetworkController->checkUserNetworkAccess(command.uid, command.netId); + } + + if (received_fds.size() != 1) { + LOG(ERROR) << "FwmarkServer received " << received_fds.size() << " fds from client?"; + return -EBADF; + } else if (received_fds[0] < 0) { + LOG(ERROR) << "FwmarkServer received fd -1 from ReceiveFileDescriptorVector?"; + return -EBADF; + } + + *socketFd = received_fds[0].release(); + + int family; + socklen_t familyLen = sizeof(family); + if (getsockopt(*socketFd, SOL_SOCKET, SO_DOMAIN, &family, &familyLen) == -1) { + return -errno; + } + if (!FwmarkCommand::isSupportedFamily(family)) { + return -EAFNOSUPPORT; + } + + Fwmark fwmark; + socklen_t fwmarkLen = sizeof(fwmark.intValue); + if (getsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) { + return -errno; + } + + switch (command.cmdId) { + case FwmarkCommand::ON_ACCEPT: { + // Called after a socket accept(). The kernel would've marked the NetId and necessary + // permissions bits, so we just add the rest of the user's permissions here. + permission = static_cast(permission | fwmark.permission); + break; + } + + case FwmarkCommand::ON_CONNECT: { + // Called before a socket connect() happens. Set an appropriate NetId into the fwmark so + // that the socket routes consistently over that network. Do this even if the socket + // already has a NetId, so that calling connect() multiple times still works. + // + // But if the explicit bit was set, the existing NetId was explicitly preferred (and not + // a case of connect() being called multiple times). Don't reset the NetId in that case. + // + // An "appropriate" NetId is the NetId of a bypassable VPN that applies to the user, or + // failing that, the default network. We'll never set the NetId of a secure VPN here. + // See the comments in the implementation of getNetworkForConnect() for more details. + // + // If the protect bit is set, this could be either a system proxy (e.g.: the dns proxy + // or the download manager) acting on behalf of another user, or a VPN provider. If it's + // a proxy, we shouldn't reset the NetId. If it's a VPN provider, we should set the + // default network's NetId. + // + // There's no easy way to tell the difference between a proxy and a VPN app. We can't + // use PERMISSION_SYSTEM to identify the proxy because a VPN app may also have those + // permissions. So we use the following heuristic: + // + // If it's a proxy, but the existing NetId is not a VPN, that means the user (that the + // proxy is acting on behalf of) is not subject to a VPN, so the proxy must have picked + // the default network's NetId. So, it's okay to replace that with the current default + // network's NetId (which in all likelihood is the same). + // + // Conversely, if it's a VPN provider, the existing NetId cannot be a VPN. The only time + // we set a VPN's NetId into a socket without setting the explicit bit is here, in + // ON_CONNECT, but we won't do that if the socket has the protect bit set. + // If the VPN provider connect()ed (and got the VPN NetId set) and then called + // protect(), we would've unset the NetId in PROTECT_FROM_VPN below. + // + // So, overall (when the explicit bit is not set but the protect bit is set), if the + // existing NetId is a VPN, don't reset it. Else, set the default network's NetId. + if (!fwmark.explicitlySelected) { + if (family == AF_INET6 && connectInfo.addr.sin6.sin6_scope_id && + IN6_IS_ADDR_LINKLOCAL(&connectInfo.addr.sin6.sin6_addr)) { + fwmark.netId = mNetworkController->getNetworkForInterface( + connectInfo.addr.sin6.sin6_scope_id); + } else if (!fwmark.protectedFromVpn) { + fwmark.netId = mNetworkController->getNetworkForConnect(client->getUid()); + } else if (!mNetworkController->isVirtualNetwork(fwmark.netId)) { + fwmark.netId = mNetworkController->getDefaultNetwork(); + } + } + break; + } + + case FwmarkCommand::ON_CONNECT_COMPLETE: { + // Called after a socket connect() completes. + // This reports connect event including netId, destination IP address, destination port, + // uid, connect latency, and connect errno if any. + + // Skip reporting if connect() happened on a UDP socket. + int socketProto; + socklen_t intSize = sizeof(socketProto); + const int ret = getsockopt(*socketFd, SOL_SOCKET, SO_PROTOCOL, &socketProto, &intSize); + if ((ret != 0) || (socketProto == IPPROTO_UDP)) { + break; + } + + android::sp netdEventListener = + mEventReporter->getNetdEventListener(); + + if (netdEventListener != nullptr) { + char addrstr[INET6_ADDRSTRLEN + IFNAMSIZ]; // ipv6 address + optional %scope + char portstr[sizeof("65535")]; + static_assert(sizeof(addrstr) >= 62); + static_assert(sizeof(portstr) >= 6); + const int ret = getnameinfo(&connectInfo.addr.s, sizeof(connectInfo.addr.s), + addrstr, sizeof(addrstr), portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV); + + netdEventListener->onConnectEvent(fwmark.netId, connectInfo.error, + connectInfo.latencyMs, + (ret == 0) ? String16(addrstr) : String16(""), + (ret == 0) ? strtoul(portstr, nullptr, 10) : 0, client->getUid()); + } + break; + } + + case FwmarkCommand::ON_SENDMMSG: + case FwmarkCommand::ON_SENDMSG: + case FwmarkCommand::ON_SENDTO: { + return 0; + } + + case FwmarkCommand::SELECT_NETWORK: { + fwmark.netId = command.netId; + if (command.netId == NETID_UNSET) { + fwmark.explicitlySelected = false; + fwmark.protectedFromVpn = false; + permission = PERMISSION_NONE; + } else { + if (int ret = mNetworkController->checkUserNetworkAccess(client->getUid(), + command.netId)) { + return ret; + } + fwmark.explicitlySelected = true; + fwmark.protectedFromVpn = mNetworkController->canProtect(client->getUid()); + } + break; + } + + case FwmarkCommand::PROTECT_FROM_VPN: { + if (!mNetworkController->canProtect(client->getUid())) { + LOG(ERROR) << "uid " << client->getUid() << " protect from VPN failed."; + return -EPERM; + } + // If a bypassable VPN's provider app calls connect() and then protect(), it will end up + // with a socket that looks like that of a system proxy but is not (see comments for + // ON_CONNECT above). So, reset the NetId. + // + // In any case, it's appropriate that if the socket has an implicit VPN NetId mark, the + // PROTECT_FROM_VPN command should unset it. + if (!fwmark.explicitlySelected && mNetworkController->isVirtualNetwork(fwmark.netId)) { + fwmark.netId = mNetworkController->getDefaultNetwork(); + } + fwmark.protectedFromVpn = true; + permission = static_cast(permission | fwmark.permission); + break; + } + + case FwmarkCommand::SELECT_FOR_USER: { + if ((permission & PERMISSION_SYSTEM) != PERMISSION_SYSTEM) { + return -EPERM; + } + fwmark.netId = mNetworkController->getNetworkForUser(command.uid); + fwmark.protectedFromVpn = true; + break; + } + + case FwmarkCommand::TAG_SOCKET: { + // If the UID is -1, tag as the caller's UID: + // - TrafficStats and NetworkManagementSocketTagger use -1 to indicate "use the + // caller's UID". + // - xt_qtaguid will see -1 on the command line, fail to parse it as a uint32_t, + // and fall back to current_fsuid(). + uid_t tagUid = command.uid; + if (static_cast(tagUid) == -1) tagUid = client->getUid(); + return libnetd_updatable_tagSocket(*socketFd, command.trafficCtrlInfo, tagUid, + client->getUid()); + } + + case FwmarkCommand::UNTAG_SOCKET: { + // Any process can untag a socket it has an fd for. + return libnetd_updatable_untagSocket(*socketFd); + } + + default: { + // unknown command + return -EPROTO; + } + } + + fwmark.permission = permission; + +#if 0 + if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, + sizeof(fwmark.intValue)) == -1) { + return -errno; + } +#endif + + return 0; +} + +} // namespace net +} // namespace android diff --git a/aosp/system/netd/server/InterfaceController.cpp b/aosp/system/netd/server/InterfaceController.cpp new file mode 100644 index 000000000..0cd2cd686 --- /dev/null +++ b/aosp/system/netd/server/InterfaceController.cpp @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2012 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. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define LOG_TAG "InterfaceController" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "InterfaceController.h" +#include "RouteController.h" + +using android::base::ReadFileToString; +using android::base::StringPrintf; +using android::base::Trim; +using android::base::WriteStringToFile; +using android::netdutils::isOk; +using android::netdutils::makeSlice; +using android::netdutils::sSyscalls; +using android::netdutils::Status; +using android::netdutils::statusFromErrno; +using android::netdutils::StatusOr; +using android::netdutils::toString; +using android::netdutils::status::ok; + +namespace { + +const char ipv4_proc_path[] = "/proc/sys/net/ipv4/conf"; +const char ipv6_proc_path[] = "/proc/sys/net/ipv6/conf"; + +const char ipv4_neigh_conf_dir[] = "/proc/sys/net/ipv4/neigh"; +const char ipv6_neigh_conf_dir[] = "/proc/sys/net/ipv6/neigh"; + +const char proc_net_path[] = "/proc/sys/net"; +const char sys_net_path[] = "/sys/class/net"; + +constexpr int kRouteInfoMinPrefixLen = 48; + +// RFC 7421 prefix length. +constexpr int kRouteInfoMaxPrefixLen = 64; + +// Property used to persist RFC 7217 stable secret. Protected by SELinux policy. +const char kStableSecretProperty[] = "persist.netd.stable_secret"; + +// RFC 7217 stable secret on linux is formatted as an IPv6 address. +// This function uses 128 bits of high quality entropy to generate an +// address for this purpose. This function should be not be called +// frequently. +StatusOr randomIPv6Address() { + in6_addr addr = {}; + const auto& sys = sSyscalls.get(); + ASSIGN_OR_RETURN(auto fd, sys.open("/dev/random", O_RDONLY)); + RETURN_IF_NOT_OK(sys.read(fd, makeSlice(addr))); + return toString(addr); +} + +inline bool isNormalPathComponent(const char *component) { + return (strcmp(component, ".") != 0) && + (strcmp(component, "..") != 0) && + (strchr(component, '/') == nullptr); +} + +inline bool isAddressFamilyPathComponent(const char *component) { + return strcmp(component, "ipv4") == 0 || strcmp(component, "ipv6") == 0; +} + +inline bool isInterfaceName(const char *name) { + return isNormalPathComponent(name) && + (strcmp(name, "default") != 0) && + (strcmp(name, "all") != 0); +} + +int writeValueToPath( + const char* dirname, const char* subdirname, const char* basename, + const char* value) { + std::string path(StringPrintf("%s/%s/%s", dirname, subdirname, basename)); + // return WriteStringToFile(value, path) ? 0 : -EREMOTEIO; + ALOGV("%s, %s", path.c_str(), value); + return 0; +} + +// Run @fn on each interface as well as 'default' in the path @dirname. +void forEachInterface( + const std::string& dirname, + const std::function& fn) { + // Run on default, which controls the behavior of any interfaces that are created in the future. + fn(dirname, "default"); + DIR* dir = opendir(dirname.c_str()); + if (!dir) { + ALOGE("Can't list %s: %s", dirname.c_str(), strerror(errno)); + return; + } + while (true) { + const dirent *ent = readdir(dir); + if (!ent) { + break; + } + if ((ent->d_type != DT_DIR) || !isInterfaceName(ent->d_name)) { + continue; + } + fn(dirname, ent->d_name); + } + closedir(dir); +} + +void setOnAllInterfaces(const char* dirname, const char* basename, const char* value) { + auto fn = [basename, value](const std::string& path, const std::string& iface) { + writeValueToPath(path.c_str(), iface.c_str(), basename, value); + }; + forEachInterface(dirname, fn); +} + +void setIPv6UseOutgoingInterfaceAddrsOnly(const char *value) { + setOnAllInterfaces(ipv6_proc_path, "use_oif_addrs_only", value); +} + +std::string getParameterPathname( + const char *family, const char *which, const char *interface, const char *parameter) { + if (!isAddressFamilyPathComponent(family)) { + errno = EAFNOSUPPORT; + return ""; + } else if (!isNormalPathComponent(which) || + !isInterfaceName(interface) || + !isNormalPathComponent(parameter)) { + errno = EINVAL; + return ""; + } + + return StringPrintf("%s/%s/%s/%s/%s", proc_net_path, family, which, interface, parameter); +} + +void setAcceptIPv6RIO(int min, int max) { + auto fn = [min, max](const std::string& prefix, const std::string& iface) { + int rv = writeValueToPath(prefix.c_str(), iface.c_str(), "accept_ra_rt_info_min_plen", + std::to_string(min).c_str()); + if (rv != 0) { + // Only update max_plen if the write to min_plen succeeded. This ordering will prevent + // RIOs from being accepted unless both min and max are written successfully. + return; + } + writeValueToPath(prefix.c_str(), iface.c_str(), "accept_ra_rt_info_max_plen", + std::to_string(max).c_str()); + }; + forEachInterface(ipv6_proc_path, fn); +} + +#if 0 +// Ideally this function would return StatusOr, however +// there is no safe value for dflt that will always differ from the +// stored property. Bugs code could conceivably end up persisting the +// reserved value resulting in surprising behavior. +std::string getProperty(const std::string& key, const std::string& dflt) { + return android::base::GetProperty(key, dflt); +}; + +Status setProperty(const std::string& key, const std::string& val) { + // SetProperty does not dependably set errno to a meaningful value. Use our own error code so + // callers don't get confused. + return android::base::SetProperty(key, val) + ? ok + : statusFromErrno(EREMOTEIO, "SetProperty failed, see libc logs"); +}; +#endif + +} // namespace + +namespace android { +namespace net { +std::mutex InterfaceController::mutex; + +android::netdutils::Status InterfaceController::enableStablePrivacyAddresses( + const std::string& iface, + const GetPropertyFn& getProperty, + const SetPropertyFn& setProperty) { + const auto& sys = sSyscalls.get(); + const std::string procTarget = std::string(ipv6_proc_path) + "/" + iface + "/stable_secret"; + auto procFd = sys.open(procTarget, O_CLOEXEC | O_WRONLY); + + // Devices with old kernels (typically < 4.4) don't support + // RFC 7217 stable privacy addresses. + if (equalToErrno(procFd, ENOENT)) { + return statusFromErrno(EOPNOTSUPP, + "Failed to open stable_secret. Assuming unsupported kernel version"); + } + + // If stable_secret exists but we can't open it, something strange is going on. + RETURN_IF_NOT_OK(procFd); + + const char kUninitialized[] = "uninitialized"; + const auto oldSecret = getProperty(kStableSecretProperty, kUninitialized); + std::string secret = oldSecret; + + // Generate a new secret if no persistent property existed. + if (oldSecret == kUninitialized) { + ASSIGN_OR_RETURN(secret, randomIPv6Address()); + } + + // Ask the OS to generate SLAAC addresses on iface using secret. + RETURN_IF_NOT_OK(sys.write(procFd.value(), makeSlice(secret))); + + // Don't persist an existing secret. + if (oldSecret != kUninitialized) { + return ok; + } + + return setProperty(kStableSecretProperty, secret); +} + +void InterfaceController::initializeAll() { + // Initial IPv6 settings. + // By default, accept_ra is set to 1 (accept RAs unless forwarding is on) on all interfaces. + // This causes RAs to work or not work based on whether forwarding is on, and causes routes + // learned from RAs to go away when forwarding is turned on. Make this behaviour predictable + // by always setting accept_ra to 2. + setAcceptRA("2"); + + // Accept RIOs with prefix length in the closed interval [48, 64]. + setAcceptIPv6RIO(kRouteInfoMinPrefixLen, kRouteInfoMaxPrefixLen); + + setAcceptRARouteTable(-RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX); + + // Enable optimistic DAD for IPv6 addresses on all interfaces. + setIPv6OptimisticMode("1"); + + // Reduce the ARP/ND base reachable time from the default (30sec) to 15sec. + setBaseReachableTimeMs(15 * 1000); + + // When sending traffic via a given interface use only addresses configured + // on that interface as possible source addresses. + setIPv6UseOutgoingInterfaceAddrsOnly("1"); + + // Ensure that ICMP redirects are rejected globally on all interfaces. + disableIcmpRedirects(); +} + +int InterfaceController::setEnableIPv6(const char *interface, const int on) { + if (!isIfaceName(interface)) { + return -ENOENT; + } + // When disable_ipv6 changes from 1 to 0, the kernel starts autoconf. + // When disable_ipv6 changes from 0 to 1, the kernel clears all autoconf + // addresses and routes and disables IPv6 on the interface. + const char *disable_ipv6 = on ? "0" : "1"; + return writeValueToPath(ipv6_proc_path, interface, "disable_ipv6", disable_ipv6); +} + +// Changes to addrGenMode will not fully take effect until the next +// time disable_ipv6 transitions from 1 to 0. +Status InterfaceController::setIPv6AddrGenMode(const std::string& interface, int mode) { + if (!isIfaceName(interface)) { + return statusFromErrno(ENOENT, "invalid iface name: " + interface); + } + + switch (mode) { + case INetd::IPV6_ADDR_GEN_MODE_EUI64: + // Ignore return value. If /proc/.../addr_gen_mode is + // missing we're probably in EUI64 mode already. + writeValueToPath(ipv6_proc_path, interface.c_str(), "addr_gen_mode", "0"); + break; + case INetd::IPV6_ADDR_GEN_MODE_STABLE_PRIVACY: { + // return enableStablePrivacyAddresses(interface, getProperty, setProperty); + return ok; + } + case INetd::IPV6_ADDR_GEN_MODE_NONE: + case INetd::IPV6_ADDR_GEN_MODE_RANDOM: + default: + return statusFromErrno(EOPNOTSUPP, "unsupported addrGenMode"); + } + + return ok; +} + +int InterfaceController::setAcceptIPv6Ra(const char *interface, const int on) { + if (!isIfaceName(interface)) { + errno = ENOENT; + return -1; + } + // Because forwarding can be enabled even when tethering is off, we always + // use mode "2" (accept RAs, even if forwarding is enabled). + const char *accept_ra = on ? "2" : "0"; + return writeValueToPath(ipv6_proc_path, interface, "accept_ra", accept_ra); +} + +int InterfaceController::setAcceptIPv6Dad(const char *interface, const int on) { + if (!isIfaceName(interface)) { + errno = ENOENT; + return -1; + } + const char *accept_dad = on ? "1" : "0"; + return writeValueToPath(ipv6_proc_path, interface, "accept_dad", accept_dad); +} + +int InterfaceController::setIPv6DadTransmits(const char *interface, const char *value) { + if (!isIfaceName(interface)) { + errno = ENOENT; + return -1; + } + return writeValueToPath(ipv6_proc_path, interface, "dad_transmits", value); +} + +int InterfaceController::setIPv6PrivacyExtensions(const char *interface, const int on) { + if (!isIfaceName(interface)) { + errno = ENOENT; + return -errno; + } + // 0: disable IPv6 privacy addresses + // 2: enable IPv6 privacy addresses and prefer them over non-privacy ones. + return writeValueToPath(ipv6_proc_path, interface, "use_tempaddr", on ? "2" : "0"); +} + +void InterfaceController::setAcceptRA(const char *value) { + setOnAllInterfaces(ipv6_proc_path, "accept_ra", value); +} + +// |tableOrOffset| is interpreted as: +// If == 0: default. Routes go into RT6_TABLE_MAIN. +// If > 0: user set. Routes go into the specified table. +// If < 0: automatic. The absolute value is intepreted as an offset and added to the interface +// ID to get the table. If it's set to -1000, routes from interface ID 5 will go into +// table 1005, etc. +void InterfaceController::setAcceptRARouteTable(int tableOrOffset) { + std::string value(StringPrintf("%d", tableOrOffset)); + setOnAllInterfaces(ipv6_proc_path, "accept_ra_rt_table", value.c_str()); +} + +int InterfaceController::setMtu(const char *interface, const char *mtu) +{ + if (!isIfaceName(interface)) { + errno = ENOENT; + return -errno; + } + return writeValueToPath(sys_net_path, interface, "mtu", mtu); +} + +// Returns zero on success and negative errno on failure. +int InterfaceController::addAddress(const char *interface, + const char *addrString, int prefixLength) { + return ifc_add_address(interface, addrString, prefixLength); +} + +// Returns zero on success and negative errno on failure. +int InterfaceController::delAddress(const char *interface, + const char *addrString, int prefixLength) { + return ifc_del_address(interface, addrString, prefixLength); +} + +int InterfaceController::disableIcmpRedirects() { + int rv = 0; + rv |= writeValueToPath(ipv4_proc_path, "all", "accept_redirects", "0"); + rv |= writeValueToPath(ipv6_proc_path, "all", "accept_redirects", "0"); + setOnAllInterfaces(ipv4_proc_path, "accept_redirects", "0"); + setOnAllInterfaces(ipv6_proc_path, "accept_redirects", "0"); + return rv; +} + +int InterfaceController::getParameter( + const char *family, const char *which, const char *interface, const char *parameter, + std::string *value) { + const std::string path(getParameterPathname(family, which, interface, parameter)); + if (path.empty()) { + return -errno; + } + if (ReadFileToString(path, value)) { + *value = Trim(*value); + return 0; + } + return -errno; +} + +int InterfaceController::setParameter( + const char *family, const char *which, const char *interface, const char *parameter, + const char *value) { + const std::string path(getParameterPathname(family, which, interface, parameter)); + if (path.empty()) { + return -errno; + } + return WriteStringToFile(value, path) ? 0 : -errno; +} + +void InterfaceController::setBaseReachableTimeMs(unsigned int millis) { + std::string value(StringPrintf("%u", millis)); + setOnAllInterfaces(ipv4_neigh_conf_dir, "base_reachable_time_ms", value.c_str()); + setOnAllInterfaces(ipv6_neigh_conf_dir, "base_reachable_time_ms", value.c_str()); +} + +void InterfaceController::setIPv6OptimisticMode(const char *value) { + setOnAllInterfaces(ipv6_proc_path, "optimistic_dad", value); + setOnAllInterfaces(ipv6_proc_path, "use_optimistic", value); +} + +namespace { + +std::string hwAddrToStr(unsigned char* hwaddr) { + return StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], + hwaddr[4], hwaddr[5]); +} + +int ipv4NetmaskToPrefixLength(in_addr_t mask) { + int prefixLength = 0; + uint32_t m = ntohl(mask); + while (m & (1 << 31)) { + prefixLength++; + m = m << 1; + } + return prefixLength; +} + +std::string toStdString(const String16& s) { + return std::string(String8(s.c_str())); +} + +} // namespace + +Status InterfaceController::setCfg(const InterfaceConfigurationParcel& cfg) { + const auto& sys = sSyscalls.get(); + ASSIGN_OR_RETURN(auto fd, sys.socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)); + struct ifreq ifr = { + .ifr_addr = {.sa_family = AF_INET}, // Clear the IPv4 address. + }; + strlcpy(ifr.ifr_name, cfg.ifName.c_str(), IFNAMSIZ); + + // Make sure that clear IPv4 address before set flag + // SIOCGIFFLAGS might override ifr and caused clear IPv4 addr ioctl error + RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCSIFADDR, &ifr)); + + if (!cfg.flags.empty()) { + RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCGIFFLAGS, &ifr)); + uint16_t flags = ifr.ifr_flags; + + for (const auto& flag : cfg.flags) { + if (flag == toStdString(INetd::IF_STATE_UP())) { + ifr.ifr_flags = ifr.ifr_flags | IFF_UP; + } else if (flag == toStdString(INetd::IF_STATE_DOWN())) { + ifr.ifr_flags = (ifr.ifr_flags & (~IFF_UP)); + } + } + + if (ifr.ifr_flags != flags) { + RETURN_IF_NOT_OK(sys.ioctl(fd, SIOCSIFFLAGS, &ifr)); + } + } + + if (int ret = ifc_add_address(cfg.ifName.c_str(), cfg.ipv4Addr.c_str(), cfg.prefixLength)) { + return statusFromErrno(-ret, "Failed to add addr"); + } + + return ok; +} + +StatusOr InterfaceController::getCfg(const std::string& ifName) { + struct in_addr addr = {}; + int prefixLength = 0; + unsigned char hwaddr[ETH_ALEN] = {}; + unsigned flags = 0; + InterfaceConfigurationParcel cfgResult; + + const auto& sys = sSyscalls.get(); + ASSIGN_OR_RETURN(auto fd, sys.socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)); + + struct ifreq ifr = {}; + strlcpy(ifr.ifr_name, ifName.c_str(), IFNAMSIZ); + + if (isOk(sys.ioctl(fd, SIOCGIFADDR, &ifr))) { + addr.s_addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + } + + if (isOk(sys.ioctl(fd, SIOCGIFNETMASK, &ifr))) { + prefixLength = + ipv4NetmaskToPrefixLength(((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr); + } + + if (isOk(sys.ioctl(fd, SIOCGIFFLAGS, &ifr))) { + flags = ifr.ifr_flags; + } + + // ETH_ALEN is for ARPHRD_ETHER, it is better to check the sa_family. + // However, we keep old design for the consistency. + if (isOk(sys.ioctl(fd, SIOCGIFHWADDR, &ifr))) { + memcpy((void*) hwaddr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + } else { + ALOGW("Failed to retrieve HW addr for %s (%s)", ifName.c_str(), strerror(errno)); + } + + cfgResult.ifName = ifName; + cfgResult.hwAddr = hwAddrToStr(hwaddr); + cfgResult.ipv4Addr = std::string(inet_ntoa(addr)); + cfgResult.prefixLength = prefixLength; + cfgResult.flags.push_back(flags & IFF_UP ? toStdString(INetd::IF_STATE_UP()) + : toStdString(INetd::IF_STATE_DOWN())); + + if (flags & IFF_BROADCAST) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_BROADCAST())); + if (flags & IFF_LOOPBACK) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_LOOPBACK())); + if (flags & IFF_POINTOPOINT) + cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_POINTOPOINT())); + if (flags & IFF_RUNNING) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_RUNNING())); + if (flags & IFF_MULTICAST) cfgResult.flags.push_back(toStdString(INetd::IF_FLAG_MULTICAST())); + + return cfgResult; +} + +int InterfaceController::clearAddrs(const std::string& ifName) { + return ifc_clear_addresses(ifName.c_str()); +} + +} // namespace net +} // namespace android diff --git a/aosp/system/netd/server/IptablesRestoreController.cpp b/aosp/system/netd/server/IptablesRestoreController.cpp new file mode 100644 index 000000000..84d59350b --- /dev/null +++ b/aosp/system/netd/server/IptablesRestoreController.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "IptablesRestoreController" +#include "IptablesRestoreController.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Controllers.h" +#include "NetdConstants.h" + +using android::netdutils::StatusOr; +using android::netdutils::sSyscalls; + +constexpr char IPTABLES_RESTORE_PATH[] = "/system/bin/iptables-restore"; +constexpr char IP6TABLES_RESTORE_PATH[] = "/system/bin/ip6tables-restore"; + +constexpr char PING[] = "#PING\n"; + +constexpr size_t PING_SIZE = sizeof(PING) - 1; + +// Not compile-time constants because they are changed by the unit tests. +int IptablesRestoreController::MAX_RETRIES = 50; +int IptablesRestoreController::POLL_TIMEOUT_MS = 100 * android::base::HwTimeoutMultiplier(); + +class IptablesProcess { +public: + IptablesProcess(const IptablesRestoreController::IptablesProcessType type, + pid_t pid, int stdIn, int stdOut, int stdErr) : + type(type), + pid(pid), + stdIn(stdIn), + processTerminated(false) { + + pollFds[STDOUT_IDX] = { .fd = stdOut, .events = POLLIN }; + pollFds[STDERR_IDX] = { .fd = stdErr, .events = POLLIN }; + } + + ~IptablesProcess() { + close(stdIn); + close(pollFds[STDOUT_IDX].fd); + close(pollFds[STDERR_IDX].fd); + } + + bool outputReady() { + struct pollfd pollfd = { .fd = stdIn, .events = POLLOUT }; + int ret = poll(&pollfd, 1, 0); + if (ret == -1) { + ALOGE("outputReady poll failed: %s", strerror(errno)); + return false; + } + return (ret == 1) && !(pollfd.revents & POLLERR); + } + + void stop() { + if (processTerminated) return; + + // This can be called by drainAndWaitForAck (after a POLLHUP) or by sendCommand (if the + // process was killed by something else on the system). In both cases, it's safe to send the + // PID a SIGTERM, because the PID continues to exist until its parent (i.e., us) calls + // waitpid on it, so there's no risk that the PID is reused. + ::stopProcess(pid, (type == IptablesRestoreController::IPTABLES_PROCESS) ? + "iptables-restore" : "ip6tables-restore"); + + processTerminated = true; + } + + const IptablesRestoreController::IptablesProcessType type; + const pid_t pid; // NOLINT(misc-non-private-member-variables-in-classes) + const int stdIn; // NOLINT(misc-non-private-member-variables-in-classes) + + struct pollfd pollFds[2]; + std::string errBuf; + + std::atomic_bool processTerminated; + + static constexpr size_t STDOUT_IDX = 0; + static constexpr size_t STDERR_IDX = 1; +}; + +IptablesRestoreController::IptablesRestoreController() { + Init(); +} + +IptablesRestoreController::~IptablesRestoreController() { +} + +void IptablesRestoreController::Init() { + // We cannot fork these in parallel or a child process could inherit the pipe fds intended for + // use by the other child process. see https://android-review.googlesource.com/469559 for what + // breaks. This does not cause a latency hit, because the parent only has to wait for + // forkAndExec, which is sub-millisecond, and the child processes then call exec() in parallel. + // mIpRestore.reset(forkAndExec(IPTABLES_PROCESS)); + // mIp6Restore.reset(forkAndExec(IP6TABLES_PROCESS)); +} + +/* static */ +IptablesProcess* IptablesRestoreController::forkAndExec(const IptablesProcessType type) { + const char* const cmd = (type == IPTABLES_PROCESS) ? + IPTABLES_RESTORE_PATH : IP6TABLES_RESTORE_PATH; + + // Create the pipes we'll use for communication with the child + // process. One each for the child's in, out and err files. + int stdin_pipe[2]; + int stdout_pipe[2]; + int stderr_pipe[2]; + + if (pipe2(stdin_pipe, O_CLOEXEC) == -1 || + pipe2(stdout_pipe, O_NONBLOCK | O_CLOEXEC) == -1 || + pipe2(stderr_pipe, O_NONBLOCK | O_CLOEXEC) == -1) { + + ALOGE("pipe2() failed: %s", strerror(errno)); + return nullptr; + } + + const auto& sys = sSyscalls.get(); + StatusOr child_pid = sys.fork(); + if (!isOk(child_pid)) { + ALOGE("fork() failed: %s", strerror(child_pid.status().code())); + return nullptr; + } + + if (child_pid.value() == 0) { + // The child process. Reads from stdin, writes to stderr and stdout. + + // stdin_pipe[0] : The read end of the stdin pipe. + // stdout_pipe[1] : The write end of the stdout pipe. + // stderr_pipe[1] : The write end of the stderr pipe. + if (dup2(stdin_pipe[0], 0) == -1 || + dup2(stdout_pipe[1], 1) == -1 || + dup2(stderr_pipe[1], 2) == -1) { + ALOGE("dup2() failed: %s", strerror(errno)); + abort(); + } + + if (execl(cmd, + cmd, + "--noflush", // Don't flush the whole table. + "-w", // Wait instead of failing if the lock is held. + "-v", // Verbose mode, to make sure our ping is echoed + // back to us. + nullptr) == -1) { + ALOGE("execl(%s, ...) failed: %s", cmd, strerror(errno)); + abort(); + } + + // This statement is unreachable. We abort() upon error, and execl + // if everything goes well. + return nullptr; + } + + // The parent process. Writes to stdout and stderr and reads from stdin. + // stdin_pipe[0] : The read end of the stdin pipe. + // stdout_pipe[1] : The write end of the stdout pipe. + // stderr_pipe[1] : The write end of the stderr pipe. + if (close(stdin_pipe[0]) == -1 || + close(stdout_pipe[1]) == -1 || + close(stderr_pipe[1]) == -1) { + ALOGW("close() failed: %s", strerror(errno)); + } + + return new IptablesProcess(type, + child_pid.value(), stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]); +} + +// TODO: Return -errno on failure instead of -1. +// TODO: Maybe we should keep a rotating buffer of the last N commands +// so that they can be dumped on dumpsys. +int IptablesRestoreController::sendCommand(const IptablesProcessType type, + const std::string& command, + std::string *output) { + (void) type; + (void) command; + (void) output; +#if 0 + std::unique_ptr *process = + (type == IPTABLES_PROCESS) ? &mIpRestore : &mIp6Restore; + + + // We might need to fork a new process if we haven't forked one yet, or + // if the forked process terminated. + // + // NOTE: For a given command, this is the last point at which we try to + // recover from a child death. If the child dies at some later point during + // the execution of this method, we will receive an EPIPE and return an + // error. The command will then need to be retried at a higher level. + IptablesProcess *existingProcess = process->get(); + if (existingProcess != nullptr && !existingProcess->outputReady()) { + existingProcess->stop(); + existingProcess = nullptr; + } + + if (existingProcess == nullptr) { + // Fork a new iptables[6]-restore process. + IptablesProcess *newProcess = IptablesRestoreController::forkAndExec(type); + if (newProcess == nullptr) { + LOG(ERROR) << "Unable to fork ip[6]tables-restore, type: " << type; + return -1; + } + + process->reset(newProcess); + } + + if (!android::base::WriteFully((*process)->stdIn, command.data(), command.length())) { + ALOGE("Unable to send command: %s", strerror(errno)); + return -1; + } + + if (!android::base::WriteFully((*process)->stdIn, PING, PING_SIZE)) { + ALOGE("Unable to send ping command: %s", strerror(errno)); + return -1; + } + + if (!drainAndWaitForAck(*process, command, output)) { + // drainAndWaitForAck has already logged an error. + return -1; + } +#endif + + return 0; +} + +void IptablesRestoreController::maybeLogStderr(const std::unique_ptr &process, + const std::string& command) { + if (process->errBuf.empty()) { + return; + } + + ALOGE("iptables error:"); + ALOGE("------- COMMAND -------"); + ALOGE("%s", command.c_str()); + ALOGE("------- ERROR -------"); + ALOGE("%s", process->errBuf.c_str()); + ALOGE("----------------------"); + process->errBuf.clear(); +} + +/* static */ +bool IptablesRestoreController::drainAndWaitForAck(const std::unique_ptr &process, + const std::string& command, + std::string *output) { + bool receivedAck = false; + int timeout = 0; + while (!receivedAck && (timeout++ < MAX_RETRIES)) { + int numEvents = TEMP_FAILURE_RETRY( + poll(process->pollFds, ARRAY_SIZE(process->pollFds), POLL_TIMEOUT_MS)); + if (numEvents == -1) { + ALOGE("Poll failed: %s", strerror(errno)); + return false; + } + + // We've timed out, which means something has gone wrong - we know that stdout should have + // become available to read with the ACK message, or that stderr should have been available + // to read with an error message. + if (numEvents == 0) { + continue; + } + + char buffer[PIPE_BUF]; + for (size_t i = 0; i < ARRAY_SIZE(process->pollFds); ++i) { + const struct pollfd &pollfd = process->pollFds[i]; + if (pollfd.revents & POLLIN) { + ssize_t size; + do { + size = TEMP_FAILURE_RETRY(read(pollfd.fd, buffer, sizeof(buffer))); + + if (size == -1) { + if (errno != EAGAIN) { + ALOGE("Unable to read from descriptor: %s", strerror(errno)); + } + break; + } + + if (i == IptablesProcess::STDOUT_IDX) { + // i == STDOUT_IDX: accumulate stdout into *output, and look + // for the ping response. + output->append(buffer, size); + size_t pos = output->find(PING); + if (pos != std::string::npos) { + if (output->size() > pos + PING_SIZE) { + size_t extra = output->size() - (pos + PING_SIZE); + ALOGW("%zd extra characters after iptables response: '%s...'", + extra, output->substr(pos + PING_SIZE, 128).c_str()); + } + output->resize(pos); + receivedAck = true; + } + } else { + // i == STDERR_IDX: accumulate stderr into errBuf. + process->errBuf.append(buffer, size); + } + } while (size > 0); + } + if (pollfd.revents & POLLHUP) { + // The pipe was closed. This likely means the subprocess is exiting, since + // iptables-restore only closes stdin on error. + process->stop(); + break; + } + } + } + + if (!receivedAck && !process->processTerminated) { + ALOGE("Timed out waiting for response from iptables process %d", process->pid); + // Kill the process so that if it eventually recovers, we don't misinterpret the ping + // response (or any output) of the command we just sent as coming from future commands. + process->stop(); + } + + maybeLogStderr(process, command); + + return receivedAck; +} + +int IptablesRestoreController::execute(const IptablesTarget target, const std::string& command, + std::string *output) { + std::lock_guard lock(mLock); + + std::string buffer; + if (output == nullptr) { + output = &buffer; + } else { + output->clear(); + } + + int res = 0; + if (target == V4 || target == V4V6) { + res |= sendCommand(IPTABLES_PROCESS, command, output); + } + if (target == V6 || target == V4V6) { + res |= sendCommand(IP6TABLES_PROCESS, command, output); + } + res = 0; + return res; +} + +int IptablesRestoreController::getIpRestorePid(const IptablesProcessType type) { + return type == IPTABLES_PROCESS ? mIpRestore->pid : mIp6Restore->pid; +} diff --git a/aosp/system/netd/server/NetlinkManager.cpp b/aosp/system/netd/server/NetlinkManager.cpp new file mode 100644 index 000000000..cae9110ed --- /dev/null +++ b/aosp/system/netd/server/NetlinkManager.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define LOG_TAG "Netd" + +#include + +#include +#include +#include + +#include + +#include "NetlinkManager.h" +#include "NetlinkHandler.h" + +#include "pcap-netfilter-linux-android.h" + +namespace android { +namespace net { + +const int NetlinkManager::NFLOG_QUOTA_GROUP = 1; +const int NetlinkManager::NETFILTER_STRICT_GROUP = 2; +const int NetlinkManager::NFLOG_WAKEUP_GROUP = 3; + +NetlinkManager *NetlinkManager::sInstance = nullptr; + +NetlinkManager *NetlinkManager::Instance() { + if (!sInstance) + sInstance = new NetlinkManager(); + return sInstance; +} + +NetlinkManager::NetlinkManager() { + mBroadcaster = nullptr; +} + +NetlinkManager::~NetlinkManager() { +} + +NetlinkHandler *NetlinkManager::setupSocket(int *sock, int netlinkFamily, + int groups, int format, bool configNflog) { + + struct sockaddr_nl nladdr; + int sz = 64 * 1024; + int on = 1; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + // Kernel will assign a unique nl_pid if set to zero. + nladdr.nl_pid = 0; + nladdr.nl_groups = groups; + + if ((*sock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, netlinkFamily)) < 0) { + ALOGE("Unable to create netlink socket for family %d: %s", netlinkFamily, strerror(errno)); + return nullptr; + } + + // When running in a net/user namespace, SO_RCVBUFFORCE will fail because + // it will check for the CAP_NET_ADMIN capability in the root namespace. + // Try using SO_RCVBUF if that fails. + // if (setsockopt(*sock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0 && + if (setsockopt(*sock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0) { + ALOGE("Unable to set uevent socket SO_RCVBUF option: %s", strerror(errno)); + close(*sock); + return nullptr; + } + + if (setsockopt(*sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { + SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno)); + close(*sock); + return nullptr; + } + + if (bind(*sock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) { + ALOGE("Unable to bind netlink socket: %s", strerror(errno)); + close(*sock); + return nullptr; + } + + if (configNflog) { + if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_UNBIND, AF_INET) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_PF_UNBIND: %s", strerror(errno)); + return nullptr; + } + if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_PF_BIND, AF_INET) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_PF_BIND: %s", strerror(errno)); + return nullptr; + } + if (android_nflog_send_config_cmd(*sock, 0, NFULNL_CFG_CMD_BIND, AF_UNSPEC) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_BIND: %s", strerror(errno)); + return nullptr; + } + } + + NetlinkHandler *handler = new NetlinkHandler(this, *sock, format); + if (handler->start()) { + ALOGE("Unable to start NetlinkHandler: %s", strerror(errno)); + close(*sock); + return nullptr; + } + + return handler; +} + +int NetlinkManager::start() { + if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT, + 0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII, false)) == nullptr) { + return -1; + } + + if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE, + RTMGRP_LINK | + RTMGRP_IPV4_IFADDR | + RTMGRP_IPV6_IFADDR | + RTMGRP_IPV6_ROUTE | + (1 << (RTNLGRP_ND_USEROPT - 1)), + NetlinkListener::NETLINK_FORMAT_BINARY, false)) == nullptr) { + return -1; + } + + if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG, + NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY, false)) == nullptr) { + ALOGW("Unable to open qlog quota socket, check if xt_quota2 can send via UeventHandler"); + // TODO: return -1 once the emulator gets a new kernel. + } + + if ((mStrictHandler = setupSocket(&mStrictSock, NETLINK_NETFILTER, + 0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST, true)) == nullptr) { + ALOGE("Unable to open strict socket"); + // TODO: return -1 once the emulator gets a new kernel. + } + + return 0; +} + +int NetlinkManager::stop() { + int status = 0; + + if (mUeventHandler->stop()) { + ALOGE("Unable to stop uevent NetlinkHandler: %s", strerror(errno)); + status = -1; + } + + delete mUeventHandler; + mUeventHandler = nullptr; + + close(mUeventSock); + mUeventSock = -1; + + if (mRouteHandler->stop()) { + ALOGE("Unable to stop route NetlinkHandler: %s", strerror(errno)); + status = -1; + } + + delete mRouteHandler; + mRouteHandler = nullptr; + + close(mRouteSock); + mRouteSock = -1; + + if (mQuotaHandler) { + if (mQuotaHandler->stop()) { + ALOGE("Unable to stop quota NetlinkHandler: %s", strerror(errno)); + status = -1; + } + + delete mQuotaHandler; + mQuotaHandler = nullptr; + + close(mQuotaSock); + mQuotaSock = -1; + } + + if (mStrictHandler) { + if (mStrictHandler->stop()) { + ALOGE("Unable to stop strict NetlinkHandler: %s", strerror(errno)); + status = -1; + } + + delete mStrictHandler; + mStrictHandler = nullptr; + + close(mStrictSock); + mStrictSock = -1; + } + + return status; +} + +} // namespace net +} // namespace android diff --git a/aosp/system/netd/server/RouteController.cpp b/aosp/system/netd/server/RouteController.cpp new file mode 100644 index 000000000..f3d7066e9 --- /dev/null +++ b/aosp/system/netd/server/RouteController.cpp @@ -0,0 +1,1555 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "RouteController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DummyNetwork.h" +#include "Fwmark.h" +#include "NetdConstants.h" +#include "NetlinkCommands.h" +#include "TcUtils.h" + +#include +#include +#include +#include "log/log.h" +#include "netid_client.h" +#include "netutils/ifc.h" + +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::WriteStringToFile; +using android::netdutils::IPPrefix; + +namespace android::net { + +auto RouteController::iptablesRestoreCommandFunction = execIptablesRestoreCommand; +auto RouteController::ifNameToIndexFunction = if_nametoindex; +// BEGIN CONSTANTS -------------------------------------------------------------------------------- + +const uint32_t ROUTE_TABLE_LOCAL_NETWORK = 97; +const uint32_t ROUTE_TABLE_LEGACY_NETWORK = 98; +const uint32_t ROUTE_TABLE_LEGACY_SYSTEM = 99; + +const char* const ROUTE_TABLE_NAME_LOCAL_NETWORK = "local_network"; +const char* const ROUTE_TABLE_NAME_LEGACY_NETWORK = "legacy_network"; +const char* const ROUTE_TABLE_NAME_LEGACY_SYSTEM = "legacy_system"; + +const char* const ROUTE_TABLE_NAME_LOCAL = "local"; +const char* const ROUTE_TABLE_NAME_MAIN = "main"; + +const char* const RouteController::LOCAL_MANGLE_INPUT = "routectrl_mangle_INPUT"; + +const IPPrefix V4_LOCAL_PREFIXES[] = { + IPPrefix::forString("169.254.0.0/16"), // Link Local + IPPrefix::forString("100.64.0.0/10"), // CGNAT + IPPrefix::forString("10.0.0.0/8"), // RFC1918 + IPPrefix::forString("172.16.0.0/12"), // RFC1918 + IPPrefix::forString("192.168.0.0/16") // RFC1918 +}; + +const uint8_t AF_FAMILIES[] = {AF_INET, AF_INET6}; + +const uid_t UID_ROOT = 0; +const uint32_t FWMARK_NONE = 0; +const uint32_t MASK_NONE = 0; +const char* const IIF_LOOPBACK = "lo"; +const char* const IIF_NONE = nullptr; +const char* const OIF_NONE = nullptr; +const bool ACTION_ADD = true; +const bool ACTION_DEL = false; +const bool MODIFY_NON_UID_BASED_RULES = true; + +const mode_t RT_TABLES_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // mode 0644, rw-r--r-- + +// Avoids "non-constant-expression cannot be narrowed from type 'unsigned int' to 'unsigned short'" +// warnings when using RTA_LENGTH(x) inside static initializers (even when x is already uint16_t). +static constexpr uint16_t U16_RTA_LENGTH(uint16_t x) { + return RTA_LENGTH(x); +} + +// These are practically const, but can't be declared so, because they are used to initialize +// non-const pointers ("void* iov_base") in iovec arrays. +rtattr FRATTR_PRIORITY = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_PRIORITY }; +rtattr FRATTR_TABLE = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_TABLE }; +rtattr FRATTR_FWMARK = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_FWMARK }; +rtattr FRATTR_FWMASK = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_FWMASK }; +rtattr FRATTR_UID_RANGE = { U16_RTA_LENGTH(sizeof(fib_rule_uid_range)), FRA_UID_RANGE }; + +rtattr RTATTR_TABLE = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_TABLE }; +rtattr RTATTR_OIF = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_OIF }; +rtattr RTATTR_PRIO = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_PRIORITY }; + +// One or more nested attributes in the RTA_METRICS attribute. +rtattr RTATTRX_MTU = { U16_RTA_LENGTH(sizeof(uint32_t)), RTAX_MTU}; +constexpr size_t RTATTRX_MTU_SIZE = RTA_SPACE(sizeof(uint32_t)); + +// The RTA_METRICS attribute itself. +constexpr size_t RTATTR_METRICS_SIZE = RTATTRX_MTU_SIZE; +rtattr RTATTR_METRICS = { U16_RTA_LENGTH(RTATTR_METRICS_SIZE), RTA_METRICS }; + +uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0}; + +constexpr bool EXPLICIT = true; +constexpr bool IMPLICIT = false; + +// END CONSTANTS ---------------------------------------------------------------------------------- + +static const char* actionName(uint16_t action) { + static const char *ops[4] = {"adding", "deleting", "getting", "???"}; + return ops[action % 4]; +} + +static const char* familyName(uint8_t family) { + switch (family) { + case AF_INET: return "IPv4"; + case AF_INET6: return "IPv6"; + default: return "???"; + } +} + +static void maybeModifyQdiscClsact(const char* interface, bool add); + +static uint32_t getRouteTableIndexFromGlobalRouteTableIndex(uint32_t index, bool local) { + // The local table is + // "global table - ROUTE_TABLE_OFFSET_FROM_INDEX + ROUTE_TABLE_OFFSET_FROM_INDEX_FOR_LOCAL" + const uint32_t localTableOffset = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX_FOR_LOCAL - + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX; + return local ? index + localTableOffset : index; +} + +// Caller must hold sInterfaceToTableLock. +uint32_t RouteController::getRouteTableForInterfaceLocked(const char* interface, bool local) { + // If we already know the routing table for this interface name, use it. + // This ensures we can remove rules and routes for an interface that has been removed, + // or has been removed and re-added with a different interface index. + // + // The caller is responsible for ensuring that an interface is never added to a network + // until it has been removed from any network it was previously in. This ensures that + // if the same interface disconnects and then reconnects with a different interface ID + // when the reconnect happens the interface will not be in the map, and the code will + // determine the new routing table from the interface ID, below. + // + // sInterfaceToTable stores the *global* routing table for the interface, and the local table is + // "global table - ROUTE_TABLE_OFFSET_FROM_INDEX + ROUTE_TABLE_OFFSET_FROM_INDEX_FOR_LOCAL" + auto iter = sInterfaceToTable.find(interface); + if (iter != sInterfaceToTable.end()) { + return getRouteTableIndexFromGlobalRouteTableIndex(iter->second, local); + } + + uint32_t index = RouteController::ifNameToIndexFunction(interface); + if (index == 0) { + ALOGE("cannot find interface %s: %s", interface, strerror(errno)); + return RT_TABLE_UNSPEC; + } + index += RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX; + sInterfaceToTable[interface] = index; + return getRouteTableIndexFromGlobalRouteTableIndex(index, local); +} + +uint32_t RouteController::getIfIndex(const char* interface) { + std::lock_guard lock(sInterfaceToTableLock); + + auto iter = sInterfaceToTable.find(interface); + if (iter == sInterfaceToTable.end()) { + ALOGE("getIfIndex: cannot find interface %s", interface); + return 0; + } + + // For interfaces that are not in the local network, the routing table is always the interface + // index plus ROUTE_TABLE_OFFSET_FROM_INDEX. But for interfaces in the local network, there's no + // way to know the interface index from this table. Return 0 here so callers of this method do + // not get confused. + // TODO: stop calling this method from any caller that only wants interfaces in client mode. + int ifindex = iter->second; + if (ifindex == ROUTE_TABLE_LOCAL_NETWORK) { + return 0; + } + + return ifindex - ROUTE_TABLE_OFFSET_FROM_INDEX; +} + +uint32_t RouteController::getRouteTableForInterface(const char* interface, bool local) { + std::lock_guard lock(sInterfaceToTableLock); + return getRouteTableForInterfaceLocked(interface, local); +} + +void addTableName(uint32_t table, const std::string& name, std::string* contents) { + char tableString[UINT32_STRLEN]; + snprintf(tableString, sizeof(tableString), "%u", table); + *contents += tableString; + *contents += " "; + *contents += name; + *contents += "\n"; +} + +// Doesn't return success/failure as the file is optional; it's okay if we fail to update it. +void RouteController::updateTableNamesFile() { + std::string contents; + + addTableName(RT_TABLE_LOCAL, ROUTE_TABLE_NAME_LOCAL, &contents); + addTableName(RT_TABLE_MAIN, ROUTE_TABLE_NAME_MAIN, &contents); + + addTableName(ROUTE_TABLE_LOCAL_NETWORK, ROUTE_TABLE_NAME_LOCAL_NETWORK, &contents); + addTableName(ROUTE_TABLE_LEGACY_NETWORK, ROUTE_TABLE_NAME_LEGACY_NETWORK, &contents); + addTableName(ROUTE_TABLE_LEGACY_SYSTEM, ROUTE_TABLE_NAME_LEGACY_SYSTEM, &contents); + + std::lock_guard lock(sInterfaceToTableLock); + for (const auto& [ifName, table] : sInterfaceToTable) { + if (table <= ROUTE_TABLE_OFFSET_FROM_INDEX) { + continue; + } + addTableName(table, ifName, &contents); + // Add table for the local route of the network. It's expected to be used for excluding the + // local traffic in the VPN network. + // Start from ROUTE_TABLE_OFFSET_FROM_INDEX_FOR_LOCAL plus with the interface table index. + uint32_t offset = ROUTE_TABLE_OFFSET_FROM_INDEX_FOR_LOCAL - ROUTE_TABLE_OFFSET_FROM_INDEX; + addTableName(offset + table, ifName + INTERFACE_LOCAL_SUFFIX, &contents); + } + + if (!WriteStringToFile(contents, RT_TABLES_PATH, RT_TABLES_MODE, AID_SYSTEM, AID_WIFI)) { + ALOGE("failed to write to %s (%s)", RT_TABLES_PATH, strerror(errno)); + return; + } +} + +// Returns 0 on success or negative errno on failure. +int padInterfaceName(const char* input, char* name, size_t* length, uint16_t* padding) { + if (!input) { + *length = 0; + *padding = 0; + return 0; + } + *length = strlcpy(name, input, IFNAMSIZ) + 1; + if (*length > IFNAMSIZ) { + ALOGE("interface name too long (%zu > %u)", *length, IFNAMSIZ); + return -ENAMETOOLONG; + } + *padding = RTA_SPACE(*length) - RTA_LENGTH(*length); + return 0; +} + +// Adds or removes a routing rule for IPv4 and IPv6. +// +// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the table is +// unspecified. An unspecified table is not allowed when creating an FR_ACT_TO_TBL rule. +// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is +// ignored. +// + If |iif| is non-NULL, the rule matches the specified incoming interface. +// + If |oif| is non-NULL, the rule matches the specified outgoing interface. +// + If |uidStart| and |uidEnd| are not INVALID_UID, the rule matches packets from UIDs in that +// range (inclusive). Otherwise, the rule matches packets from all UIDs. +// +// Returns 0 on success or negative errno on failure. +[[nodiscard]] static int modifyIpRule(uint16_t action, int32_t priority, uint8_t ruleType, + uint32_t table, uint32_t fwmark, uint32_t mask, + const char* iif, const char* oif, uid_t uidStart, + uid_t uidEnd) { + if (priority < 0) { + ALOGE("invalid IP-rule priority %d", priority); + return -ERANGE; + } + + // Ensure that if you set a bit in the fwmark, it's not being ignored by the mask. + if (fwmark & ~mask) { + ALOGE("mask 0x%x does not select all the bits set in fwmark 0x%x", mask, fwmark); + return -ERANGE; + } + + // Interface names must include exactly one terminating NULL and be properly padded, or older + // kernels will refuse to delete rules. + char iifName[IFNAMSIZ], oifName[IFNAMSIZ]; + size_t iifLength, oifLength; + uint16_t iifPadding, oifPadding; + if (int ret = padInterfaceName(iif, iifName, &iifLength, &iifPadding)) { + return ret; + } + if (int ret = padInterfaceName(oif, oifName, &oifLength, &oifPadding)) { + return ret; + } + + // Either both start and end UID must be specified, or neither. + if ((uidStart == INVALID_UID) != (uidEnd == INVALID_UID)) { + ALOGE("incompatible start and end UIDs (%u vs %u)", uidStart, uidEnd); + return -EUSERS; + } + + bool isUidRule = (uidStart != INVALID_UID); + + // Assemble a rule request and put it in an array of iovec structures. + fib_rule_hdr rule = { + .action = ruleType, + // Note that here we're implicitly setting rule.table to 0. When we want to specify a + // non-zero table, we do this via the FRATTR_TABLE attribute. + }; + + // Don't ever create a rule that looks up table 0, because table 0 is the local table. + // It's OK to specify a table ID of 0 when deleting a rule, because that doesn't actually select + // table 0, it's a wildcard that matches anything. + if (table == RT_TABLE_UNSPEC && rule.action == FR_ACT_TO_TBL && action != RTM_DELRULE) { + ALOGE("RT_TABLE_UNSPEC only allowed when deleting rules"); + return -ENOTUNIQ; + } + + rtattr fraIifName = { U16_RTA_LENGTH(iifLength), FRA_IIFNAME }; + rtattr fraOifName = { U16_RTA_LENGTH(oifLength), FRA_OIFNAME }; + struct fib_rule_uid_range uidRange = { uidStart, uidEnd }; + + iovec iov[] = { + { nullptr, 0 }, + { &rule, sizeof(rule) }, + { &FRATTR_PRIORITY, sizeof(FRATTR_PRIORITY) }, + { &priority, sizeof(priority) }, + { &FRATTR_TABLE, table != RT_TABLE_UNSPEC ? sizeof(FRATTR_TABLE) : 0 }, + { &table, table != RT_TABLE_UNSPEC ? sizeof(table) : 0 }, + { &FRATTR_FWMARK, mask ? sizeof(FRATTR_FWMARK) : 0 }, + { &fwmark, mask ? sizeof(fwmark) : 0 }, + { &FRATTR_FWMASK, mask ? sizeof(FRATTR_FWMASK) : 0 }, + { &mask, mask ? sizeof(mask) : 0 }, + { &FRATTR_UID_RANGE, isUidRule ? sizeof(FRATTR_UID_RANGE) : 0 }, + { &uidRange, isUidRule ? sizeof(uidRange) : 0 }, + { &fraIifName, iif != IIF_NONE ? sizeof(fraIifName) : 0 }, + { iifName, iifLength }, + { PADDING_BUFFER, iifPadding }, + { &fraOifName, oif != OIF_NONE ? sizeof(fraOifName) : 0 }, + { oifName, oifLength }, + { PADDING_BUFFER, oifPadding }, + }; + + uint16_t flags = (action == RTM_NEWRULE) ? NETLINK_RULE_CREATE_FLAGS : NETLINK_REQUEST_FLAGS; + for (size_t i = 0; i < ARRAY_SIZE(AF_FAMILIES); ++i) { + rule.family = AF_FAMILIES[i]; + if (int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr)) { + if (!(action == RTM_DELRULE && ret == -ENOENT && priority == RULE_PRIORITY_TETHERING)) { + // Don't log when deleting a tethering rule that's not there. This matches the + // behaviour of clearTetheringRules, which ignores ENOENT in this case. + ALOGE("Error %s %s rule: %s", actionName(action), familyName(rule.family), + strerror(-ret)); + } + return ret; + } + } + + return 0; +} + +[[nodiscard]] static int modifyIpRule(uint16_t action, int32_t priority, uint32_t table, + uint32_t fwmark, uint32_t mask, const char* iif, + const char* oif, uid_t uidStart, uid_t uidEnd) { + return modifyIpRule(action, priority, FR_ACT_TO_TBL, table, fwmark, mask, iif, oif, uidStart, + uidEnd); +} + +[[nodiscard]] static int modifyIpRule(uint16_t action, int32_t priority, uint32_t table, + uint32_t fwmark, uint32_t mask) { + return modifyIpRule(action, priority, table, fwmark, mask, IIF_NONE, OIF_NONE, INVALID_UID, + INVALID_UID); +} + +// Adds or deletes an IPv4 or IPv6 route. +// Returns 0 on success or negative errno on failure. +int modifyIpRoute(uint16_t action, uint16_t flags, uint32_t table, const char* interface, + const char* destination, const char* nexthop, uint32_t mtu, uint32_t priority) { + // At least the destination must be non-null. + if (!destination) { + ALOGE("null destination"); + return -EFAULT; + } + + // Parse the prefix. + uint8_t rawAddress[sizeof(in6_addr)]; + uint8_t family; + uint8_t prefixLength; + int rawLength = parsePrefix(destination, &family, rawAddress, sizeof(rawAddress), + &prefixLength); + if (rawLength < 0) { + ALOGE("parsePrefix failed for destination %s (%s)", destination, strerror(-rawLength)); + return rawLength; + } + + if (static_cast(rawLength) > sizeof(rawAddress)) { + ALOGE("impossible! address too long (%d vs %zu)", rawLength, sizeof(rawAddress)); + return -ENOBUFS; // Cannot happen; parsePrefix only supports IPv4 and IPv6. + } + + uint8_t type = RTN_UNICAST; + uint32_t ifindex; + uint8_t rawNexthop[sizeof(in6_addr)]; + + if (nexthop && !strcmp(nexthop, "unreachable")) { + type = RTN_UNREACHABLE; + // 'interface' is likely non-NULL, as the caller (modifyRoute()) likely used it to lookup + // the table number. But it's an error to specify an interface ("dev ...") or a nexthop for + // unreachable routes, so nuke them. (IPv6 allows them to be specified; IPv4 doesn't.) + interface = OIF_NONE; + nexthop = nullptr; + } else if (nexthop && !strcmp(nexthop, "throw")) { + type = RTN_THROW; + interface = OIF_NONE; + nexthop = nullptr; + } else { + // If an interface was specified, find the ifindex. + if (interface != OIF_NONE) { + ifindex = RouteController::ifNameToIndexFunction(interface); + + if (!ifindex) { + ALOGE("cannot find interface %s", interface); + return -ENODEV; + } + } + + // If a nexthop was specified, parse it as the same family as the prefix. + if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) { + ALOGE("inet_pton failed for nexthop %s", nexthop); + return -EINVAL; + } + } + + // Assemble a rtmsg and put it in an array of iovec structures. + rtmsg route = { + .rtm_family = family, + .rtm_dst_len = prefixLength, + .rtm_protocol = RTPROT_STATIC, + .rtm_scope = static_cast(nexthop ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK), + .rtm_type = type, + }; + + rtattr rtaDst = { U16_RTA_LENGTH(rawLength), RTA_DST }; + rtattr rtaGateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY }; + + iovec iov[] = { + {nullptr, 0}, + {&route, sizeof(route)}, + {&RTATTR_TABLE, sizeof(RTATTR_TABLE)}, + {&table, sizeof(table)}, + {&rtaDst, sizeof(rtaDst)}, + {rawAddress, static_cast(rawLength)}, + {&RTATTR_OIF, interface != OIF_NONE ? sizeof(RTATTR_OIF) : 0}, + {&ifindex, interface != OIF_NONE ? sizeof(ifindex) : 0}, + {&rtaGateway, nexthop ? sizeof(rtaGateway) : 0}, + {rawNexthop, nexthop ? static_cast(rawLength) : 0}, + {&RTATTR_METRICS, mtu != 0 ? sizeof(RTATTR_METRICS) : 0}, + {&RTATTRX_MTU, mtu != 0 ? sizeof(RTATTRX_MTU) : 0}, + {&mtu, mtu != 0 ? sizeof(mtu) : 0}, + {&RTATTR_PRIO, priority != 0 ? sizeof(RTATTR_PRIO) : 0}, + {&priority, priority != 0 ? sizeof(priority) : 0}, + }; + + // Allow creating multiple link-local routes in the same table, so we can make IPv6 + // work on all interfaces in the local_network table. + if (family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(reinterpret_cast(rawAddress))) { + flags &= ~NLM_F_EXCL; + } + + int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr); + if (ret) { + ALOGE("Error %s route %s -> %s %s to table %u: %s", + actionName(action), destination, nexthop, interface, table, strerror(-ret)); + } + return ret; +} + +// An iptables rule to mark incoming packets on a network with the netId of the network. +// +// This is so that the kernel can: +// + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors, ping +// replies, SYN-ACKs, etc). +// + Mark sockets that accept connections from this interface so that the connection stays on the +// same interface. +int modifyIncomingPacketMark(unsigned netId, const char* interface, Permission permission, + bool add) { + Fwmark fwmark; + + fwmark.netId = netId; + fwmark.explicitlySelected = true; + fwmark.protectedFromVpn = true; + fwmark.permission = permission; + + const uint32_t mask = ~Fwmark::getUidBillingMask(); + + std::string cmd = StringPrintf( + "%s %s -i %s -j MARK --set-mark 0x%x/0x%x", add ? "-A" : "-D", + RouteController::LOCAL_MANGLE_INPUT, interface, fwmark.intValue, mask); + if (RouteController::iptablesRestoreCommandFunction(V4V6, "mangle", cmd, nullptr) != 0) { + ALOGE("failed to change iptables rule that sets incoming packet mark"); + return -EREMOTEIO; + } + + return 0; +} + +// A rule to route responses to the local network forwarded via the VPN. +// +// When a VPN is in effect, packets from the local network to upstream networks are forwarded into +// the VPN's tunnel interface. When the VPN forwards the responses, they emerge out of the tunnel. +[[nodiscard]] static int modifyVpnOutputToLocalRule(const char* vpnInterface, bool add) { + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL, + ROUTE_TABLE_LOCAL_NETWORK, MARK_UNSET, MARK_UNSET, vpnInterface, OIF_NONE, + INVALID_UID, INVALID_UID); +} + +// A rule to route all traffic from a given set of UIDs to go over the VPN. +// +// Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket may +// have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic to +// bypass the VPN if the protectedFromVpn bit is set. +[[nodiscard]] static int modifyVpnUidRangeRule(uint32_t table, uid_t uidStart, uid_t uidEnd, + int32_t subPriority, bool secure, bool add, + bool excludeLocalRoutes) { + Fwmark fwmark; + Fwmark mask; + + fwmark.protectedFromVpn = false; + mask.protectedFromVpn = true; + + int32_t priority; + + if (secure) { + priority = RULE_PRIORITY_SECURE_VPN; + } else { + priority = excludeLocalRoutes ? RULE_PRIORITY_BYPASSABLE_VPN_LOCAL_EXCLUSION + : RULE_PRIORITY_BYPASSABLE_VPN_NO_LOCAL_EXCLUSION; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + } + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority + subPriority, table, + fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd); +} + +// A rule to allow system apps to send traffic over this VPN even if they are not part of the target +// set of UIDs. +// +// This is needed for DnsProxyListener to correctly resolve a request for a user who is in the +// target set, but where the DnsProxyListener itself is not. +[[nodiscard]] static int modifyVpnSystemPermissionRule(unsigned netId, uint32_t table, bool secure, + bool add, bool excludeLocalRoutes) { + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = netId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.permission = PERMISSION_SYSTEM; + mask.permission = PERMISSION_SYSTEM; + + uint32_t priority; + + if (secure) { + priority = RULE_PRIORITY_SECURE_VPN; + } else { + priority = excludeLocalRoutes ? RULE_PRIORITY_BYPASSABLE_VPN_LOCAL_EXCLUSION + : RULE_PRIORITY_BYPASSABLE_VPN_NO_LOCAL_EXCLUSION; + } + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue, + mask.intValue); +} + +// A rule to route traffic based on an explicitly chosen network. +// +// Supports apps that use the multinetwork APIs to restrict their traffic to a network. +// +// Even though we check permissions at the time we set a netId into the fwmark of a socket, we need +// to check it again in the rules here, because a network's permissions may have been updated via +// modifyNetworkPermission(). +[[nodiscard]] static int modifyExplicitNetworkRule(unsigned netId, uint32_t table, + Permission permission, uid_t uidStart, + uid_t uidEnd, int32_t subPriority, bool add) { + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = netId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.explicitlySelected = true; + mask.explicitlySelected = true; + + fwmark.permission = permission; + mask.permission = permission; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + RULE_PRIORITY_EXPLICIT_NETWORK + subPriority, table, fwmark.intValue, + mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd); +} + +// A rule to route traffic based on an local network. +// +// Supports apps that send traffic to local IPs without binding to a particular network. +// +[[nodiscard]] static int modifyLocalNetworkRule(uint32_t table, bool add) { + Fwmark fwmark; + Fwmark mask; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + if (const int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_LOCAL_NETWORK, + table, fwmark.intValue, mask.intValue, IIF_NONE, OIF_NONE, + INVALID_UID, INVALID_UID)) { + return ret; + } + + fwmark.explicitlySelected = true; + mask.explicitlySelected = true; + + fwmark.netId = INetd::LOCAL_NET_ID; + mask.netId = FWMARK_NET_ID_MASK; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_EXPLICIT_NETWORK, table, + fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, INVALID_UID, + INVALID_UID); +} + +// A rule to route traffic based on a chosen outgoing interface. +// +// Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already knows +// the outgoing interface (typically for link-local communications). +[[nodiscard]] static int modifyOutputInterfaceRules(const char* interface, uint32_t table, + Permission permission, uid_t uidStart, + uid_t uidEnd, int32_t subPriority, bool add) { + Fwmark fwmark; + Fwmark mask; + + fwmark.permission = permission; + mask.permission = permission; + + // If this rule does not specify a UID range, then also add a corresponding high-priority rule + // for root. This covers kernel-originated packets, TEEd packets and any local daemons that open + // sockets as root. + if (uidStart == INVALID_UID && uidEnd == INVALID_UID) { + if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OVERRIDE_OIF, + table, FWMARK_NONE, MASK_NONE, IIF_LOOPBACK, interface, + UID_ROOT, UID_ROOT)) { + return ret; + } + } + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + RULE_PRIORITY_OUTPUT_INTERFACE + subPriority, table, fwmark.intValue, + mask.intValue, IIF_LOOPBACK, interface, uidStart, uidEnd); +} + +// A rule to route traffic based on the chosen network. +// +// This is for sockets that have not explicitly requested a particular network, but have been +// bound to one when they called connect(). This ensures that sockets connected on a particular +// network stay on that network even if the default network changes. +[[nodiscard]] static int modifyImplicitNetworkRule(unsigned netId, uint32_t table, bool add) { + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = netId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_IMPLICIT_NETWORK, table, + fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, INVALID_UID, + INVALID_UID); +} + +int RouteController::modifyVpnLocalExclusionRule(bool add, const char* physicalInterface) { + uint32_t table = getRouteTableForInterface(physicalInterface, true /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_LOCAL_ROUTES, table, + fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, INVALID_UID, + INVALID_UID); +} + +int RouteController::addFixedLocalRoutes(const char* interface) { + for (size_t i = 0; i < ARRAY_SIZE(V4_FIXED_LOCAL_PREFIXES); ++i) { + if (int ret = modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, interface, + V4_FIXED_LOCAL_PREFIXES[i], nullptr /* nexthop */, + RouteController::INTERFACE, 0 /* mtu */, 0 /* priority */, + true /* isLocal */)) { + return ret; + } + } + + return 0; +} + +// A rule to enable split tunnel VPNs. +// +// If a packet with a VPN's netId doesn't find a route in the VPN's routing table, it's allowed to +// go over the default network, provided it has the permissions required by the default network. +int RouteController::modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId, + const char* physicalInterface, + Permission permission) { + uint32_t table = getRouteTableForInterface(physicalInterface, false /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = vpnNetId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.permission = permission; + mask.permission = permission; + + return modifyIpRule(action, RULE_PRIORITY_VPN_FALLTHROUGH, table, fwmark.intValue, + mask.intValue); +} + +// Add rules to allow legacy routes added through the requestRouteToHost() API. +[[nodiscard]] static int addLegacyRouteRules() { + Fwmark fwmark; + Fwmark mask; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + // Rules to allow legacy routes to override the default network. + if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM, + fwmark.intValue, mask.intValue)) { + return ret; + } + if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_NETWORK, + ROUTE_TABLE_LEGACY_NETWORK, fwmark.intValue, mask.intValue)) { + return ret; + } + + fwmark.permission = PERMISSION_SYSTEM; + mask.permission = PERMISSION_SYSTEM; + + // A rule to allow legacy routes from system apps to override VPNs. + return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_VPN_OVERRIDE_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM, + fwmark.intValue, mask.intValue); +} + +// Add rules to lookup the local network when specified explicitly or otherwise. +[[nodiscard]] static int addLocalNetworkRules(unsigned localNetId) { + if (int ret = modifyExplicitNetworkRule(localNetId, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE, + INVALID_UID, INVALID_UID, + UidRanges::SUB_PRIORITY_HIGHEST, ACTION_ADD)) { + return ret; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LOCAL_NETWORK, ROUTE_TABLE_LOCAL_NETWORK, + fwmark.intValue, mask.intValue); +} + +/* static */ +int RouteController::configureDummyNetwork() { + const char *interface = DummyNetwork::INTERFACE_NAME; + uint32_t table = getRouteTableForInterface(interface, false /* local */); + if (table == RT_TABLE_UNSPEC) { + // getRouteTableForInterface has already logged an error. + return -ESRCH; + } + + ifc_init(); + int ret = ifc_up(interface); + ifc_close(); + if (ret) { + ALOGE("Can't bring up %s: %s", interface, strerror(errno)); + return -errno; + } + + if ((ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, INVALID_UID, + INVALID_UID, UidRanges::SUB_PRIORITY_HIGHEST, + ACTION_ADD))) { + ALOGE("Can't create oif rules for %s: %s", interface, strerror(-ret)); + return ret; + } + + if ((ret = modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table, interface, + "0.0.0.0/0", nullptr, 0 /* mtu */, 0 /* priority */))) { + return ret; + } + + if ((ret = modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table, interface, "::/0", + nullptr, 0 /* mtu */, 0 /* priority */))) { + return ret; + } + + return 0; +} + +// Add an explicit unreachable rule close to the end of the prioriy list to make it clear that +// relying on the kernel-default "from all lookup main" rule at priority 32766 is not intended +// behaviour. We do flush the kernel-default rules at startup, but having an explicit unreachable +// rule will hopefully make things even clearer. +[[nodiscard]] static int addUnreachableRule() { + return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, FR_ACT_UNREACHABLE, RT_TABLE_UNSPEC, + MARK_UNSET, MARK_UNSET, IIF_NONE, OIF_NONE, INVALID_UID, INVALID_UID); +} + +[[nodiscard]] static int modifyLocalNetwork(unsigned netId, const char* interface, bool add) { + if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) { + return ret; + } + maybeModifyQdiscClsact(interface, add); + return modifyOutputInterfaceRules(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE, + INVALID_UID, INVALID_UID, UidRanges::SUB_PRIORITY_HIGHEST, + add); +} + +[[nodiscard]] static int modifyUidNetworkRule(unsigned netId, uint32_t table, uid_t uidStart, + uid_t uidEnd, int32_t subPriority, bool add, + bool explicitSelect) { + if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) { + ALOGE("modifyUidNetworkRule, invalid UIDs (%u, %u)", uidStart, uidEnd); + return -EUSERS; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = netId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.explicitlySelected = explicitSelect; + mask.explicitlySelected = true; + + // Access to this network is controlled by UID rules, not permission bits. + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + explicitSelect ? (RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority) + : (RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority), + table, fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, + uidEnd); +} + +[[nodiscard]] static int modifyUidDefaultNetworkRule(uint32_t table, uid_t uidStart, uid_t uidEnd, + int32_t subPriority, bool add) { + if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) { + ALOGE("modifyUidDefaultNetworkRule, invalid UIDs (%u, %u)", uidStart, uidEnd); + return -EUSERS; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = NETID_UNSET; + mask.netId = FWMARK_NET_ID_MASK; + + // Access to this network is controlled by UID rules, not permission bits. + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + RULE_PRIORITY_UID_DEFAULT_NETWORK + subPriority, table, fwmark.intValue, + mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd); +} + +/* static */ +int RouteController::modifyPhysicalNetwork(unsigned netId, const char* interface, + const UidRangeMap& uidRangeMap, Permission permission, + bool add, bool modifyNonUidBasedRules, bool local) { + uint32_t table = getRouteTableForInterface(interface, false /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + for (const auto& [subPriority, uidRanges] : uidRangeMap) { + for (const UidRangeParcel& range : uidRanges.getRanges()) { + if (int ret = modifyUidNetworkRule(netId, table, range.start, range.stop, subPriority, + add, EXPLICIT)) { + return ret; + } + if (int ret = modifyUidNetworkRule(netId, table, range.start, range.stop, subPriority, + add, IMPLICIT)) { + return ret; + } + // SUB_PRIORITY_NO_DEFAULT is "special" and does not require a + // default network rule, see UidRanges.h. + if (subPriority != UidRanges::SUB_PRIORITY_NO_DEFAULT) { + if (int ret = modifyUidDefaultNetworkRule(table, range.start, range.stop, + subPriority, add)) { + return ret; + } + + // Per-UID local network rules must always match per-app default network rules, + // because their purpose is to allow the UIDs to use the default network for + // local destinations within it. + if (int ret = modifyUidLocalNetworkRule(interface, range.start, range.stop, add)) { + return ret; + } + } + } + } + + if (!modifyNonUidBasedRules) { + // we are done. + return 0; + } + + if (int ret = modifyIncomingPacketMark(netId, interface, permission, add)) { + return ret; + } + if (int ret = modifyExplicitNetworkRule(netId, table, permission, INVALID_UID, INVALID_UID, + UidRanges::SUB_PRIORITY_HIGHEST, add)) { + return ret; + } + if (local) { + if (const int ret = modifyLocalNetworkRule(table, add)) { + return ret; + } + } + if (int ret = modifyOutputInterfaceRules(interface, table, permission, INVALID_UID, INVALID_UID, + UidRanges::SUB_PRIORITY_HIGHEST, add)) { + return ret; + } + + // Only set implicit rules for networks that don't require permissions. + // + // This is so that if the default network ceases to be the default network and then switches + // from requiring no permissions to requiring permissions, we ensure that apps only use the + // network if they explicitly select it. This is consistent with destroySocketsLackingPermission + // - it closes all sockets on the network except sockets that are explicitly selected. + // + // The lack of this rule only affects the special case above, because: + // - The only cases where we implicitly bind a socket to a network are the default network and + // the bypassable VPN that applies to the app, if any. + // - This rule doesn't affect VPNs because they don't support permissions at all. + // - The default network doesn't require permissions. While we support doing this, the framework + // never does it (partly because we'd end up in the situation where we tell apps that there is + // a default network, but they can't use it). + // - If the network is still the default network, the presence or absence of this rule does not + // matter. + // + // Therefore, for the lack of this rule to affect a socket, the socket has to have been + // implicitly bound to a network because at the time of connect() it was the default, and that + // network must no longer be the default, and must now require permissions. + if (permission == PERMISSION_NONE) { + return modifyImplicitNetworkRule(netId, table, add); + } + return 0; +} + +int RouteController::modifyUidLocalNetworkRule(const char* interface, uid_t uidStart, uid_t uidEnd, + bool add) { + uint32_t table = getRouteTableForInterface(interface, true /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) { + ALOGE("modifyUidLocalNetworkRule, invalid UIDs (%u, %u)", uidStart, uidEnd); + return -EUSERS; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.explicitlySelected = false; + mask.explicitlySelected = true; + + // Access to this network is controlled by UID rules, not permission bits. + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_UID_LOCAL_ROUTES, table, + fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd); +} + +[[nodiscard]] static int modifyUidUnreachableRule(unsigned netId, uid_t uidStart, uid_t uidEnd, + int32_t subPriority, bool add, + bool explicitSelect) { + if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) { + ALOGE("modifyUidUnreachableRule, invalid UIDs (%u, %u)", uidStart, uidEnd); + return -EUSERS; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = netId; + mask.netId = FWMARK_NET_ID_MASK; + + fwmark.explicitlySelected = explicitSelect; + mask.explicitlySelected = true; + + // Access to this network is controlled by UID rules, not permission bits. + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + explicitSelect ? (RULE_PRIORITY_UID_EXPLICIT_NETWORK + subPriority) + : (RULE_PRIORITY_UID_IMPLICIT_NETWORK + subPriority), + FR_ACT_UNREACHABLE, RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue, + IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd); +} + +[[nodiscard]] static int modifyUidDefaultUnreachableRule(uid_t uidStart, uid_t uidEnd, + int32_t subPriority, bool add) { + if ((uidStart == INVALID_UID) || (uidEnd == INVALID_UID)) { + ALOGE("modifyUidDefaultUnreachableRule, invalid UIDs (%u, %u)", uidStart, uidEnd); + return -EUSERS; + } + + Fwmark fwmark; + Fwmark mask; + + fwmark.netId = NETID_UNSET; + mask.netId = FWMARK_NET_ID_MASK; + + // Access to this network is controlled by UID rules, not permission bits. + fwmark.permission = PERMISSION_NONE; + mask.permission = PERMISSION_NONE; + + return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, + RULE_PRIORITY_UID_DEFAULT_UNREACHABLE + subPriority, FR_ACT_UNREACHABLE, + RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE, + uidStart, uidEnd); +} + +int RouteController::modifyUnreachableNetwork(unsigned netId, const UidRangeMap& uidRangeMap, + bool add) { + for (const auto& [subPriority, uidRanges] : uidRangeMap) { + for (const UidRangeParcel& range : uidRanges.getRanges()) { + if (int ret = modifyUidUnreachableRule(netId, range.start, range.stop, subPriority, add, + EXPLICIT)) { + return ret; + } + if (int ret = modifyUidUnreachableRule(netId, range.start, range.stop, subPriority, add, + IMPLICIT)) { + return ret; + } + if (int ret = modifyUidDefaultUnreachableRule(range.start, range.stop, subPriority, + add)) { + return ret; + } + } + } + + return 0; +} + +[[nodiscard]] static int modifyRejectNonSecureNetworkRule(const UidRanges& uidRanges, bool add) { + Fwmark fwmark; + Fwmark mask; + fwmark.protectedFromVpn = false; + mask.protectedFromVpn = true; + + for (const UidRangeParcel& range : uidRanges.getRanges()) { + if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_PROHIBIT_NON_VPN, + FR_ACT_PROHIBIT, RT_TABLE_UNSPEC, fwmark.intValue, mask.intValue, + IIF_LOOPBACK, OIF_NONE, range.start, range.stop)) { + return ret; + } + } + + return 0; +} + +int RouteController::modifyVirtualNetwork(unsigned netId, const char* interface, + const UidRangeMap& uidRangeMap, bool secure, bool add, + bool modifyNonUidBasedRules, bool excludeLocalRoutes) { + uint32_t table = getRouteTableForInterface(interface, false /* false */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + for (const auto& [subPriority, uidRanges] : uidRangeMap) { + for (const UidRangeParcel& range : uidRanges.getRanges()) { + if (int ret = modifyVpnUidRangeRule(table, range.start, range.stop, subPriority, secure, + add, excludeLocalRoutes)) { + return ret; + } + if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.start, + range.stop, subPriority, add)) { + return ret; + } + if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.start, + range.stop, subPriority, add)) { + return ret; + } + } + } + + if (modifyNonUidBasedRules) { + if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) { + return ret; + } + if (int ret = modifyVpnOutputToLocalRule(interface, add)) { + return ret; + } + if (int ret = + modifyVpnSystemPermissionRule(netId, table, secure, add, excludeLocalRoutes)) { + return ret; + } + return modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT, + UidRanges::SUB_PRIORITY_HIGHEST, add); + } + + return 0; +} + +int RouteController::modifyDefaultNetwork(uint16_t action, const char* interface, + Permission permission) { + (void)permission; + uint32_t table = getRouteTableForInterface(interface, false /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + return modifyIpRule(action, RULE_PRIORITY_DEFAULT_NETWORK, table, FWMARK_NONE, + MASK_NONE, IIF_LOOPBACK, OIF_NONE, INVALID_UID, INVALID_UID); +} + +int RouteController::modifyTetheredNetwork(uint16_t action, const char* inputInterface, + const char* outputInterface) { + uint32_t table = getRouteTableForInterface(outputInterface, false /* local */); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + return modifyIpRule(action, RULE_PRIORITY_TETHERING, table, MARK_UNSET, MARK_UNSET, + inputInterface, OIF_NONE, INVALID_UID, INVALID_UID); +} + +// Adds or removes an IPv4 or IPv6 route to the specified table. +// Returns 0 on success or negative errno on failure. +int RouteController::modifyRoute(uint16_t action, uint16_t flags, const char* interface, + const char* destination, const char* nexthop, TableType tableType, + int mtu, int priority, bool isLocal) { + uint32_t table; + switch (tableType) { + case RouteController::INTERFACE: { + table = getRouteTableForInterface(interface, isLocal); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + break; + } + case RouteController::LOCAL_NETWORK: { + table = ROUTE_TABLE_LOCAL_NETWORK; + break; + } + case RouteController::LEGACY_NETWORK: { + table = ROUTE_TABLE_LEGACY_NETWORK; + break; + } + case RouteController::LEGACY_SYSTEM: { + table = ROUTE_TABLE_LEGACY_SYSTEM; + break; + } + } + + int ret = modifyIpRoute(action, flags, table, interface, destination, nexthop, mtu, priority); + // Trying to add a route that already exists shouldn't cause an error. + if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) { + return ret; + } + + return 0; +} + +static void maybeModifyQdiscClsact(const char* interface, bool add) { + // The clsact attaching of v4- tun interface is triggered by ClatdController::maybeStartBpf + // because the clat is started before the v4- interface is added to the network and the + // clat startup needs to add {in, e}gress filters. + // TODO: remove this workaround once v4- tun interface clsact attaching is moved out from + // ClatdController::maybeStartBpf. + if (StartsWith(interface, "v4-") && add) return; + + // The interface may have already gone away in the delete case. + uint32_t ifindex = RouteController::ifNameToIndexFunction(interface); + if (!ifindex) { + ALOGE("cannot find interface %s", interface); + return; + } + + if (add) { + if (int ret = tcQdiscAddDevClsact(ifindex)) { + ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", ifindex, interface, strerror(-ret)); + return; + } + } else { + if (int ret = tcQdiscDelDevClsact(ifindex)) { + ALOGE("tcQdiscDelDevClsact(%d[%s]) failure: %s", ifindex, interface, strerror(-ret)); + return; + } + } + + return; +} + +[[nodiscard]] static int clearTetheringRules(const char* inputInterface) { + int ret = 0; + while (ret == 0) { + ret = modifyIpRule(RTM_DELRULE, RULE_PRIORITY_TETHERING, 0, MARK_UNSET, MARK_UNSET, + inputInterface, OIF_NONE, INVALID_UID, INVALID_UID); + } + + if (ret == -ENOENT) { + return 0; + } else { + return ret; + } +} + +uint32_t getRulePriority(const nlmsghdr *nlh) { + return getRtmU32Attribute(nlh, FRA_PRIORITY); +} + +uint32_t getRouteTable(const nlmsghdr *nlh) { + return getRtmU32Attribute(nlh, RTA_TABLE); +} + +[[nodiscard]] static int flushRules() { + NetlinkDumpFilter shouldDelete = [] (nlmsghdr *nlh) { + // Don't touch rules at priority 0 because by default they are used for local input. + return getRulePriority(nlh) != 0; + }; + return rtNetlinkFlush(RTM_GETRULE, RTM_DELRULE, "rules", shouldDelete); +} + +int RouteController::flushRoutes(uint32_t table) { + NetlinkDumpFilter shouldDelete = [table] (nlmsghdr *nlh) { + return getRouteTable(nlh) == table; + }; + + return rtNetlinkFlush(RTM_GETROUTE, RTM_DELROUTE, "routes", shouldDelete); +} + +int RouteController::flushRoutes(const char* interface) { + // Try to flush both local and global routing tables. + // + // Flush local first because flush global routing tables may erase the sInterfaceToTable map. + // Then the fake _local interface will be unable to find the index because the local + // interface depends physical interface to find the correct index. + int ret = flushRoutes(interface, true); + ret |= flushRoutes(interface, false); + return ret; +} + +// Returns 0 on success or negative errno on failure. +int RouteController::flushRoutes(const char* interface, bool local) { + std::lock_guard lock(sInterfaceToTableLock); + + uint32_t table = getRouteTableForInterfaceLocked(interface, local); + if (table == RT_TABLE_UNSPEC) { + return -ESRCH; + } + + int ret = flushRoutes(table); + + // If we failed to flush routes, the caller may elect to keep this interface around, so keep + // track of its name. + // Skip erasing local fake interface since it does not exist in sInterfaceToTable. + if (ret == 0 && !local) { + sInterfaceToTable.erase(interface); + } + + return ret; +} + +int RouteController::Init(unsigned localNetId) { + if (int ret = flushRules()) { + return ret; + } + if (int ret = addLegacyRouteRules()) { + return ret; + } + if (int ret = addLocalNetworkRules(localNetId)) { + return ret; + } + if (int ret = addUnreachableRule()) { + return ret; + } + // Don't complain if we can't add the dummy network, since not all devices support it. + configureDummyNetwork(); + + updateTableNamesFile(); + return 0; +} + +int RouteController::addInterfaceToLocalNetwork(unsigned netId, const char* interface) { + if (int ret = modifyLocalNetwork(netId, interface, ACTION_ADD)) { + return ret; + } + std::lock_guard lock(sInterfaceToTableLock); + sInterfaceToTable[interface] = ROUTE_TABLE_LOCAL_NETWORK; + return 0; +} + +int RouteController::removeInterfaceFromLocalNetwork(unsigned netId, const char* interface) { + if (int ret = modifyLocalNetwork(netId, interface, ACTION_DEL)) { + return ret; + } + std::lock_guard lock(sInterfaceToTableLock); + sInterfaceToTable.erase(interface); + return 0; +} + +int RouteController::addInterfaceToPhysicalNetwork(unsigned netId, const char* interface, + Permission permission, + const UidRangeMap& uidRangeMap, bool local) { + if (int ret = modifyPhysicalNetwork(netId, interface, uidRangeMap, permission, ACTION_ADD, + MODIFY_NON_UID_BASED_RULES, local)) { + return ret; + } + + maybeModifyQdiscClsact(interface, ACTION_ADD); + updateTableNamesFile(); + + if (int ret = addFixedLocalRoutes(interface)) { + return ret; + } + + return 0; +} + +int RouteController::removeInterfaceFromPhysicalNetwork(unsigned netId, const char* interface, + Permission permission, + const UidRangeMap& uidRangeMap, + bool local) { + if (int ret = modifyPhysicalNetwork(netId, interface, uidRangeMap, permission, ACTION_DEL, + MODIFY_NON_UID_BASED_RULES, local)) { + return ret; + } + + if (int ret = flushRoutes(interface)) { + return ret; + } + + if (int ret = clearTetheringRules(interface)) { + return ret; + } + + maybeModifyQdiscClsact(interface, ACTION_DEL); + updateTableNamesFile(); + return 0; +} + +int RouteController::addInterfaceToVirtualNetwork(unsigned netId, const char* interface, + bool secure, const UidRangeMap& uidRangeMap, + bool excludeLocalRoutes) { + if (int ret = modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_ADD, + MODIFY_NON_UID_BASED_RULES, excludeLocalRoutes)) { + return ret; + } + updateTableNamesFile(); + return 0; +} + +int RouteController::removeInterfaceFromVirtualNetwork(unsigned netId, const char* interface, + bool secure, const UidRangeMap& uidRangeMap, + bool excludeLocalRoutes) { + if (int ret = modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_DEL, + MODIFY_NON_UID_BASED_RULES, excludeLocalRoutes)) { + return ret; + } + if (int ret = flushRoutes(interface)) { + return ret; + } + updateTableNamesFile(); + return 0; +} + +int RouteController::modifyPhysicalNetworkPermission(unsigned netId, const char* interface, + Permission oldPermission, + Permission newPermission, bool local) { + // Physical network rules either use permission bits or UIDs, but not both. + // So permission changes don't affect any UID-based rules. + UidRangeMap emptyUidRangeMap; + // Add the new rules before deleting the old ones, to avoid race conditions. + if (int ret = modifyPhysicalNetwork(netId, interface, emptyUidRangeMap, newPermission, + ACTION_ADD, MODIFY_NON_UID_BASED_RULES, local)) { + return ret; + } + return modifyPhysicalNetwork(netId, interface, emptyUidRangeMap, oldPermission, ACTION_DEL, + MODIFY_NON_UID_BASED_RULES, local); +} + +int RouteController::addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges) { + return modifyRejectNonSecureNetworkRule(uidRanges, true); +} + +int RouteController::removeUsersFromRejectNonSecureNetworkRule(const UidRanges& uidRanges) { + return modifyRejectNonSecureNetworkRule(uidRanges, false); +} + +int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure, + const UidRangeMap& uidRangeMap, + bool excludeLocalRoutes) { + return modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_ADD, + !MODIFY_NON_UID_BASED_RULES, excludeLocalRoutes); +} + +int RouteController::removeUsersFromVirtualNetwork(unsigned netId, const char* interface, + bool secure, const UidRangeMap& uidRangeMap, + bool excludeLocalRoutes) { + return modifyVirtualNetwork(netId, interface, uidRangeMap, secure, ACTION_DEL, + !MODIFY_NON_UID_BASED_RULES, excludeLocalRoutes); +} + +int RouteController::addInterfaceToDefaultNetwork(const char* interface, Permission permission) { + return modifyDefaultNetwork(RTM_NEWRULE, interface, permission); +} + +int RouteController::removeInterfaceFromDefaultNetwork(const char* interface, + Permission permission) { + return modifyDefaultNetwork(RTM_DELRULE, interface, permission); +} + +bool RouteController::isWithinIpv4LocalPrefix(const char* dst) { + for (IPPrefix addr : V4_LOCAL_PREFIXES) { + if (addr.contains(IPPrefix::forString(dst))) { + return true; + } + } + return false; +} + +bool RouteController::isLocalRoute(TableType tableType, const char* destination, + const char* nexthop) { + IPPrefix prefix = IPPrefix::forString(destination); + return nexthop == nullptr && tableType == RouteController::INTERFACE && + // Skip default route to prevent network being modeled as point-to-point interfaces. + ((prefix.family() == AF_INET6 && prefix != IPPrefix::forString("::/0")) || + // Skip adding non-target local network range. + (prefix.family() == AF_INET && isWithinIpv4LocalPrefix(destination))); +} + +int RouteController::addRoute(const char* interface, const char* destination, const char* nexthop, + TableType tableType, int mtu, int priority) { + if (int ret = modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, interface, destination, + nexthop, tableType, mtu, priority, false /* isLocal */)) { + return ret; + } + + if (isLocalRoute(tableType, destination, nexthop)) { + return modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, interface, destination, + nexthop, tableType, mtu, priority, true /* isLocal */); + } + + return 0; +} + +int RouteController::removeRoute(const char* interface, const char* destination, + const char* nexthop, TableType tableType, int priority) { + if (int ret = modifyRoute(RTM_DELROUTE, NETLINK_REQUEST_FLAGS, interface, destination, nexthop, + tableType, 0 /* mtu */, priority, false /* isLocal */)) { + return ret; + } + + if (isLocalRoute(tableType, destination, nexthop)) { + return modifyRoute(RTM_DELROUTE, NETLINK_REQUEST_FLAGS, interface, destination, nexthop, + tableType, 0 /* mtu */, priority, true /* isLocal */); + } + return 0; +} + +int RouteController::updateRoute(const char* interface, const char* destination, + const char* nexthop, TableType tableType, int mtu) { + if (int ret = modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_REPLACE_FLAGS, interface, destination, + nexthop, tableType, mtu, 0 /* priority */, false /* isLocal */)) { + return ret; + } + + if (isLocalRoute(tableType, destination, nexthop)) { + return modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_REPLACE_FLAGS, interface, destination, + nexthop, tableType, mtu, 0 /* priority */, true /* isLocal */); + } + return 0; +} + +int RouteController::enableTethering(const char* inputInterface, const char* outputInterface) { + return modifyTetheredNetwork(RTM_NEWRULE, inputInterface, outputInterface); +} + +int RouteController::disableTethering(const char* inputInterface, const char* outputInterface) { + return modifyTetheredNetwork(RTM_DELRULE, inputInterface, outputInterface); +} + +int RouteController::addVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface, + Permission permission) { + if (int ret = modifyVpnFallthroughRule(RTM_NEWRULE, vpnNetId, physicalInterface, permission)) { + return ret; + } + + return modifyVpnLocalExclusionRule(true /* add */, physicalInterface); +} + +int RouteController::removeVirtualNetworkFallthrough(unsigned vpnNetId, + const char* physicalInterface, + Permission permission) { + if (int ret = modifyVpnFallthroughRule(RTM_DELRULE, vpnNetId, physicalInterface, permission)) { + return ret; + } + + return modifyVpnLocalExclusionRule(false /* add */, physicalInterface); +} + +int RouteController::addUsersToPhysicalNetwork(unsigned netId, const char* interface, + const UidRangeMap& uidRangeMap, bool local) { + return modifyPhysicalNetwork(netId, interface, uidRangeMap, PERMISSION_NONE, ACTION_ADD, + !MODIFY_NON_UID_BASED_RULES, local); +} + +int RouteController::removeUsersFromPhysicalNetwork(unsigned netId, const char* interface, + const UidRangeMap& uidRangeMap, bool local) { + return modifyPhysicalNetwork(netId, interface, uidRangeMap, PERMISSION_NONE, ACTION_DEL, + !MODIFY_NON_UID_BASED_RULES, local); +} + +int RouteController::addUsersToUnreachableNetwork(unsigned netId, const UidRangeMap& uidRangeMap) { + return modifyUnreachableNetwork(netId, uidRangeMap, ACTION_ADD); +} + +int RouteController::removeUsersFromUnreachableNetwork(unsigned netId, + const UidRangeMap& uidRangeMap) { + return modifyUnreachableNetwork(netId, uidRangeMap, ACTION_DEL); +} + +// Protects sInterfaceToTable. +std::mutex RouteController::sInterfaceToTableLock; +std::map RouteController::sInterfaceToTable; + +} // namespace android::net diff --git a/aosp/system/vold/Utils.cpp b/aosp/system/vold/Utils.cpp new file mode 100644 index 000000000..53b2f69ca --- /dev/null +++ b/aosp/system/vold/Utils.cpp @@ -0,0 +1,1812 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "Utils.h" + +#include "Process.h" +#include "sehandle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef UMOUNT_NOFOLLOW +#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */ +#endif + +using namespace std::chrono_literals; +using android::base::EndsWith; +using android::base::ReadFileToString; +using android::base::StartsWith; +using android::base::StringPrintf; +using android::base::unique_fd; + +namespace android { +namespace vold { + +char* sBlkidContext = nullptr; +char* sBlkidUntrustedContext = nullptr; +char* sFsckContext = nullptr; +char* sFsckUntrustedContext = nullptr; + +bool sSleepOnUnmount = true; + +static const char* kBlkidPath = "/system/bin/blkid"; +static const char* kKeyPath = "/data/misc/vold"; + +static const char* kProcDevices = "/proc/devices"; +static const char* kProcFilesystems = "/proc/filesystems"; + +static const char* kAndroidDir = "/Android/"; +static const char* kAppDataDir = "/Android/data/"; +static const char* kAppMediaDir = "/Android/media/"; +static const char* kAppObbDir = "/Android/obb/"; + +static const char* kMediaProviderCtx = "u:r:mediaprovider:"; +static const char* kMediaProviderAppCtx = "u:r:mediaprovider_app:"; + +// Lock used to protect process-level SELinux changes from racing with each +// other between multiple threads. +static std::mutex kSecurityLock; + +std::string GetFuseMountPathForUser(userid_t user_id, const std::string& relative_upper_path) { + return StringPrintf("/mnt/user/%d/%s", user_id, relative_upper_path.c_str()); +} + +status_t CreateDeviceNode(const std::string& path, dev_t dev) { + std::lock_guard lock(kSecurityLock); + const char* cpath = path.c_str(); + auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); }); + auto secontext = std::unique_ptr(nullptr, freecon); + char* tmp_secontext; + + if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFBLK) == 0) { + secontext.reset(tmp_secontext); + if (setfscreatecon(secontext.get()) != 0) { + LOG(ERROR) << "Failed to setfscreatecon for device node " << path; + return -EINVAL; + } + } else if (errno == ENOENT) { + LOG(DEBUG) << "No selabel defined for device node " << path; + } else { + PLOG(ERROR) << "Failed to look up selabel for device node " << path; + return -errno; + } + + mode_t mode = 0660 | S_IFBLK; + if (mknod(cpath, mode, dev) < 0) { + if (errno != EEXIST) { + PLOG(ERROR) << "Failed to create device node for " << major(dev) << ":" << minor(dev) + << " at " << path; + return -errno; + } + } + return OK; +} + +status_t DestroyDeviceNode(const std::string& path) { + const char* cpath = path.c_str(); + if (TEMP_FAILURE_RETRY(unlink(cpath))) { + return -errno; + } else { + return OK; + } +} + +// Sets a default ACL on the directory. +status_t SetDefaultAcl(const std::string& path, mode_t mode, uid_t uid, gid_t gid, + std::vector additionalGids) { + if (IsSdcardfsUsed()) { + // sdcardfs magically takes care of this + return OK; + } + + size_t num_entries = 3 + (additionalGids.size() > 0 ? additionalGids.size() + 1 : 0); + size_t size = sizeof(posix_acl_xattr_header) + num_entries * sizeof(posix_acl_xattr_entry); + auto buf = std::make_unique(size); + + posix_acl_xattr_header* acl_header = reinterpret_cast(buf.get()); + acl_header->a_version = POSIX_ACL_XATTR_VERSION; + + posix_acl_xattr_entry* entry = + reinterpret_cast(buf.get() + sizeof(posix_acl_xattr_header)); + + int tag_index = 0; + + entry[tag_index].e_tag = ACL_USER_OBJ; + // The existing mode_t mask has the ACL in the lower 9 bits: + // the lowest 3 for "other", the next 3 the group, the next 3 for the owner + // Use the mode_t masks to get these bits out, and shift them to get the + // correct value per entity. + // + // Eg if mode_t = 0700, rwx for the owner, then & S_IRWXU >> 6 results in 7 + entry[tag_index].e_perm = (mode & S_IRWXU) >> 6; + entry[tag_index].e_id = uid; + tag_index++; + + entry[tag_index].e_tag = ACL_GROUP_OBJ; + entry[tag_index].e_perm = (mode & S_IRWXG) >> 3; + entry[tag_index].e_id = gid; + tag_index++; + + if (additionalGids.size() > 0) { + for (gid_t additional_gid : additionalGids) { + entry[tag_index].e_tag = ACL_GROUP; + entry[tag_index].e_perm = (mode & S_IRWXG) >> 3; + entry[tag_index].e_id = additional_gid; + tag_index++; + } + + entry[tag_index].e_tag = ACL_MASK; + entry[tag_index].e_perm = (mode & S_IRWXG) >> 3; + entry[tag_index].e_id = 0; + tag_index++; + } + + entry[tag_index].e_tag = ACL_OTHER; + entry[tag_index].e_perm = mode & S_IRWXO; + entry[tag_index].e_id = 0; + + int ret = setxattr(path.c_str(), XATTR_NAME_POSIX_ACL_DEFAULT, acl_header, size, 0); + + if (ret != 0) { + PLOG(ERROR) << "Failed to set default ACL on " << path; + } + + return ret; +} + +int SetQuotaInherit(const std::string& path) { + unsigned int flags; + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path << " to set project id inheritance."; + return -1; + } + + int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags); + if (ret == -1) { + PLOG(ERROR) << "Failed to get flags for " << path << " to set project id inheritance."; + return ret; + } + + flags |= FS_PROJINHERIT_FL; + + ret = ioctl(fd, FS_IOC_SETFLAGS, &flags); + if (ret == -1) { + PLOG(ERROR) << "Failed to set flags for " << path << " to set project id inheritance."; + return ret; + } + + return 0; +} + +int SetQuotaProjectId(const std::string& path, long projectId) { + struct fsxattr fsx; + + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path << " to set project id."; + return -1; + } + + int ret = ioctl(fd, FS_IOC_FSGETXATTR, &fsx); + if (ret == -1) { + PLOG(ERROR) << "Failed to get extended attributes for " << path << " to get project id."; + return ret; + } + + fsx.fsx_projid = projectId; + ret = ioctl(fd, FS_IOC_FSSETXATTR, &fsx); + if (ret == -1) { + PLOG(ERROR) << "Failed to set project id on " << path; + // return ret; + } + return 0; +} + +int PrepareDirWithProjectId(const std::string& path, mode_t mode, uid_t uid, gid_t gid, + long projectId) { + int ret = fs_prepare_dir(path.c_str(), mode, uid, gid); + + if (ret != 0) { + return ret; + } + + if (!IsSdcardfsUsed()) { + ret = SetQuotaProjectId(path, projectId); + } + + return ret; +} + +static int FixupAppDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid, long projectId) { + namespace fs = std::filesystem; + + // Setup the directory itself correctly + int ret = PrepareDirWithProjectId(path, mode, uid, gid, projectId); + if (ret != OK) { + return ret; + } + + // Fixup all of its file entries + for (const auto& itEntry : fs::directory_iterator(path)) { + ret = lchown(itEntry.path().c_str(), uid, gid); + if (ret != 0) { + return ret; + } + + ret = chmod(itEntry.path().c_str(), mode); + if (ret != 0) { + return ret; + } + + if (!IsSdcardfsUsed()) { + ret = SetQuotaProjectId(itEntry.path(), projectId); + if (ret != 0) { + return ret; + } + } + } + + return OK; +} + +int PrepareAppDirFromRoot(const std::string& path, const std::string& root, int appUid, + bool fixupExisting) { + long projectId; + size_t pos; + int ret = 0; + bool sdcardfsSupport = IsSdcardfsUsed(); + + // Make sure the Android/ directories exist and are setup correctly + ret = PrepareAndroidDirs(root); + if (ret != 0) { + LOG(ERROR) << "Failed to prepare Android/ directories."; + return ret; + } + + // Now create the application-specific subdir(s) + // path is something like /data/media/0/Android/data/com.foo/files + // First, chop off the volume root, eg /data/media/0 + std::string pathFromRoot = path.substr(root.length()); + + uid_t uid = appUid; + gid_t gid = AID_MEDIA_RW; + std::vector additionalGids; + std::string appDir; + + // Check that the next part matches one of the allowed Android/ dirs + if (StartsWith(pathFromRoot, kAppDataDir)) { + appDir = kAppDataDir; + if (!sdcardfsSupport) { + gid = AID_EXT_DATA_RW; + // Also add the app's own UID as a group; since apps belong to a group + // that matches their UID, this ensures that they will always have access to + // the files created in these dirs, even if they are created by other processes + additionalGids.push_back(uid); + } + } else if (StartsWith(pathFromRoot, kAppMediaDir)) { + appDir = kAppMediaDir; + if (!sdcardfsSupport) { + gid = AID_MEDIA_RW; + } + } else if (StartsWith(pathFromRoot, kAppObbDir)) { + appDir = kAppObbDir; + if (!sdcardfsSupport) { + gid = AID_EXT_OBB_RW; + // See comments for kAppDataDir above + additionalGids.push_back(uid); + } + } else { + LOG(ERROR) << "Invalid application directory: " << path; + return -EINVAL; + } + + // mode = 770, plus sticky bit on directory to inherit GID when apps + // create subdirs + mode_t mode = S_IRWXU | S_IRWXG | S_ISGID; + // the project ID for application-specific directories is directly + // derived from their uid + + // Chop off the generic application-specific part, eg /Android/data/ + // this leaves us with something like com.foo/files/ + std::string leftToCreate = pathFromRoot.substr(appDir.length()); + if (!EndsWith(leftToCreate, "/")) { + leftToCreate += "/"; + } + std::string pathToCreate = root + appDir; + int depth = 0; + // Derive initial project ID + if (appDir == kAppDataDir || appDir == kAppMediaDir) { + projectId = uid - AID_APP_START + PROJECT_ID_EXT_DATA_START; + } else if (appDir == kAppObbDir) { + projectId = uid - AID_APP_START + PROJECT_ID_EXT_OBB_START; + } + + while ((pos = leftToCreate.find('/')) != std::string::npos) { + std::string component = leftToCreate.substr(0, pos + 1); + leftToCreate = leftToCreate.erase(0, pos + 1); + pathToCreate = pathToCreate + component; + + if (appDir == kAppDataDir && depth == 1 && component == "cache/") { + // All dirs use the "app" project ID, except for the cache dirs in + // Android/data, eg Android/data/com.foo/cache + // Note that this "sticks" - eg subdirs of this dir need the same + // project ID. + projectId = uid - AID_APP_START + PROJECT_ID_EXT_CACHE_START; + } + + if (fixupExisting && access(pathToCreate.c_str(), F_OK) == 0) { + // Fixup all files in this existing directory with the correct UID/GID + // and project ID. + ret = FixupAppDir(pathToCreate, mode, uid, gid, projectId); + } else { + ret = PrepareDirWithProjectId(pathToCreate, mode, uid, gid, projectId); + } + + if (ret != 0) { + return ret; + } + + if (depth == 0) { + // Set the default ACL on the top-level application-specific directories, + // to ensure that even if applications run with a umask of 0077, + // new directories within these directories will allow the GID + // specified here to write; this is necessary for apps like + // installers and MTP, that require access here. + // + // See man (5) acl for more details. + ret = SetDefaultAcl(pathToCreate, mode, uid, gid, additionalGids); + if (ret != 0) { + return ret; + } + + if (!sdcardfsSupport) { + // Set project ID inheritance, so that future subdirectories inherit the + // same project ID + ret = SetQuotaInherit(pathToCreate); + if (ret != 0) { + return ret; + } + } + } + + depth++; + } + + return OK; +} + +int SetAttrs(const std::string& path, unsigned int attrs) { + unsigned int flags; + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC))); + + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path; + return -1; + } + + if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) { + PLOG(ERROR) << "Failed to get flags for " << path; + return -1; + } + + if ((flags & attrs) == attrs) return 0; + flags |= attrs; + if (ioctl(fd, FS_IOC_SETFLAGS, &flags)) { + PLOG(ERROR) << "Failed to set flags for " << path << "(0x" << std::hex << attrs << ")"; + return -1; + } + return 0; +} + +status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid, + unsigned int attrs) { + std::lock_guard lock(kSecurityLock); + const char* cpath = path.c_str(); + auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); }); + auto secontext = std::unique_ptr(nullptr, freecon); + char* tmp_secontext; + + if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFDIR) == 0) { + secontext.reset(tmp_secontext); + if (setfscreatecon(secontext.get()) != 0) { + LOG(ERROR) << "Failed to setfscreatecon for directory " << path; + return -EINVAL; + } + } else if (errno == ENOENT) { + LOG(DEBUG) << "No selabel defined for directory " << path; + } else { + PLOG(ERROR) << "Failed to look up selabel for directory " << path; + return -errno; + } + + if (fs_prepare_dir(cpath, mode, uid, gid) != 0) return -errno; + if (attrs && SetAttrs(path, attrs) != 0) return -errno; + return OK; +} + +status_t ForceUnmount(const std::string& path) { + const char* cpath = path.c_str(); + if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) { + return OK; + } + // Apps might still be handling eject request, so wait before + // we start sending signals + if (sSleepOnUnmount) sleep(5); + + KillProcessesWithOpenFiles(path, SIGINT); + if (sSleepOnUnmount) sleep(5); + if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) { + return OK; + } + + KillProcessesWithOpenFiles(path, SIGTERM); + if (sSleepOnUnmount) sleep(5); + if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) { + return OK; + } + + KillProcessesWithOpenFiles(path, SIGKILL); + if (sSleepOnUnmount) sleep(5); + if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) { + return OK; + } + PLOG(INFO) << "ForceUnmount failed"; + return -errno; +} + +status_t KillProcessesWithTmpfsMountPrefix(const std::string& path) { + if (KillProcessesWithTmpfsMounts(path, SIGINT) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + if (KillProcessesWithTmpfsMounts(path, SIGTERM) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + if (KillProcessesWithTmpfsMounts(path, SIGKILL) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + // Send SIGKILL a second time to determine if we've + // actually killed everyone mount + if (KillProcessesWithTmpfsMounts(path, SIGKILL) == 0) { + return OK; + } + PLOG(ERROR) << "Failed to kill processes using " << path; + return -EBUSY; +} + +status_t KillProcessesUsingPath(const std::string& path) { + if (KillProcessesWithOpenFiles(path, SIGINT, false /* killFuseDaemon */) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + if (KillProcessesWithOpenFiles(path, SIGTERM, false /* killFuseDaemon */) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + if (KillProcessesWithOpenFiles(path, SIGKILL, false /* killFuseDaemon */) == 0) { + return OK; + } + if (sSleepOnUnmount) sleep(5); + + // Send SIGKILL a second time to determine if we've + // actually killed everyone with open files + // This time, we also kill the FUSE daemon if found + if (KillProcessesWithOpenFiles(path, SIGKILL, true /* killFuseDaemon */) == 0) { + return OK; + } + PLOG(ERROR) << "Failed to kill processes using " << path; + return -EBUSY; +} + +status_t BindMount(const std::string& source, const std::string& target) { + if (UnmountTree(target) < 0) { + return -errno; + } + if (TEMP_FAILURE_RETRY(mount(source.c_str(), target.c_str(), nullptr, MS_BIND, nullptr)) < 0) { + PLOG(ERROR) << "Failed to bind mount " << source << " to " << target; + return -errno; + } + return OK; +} + +status_t Symlink(const std::string& target, const std::string& linkpath) { + if (Unlink(linkpath) < 0) { + return -errno; + } + if (TEMP_FAILURE_RETRY(symlink(target.c_str(), linkpath.c_str())) < 0) { + PLOG(ERROR) << "Failed to create symlink " << linkpath << " to " << target; + return -errno; + } + return OK; +} + +status_t Unlink(const std::string& linkpath) { + if (TEMP_FAILURE_RETRY(unlink(linkpath.c_str())) < 0 && errno != EINVAL && errno != ENOENT) { + PLOG(ERROR) << "Failed to unlink " << linkpath; + return -errno; + } + return OK; +} + +status_t CreateDir(const std::string& dir, mode_t mode) { + struct stat sb; + if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) == 0) { + if (S_ISDIR(sb.st_mode)) { + return OK; + } else if (TEMP_FAILURE_RETRY(unlink(dir.c_str())) == -1) { + PLOG(ERROR) << "Failed to unlink " << dir; + return -errno; + } + } else if (errno != ENOENT) { + PLOG(ERROR) << "Failed to stat " << dir; + return -errno; + } + if (TEMP_FAILURE_RETRY(mkdir(dir.c_str(), mode)) == -1 && errno != EEXIST) { + PLOG(ERROR) << "Failed to mkdir " << dir; + return -errno; + } + return OK; +} + +bool FindValue(const std::string& raw, const std::string& key, std::string* value) { + auto qual = key + "=\""; + size_t start = 0; + while (true) { + start = raw.find(qual, start); + if (start == std::string::npos) return false; + if (start == 0 || raw[start - 1] == ' ') { + break; + } + start += 1; + } + start += qual.length(); + + auto end = raw.find("\"", start); + if (end == std::string::npos) return false; + + *value = raw.substr(start, end - start); + return true; +} + +static status_t readMetadata(const std::string& path, std::string* fsType, std::string* fsUuid, + std::string* fsLabel, bool untrusted) { + fsType->clear(); + fsUuid->clear(); + fsLabel->clear(); + + std::vector cmd; + cmd.push_back(kBlkidPath); + cmd.push_back("-c"); + cmd.push_back("/dev/null"); + cmd.push_back("-s"); + cmd.push_back("TYPE"); + cmd.push_back("-s"); + cmd.push_back("UUID"); + cmd.push_back("-s"); + cmd.push_back("LABEL"); + cmd.push_back(path); + + std::vector output; + status_t res = ForkExecvp(cmd, &output, untrusted ? sBlkidUntrustedContext : sBlkidContext); + if (res != OK) { + LOG(WARNING) << "blkid failed to identify " << path; + return res; + } + + for (const auto& line : output) { + // Extract values from blkid output, if defined + FindValue(line, "TYPE", fsType); + FindValue(line, "UUID", fsUuid); + FindValue(line, "LABEL", fsLabel); + } + + return OK; +} + +status_t ReadMetadata(const std::string& path, std::string* fsType, std::string* fsUuid, + std::string* fsLabel) { + return readMetadata(path, fsType, fsUuid, fsLabel, false); +} + +status_t ReadMetadataUntrusted(const std::string& path, std::string* fsType, std::string* fsUuid, + std::string* fsLabel) { + return readMetadata(path, fsType, fsUuid, fsLabel, true); +} + +static std::vector ConvertToArgv(const std::vector& args) { + std::vector argv; + argv.reserve(args.size() + 1); + for (const auto& arg : args) { + if (argv.empty()) { + LOG(DEBUG) << arg; + } else { + LOG(DEBUG) << " " << arg; + } + argv.emplace_back(arg.data()); + } + argv.emplace_back(nullptr); + return argv; +} + +static status_t ReadLinesFromFdAndLog(std::vector* output, + android::base::unique_fd ufd) { + std::unique_ptr fp(android::base::Fdopen(std::move(ufd), "r"), fclose); + if (!fp) { + PLOG(ERROR) << "fdopen in ReadLinesFromFdAndLog"; + return -errno; + } + if (output) output->clear(); + char line[1024]; + while (fgets(line, sizeof(line), fp.get()) != nullptr) { + LOG(DEBUG) << line; + if (output) output->emplace_back(line); + } + return OK; +} + +status_t ForkExecvp(const std::vector& args, std::vector* output, + char* context) { + auto argv = ConvertToArgv(args); + + android::base::unique_fd pipe_read, pipe_write; + if (!android::base::Pipe(&pipe_read, &pipe_write)) { + PLOG(ERROR) << "Pipe in ForkExecvp"; + return -errno; + } + + pid_t pid = fork(); + if (pid == 0) { + if (context) { + if (setexeccon(context)) { + LOG(ERROR) << "Failed to setexeccon in ForkExecvp"; + abort(); + } + } + pipe_read.reset(); + if (dup2(pipe_write.get(), STDOUT_FILENO) == -1) { + PLOG(ERROR) << "dup2 in ForkExecvp"; + _exit(EXIT_FAILURE); + } + pipe_write.reset(); + execvp(argv[0], const_cast(argv.data())); + PLOG(ERROR) << "exec in ForkExecvp"; + _exit(EXIT_FAILURE); + } + if (pid == -1) { + PLOG(ERROR) << "fork in ForkExecvp"; + return -errno; + } + + pipe_write.reset(); + auto st = ReadLinesFromFdAndLog(output, std::move(pipe_read)); + if (st != 0) return st; + + int status; + if (waitpid(pid, &status, 0) == -1) { + PLOG(ERROR) << "waitpid in ForkExecvp"; + return -errno; + } + if (!WIFEXITED(status)) { + LOG(ERROR) << "Process did not exit normally, status: " << status; + return -ECHILD; + } + if (WEXITSTATUS(status)) { + LOG(ERROR) << "Process exited with code: " << WEXITSTATUS(status); + return WEXITSTATUS(status); + } + return OK; +} + +status_t ForkExecvpTimeout(const std::vector& args, std::chrono::seconds timeout, + char* context) { + int status; + + pid_t wait_timeout_pid = fork(); + if (wait_timeout_pid == 0) { + pid_t pid = ForkExecvpAsync(args, context); + if (pid == -1) { + _exit(EXIT_FAILURE); + } + pid_t timer_pid = fork(); + if (timer_pid == 0) { + std::this_thread::sleep_for(timeout); + _exit(ETIMEDOUT); + } + if (timer_pid == -1) { + PLOG(ERROR) << "fork in ForkExecvpAsync_timeout"; + kill(pid, SIGTERM); + _exit(EXIT_FAILURE); + } + pid_t finished = wait(&status); + if (finished == pid) { + kill(timer_pid, SIGTERM); + } else { + kill(pid, SIGTERM); + } + if (!WIFEXITED(status)) { + _exit(ECHILD); + } + _exit(WEXITSTATUS(status)); + } + if (waitpid(wait_timeout_pid, &status, 0) == -1) { + PLOG(ERROR) << "waitpid in ForkExecvpAsync_timeout"; + return -errno; + } + if (!WIFEXITED(status)) { + LOG(ERROR) << "Process did not exit normally, status: " << status; + return -ECHILD; + } + if (WEXITSTATUS(status)) { + LOG(ERROR) << "Process exited with code: " << WEXITSTATUS(status); + return WEXITSTATUS(status); + } + return OK; +} + +pid_t ForkExecvpAsync(const std::vector& args, char* context) { + auto argv = ConvertToArgv(args); + + pid_t pid = fork(); + if (pid == 0) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + if (context) { + if (setexeccon(context)) { + LOG(ERROR) << "Failed to setexeccon in ForkExecvpAsync"; + abort(); + } + } + + execvp(argv[0], const_cast(argv.data())); + PLOG(ERROR) << "exec in ForkExecvpAsync"; + _exit(EXIT_FAILURE); + } + if (pid == -1) { + PLOG(ERROR) << "fork in ForkExecvpAsync"; + return -1; + } + return pid; +} + +status_t ReadRandomBytes(size_t bytes, std::string& out) { + out.resize(bytes); + return ReadRandomBytes(bytes, &out[0]); +} + +status_t ReadRandomBytes(size_t bytes, char* buf) { + int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (fd == -1) { + return -errno; + } + + ssize_t n; + while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], bytes))) > 0) { + bytes -= n; + buf += n; + } + close(fd); + + if (bytes == 0) { + return OK; + } else { + return -EIO; + } +} + +status_t GenerateRandomUuid(std::string& out) { + status_t res = ReadRandomBytes(16, out); + if (res == OK) { + out[6] &= 0x0f; /* clear version */ + out[6] |= 0x40; /* set to version 4 */ + out[8] &= 0x3f; /* clear variant */ + out[8] |= 0x80; /* set to IETF variant */ + } + return res; +} + +status_t HexToStr(const std::string& hex, std::string& str) { + str.clear(); + bool even = true; + char cur = 0; + for (size_t i = 0; i < hex.size(); i++) { + int val = 0; + switch (hex[i]) { + // clang-format off + case ' ': case '-': case ':': continue; + case 'f': case 'F': val = 15; break; + case 'e': case 'E': val = 14; break; + case 'd': case 'D': val = 13; break; + case 'c': case 'C': val = 12; break; + case 'b': case 'B': val = 11; break; + case 'a': case 'A': val = 10; break; + case '9': val = 9; break; + case '8': val = 8; break; + case '7': val = 7; break; + case '6': val = 6; break; + case '5': val = 5; break; + case '4': val = 4; break; + case '3': val = 3; break; + case '2': val = 2; break; + case '1': val = 1; break; + case '0': val = 0; break; + default: return -EINVAL; + // clang-format on + } + + if (even) { + cur = val << 4; + } else { + cur += val; + str.push_back(cur); + cur = 0; + } + even = !even; + } + return even ? OK : -EINVAL; +} + +static const char* kLookup = "0123456789abcdef"; + +status_t StrToHex(const std::string& str, std::string& hex) { + hex.clear(); + for (size_t i = 0; i < str.size(); i++) { + hex.push_back(kLookup[(str[i] & 0xF0) >> 4]); + hex.push_back(kLookup[str[i] & 0x0F]); + } + return OK; +} + +status_t StrToHex(const KeyBuffer& str, KeyBuffer& hex) { + hex.clear(); + for (size_t i = 0; i < str.size(); i++) { + hex.push_back(kLookup[(str.data()[i] & 0xF0) >> 4]); + hex.push_back(kLookup[str.data()[i] & 0x0F]); + } + return OK; +} + +status_t NormalizeHex(const std::string& in, std::string& out) { + std::string tmp; + if (HexToStr(in, tmp)) { + return -EINVAL; + } + return StrToHex(tmp, out); +} + +status_t GetBlockDevSize(int fd, uint64_t* size) { + if (ioctl(fd, BLKGETSIZE64, size)) { + return -errno; + } + + return OK; +} + +status_t GetBlockDevSize(const std::string& path, uint64_t* size) { + int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + status_t res = OK; + + if (fd < 0) { + return -errno; + } + + res = GetBlockDevSize(fd, size); + + close(fd); + + return res; +} + +status_t GetBlockDev512Sectors(const std::string& path, uint64_t* nr_sec) { + uint64_t size; + status_t res = GetBlockDevSize(path, &size); + + if (res != OK) { + return res; + } + + *nr_sec = size / 512; + + return OK; +} + +uint64_t GetFreeBytes(const std::string& path) { + struct statvfs sb; + if (statvfs(path.c_str(), &sb) == 0) { + return (uint64_t)sb.f_bavail * sb.f_frsize; + } else { + return -1; + } +} + +// TODO: borrowed from frameworks/native/libs/diskusage/ which should +// eventually be migrated into system/ +static int64_t stat_size(struct stat* s) { + int64_t blksize = s->st_blksize; + // count actual blocks used instead of nominal file size + int64_t size = s->st_blocks * 512; + + if (blksize) { + /* round up to filesystem block size */ + size = (size + blksize - 1) & (~(blksize - 1)); + } + + return size; +} + +// TODO: borrowed from frameworks/native/libs/diskusage/ which should +// eventually be migrated into system/ +int64_t calculate_dir_size(int dfd) { + int64_t size = 0; + struct stat s; + DIR* d; + struct dirent* de; + + d = fdopendir(dfd); + if (d == NULL) { + close(dfd); + return 0; + } + + while ((de = readdir(d))) { + const char* name = de->d_name; + if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) { + size += stat_size(&s); + } + if (de->d_type == DT_DIR) { + int subfd; + + /* always skip "." and ".." */ + if (IsDotOrDotDot(*de)) continue; + + subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (subfd >= 0) { + size += calculate_dir_size(subfd); + } + } + } + closedir(d); + return size; +} + +uint64_t GetTreeBytes(const std::string& path) { + int dirfd = open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dirfd < 0) { + PLOG(WARNING) << "Failed to open " << path; + return -1; + } else { + return calculate_dir_size(dirfd); + } +} + +// TODO: Use a better way to determine if it's media provider app. +bool IsFuseDaemon(const pid_t pid) { + auto path = StringPrintf("/proc/%d/mounts", pid); + char* tmp; + if (lgetfilecon(path.c_str(), &tmp) < 0) { + return false; + } + bool result = android::base::StartsWith(tmp, kMediaProviderAppCtx) + || android::base::StartsWith(tmp, kMediaProviderCtx); + freecon(tmp); + return result; +} + +bool IsFilesystemSupported(const std::string& fsType) { + std::string supported; + if (!ReadFileToString(kProcFilesystems, &supported)) { + PLOG(ERROR) << "Failed to read supported filesystems"; + return false; + } + return supported.find(fsType + "\n") != std::string::npos; +} + +bool IsSdcardfsUsed() { + return IsFilesystemSupported("sdcardfs") && + base::GetBoolProperty(kExternalStorageSdcardfs, true); +} + +status_t WipeBlockDevice(const std::string& path) { + status_t res = -1; + const char* c_path = path.c_str(); + uint64_t range[2] = {0, 0}; + + int fd = TEMP_FAILURE_RETRY(open(c_path, O_RDWR | O_CLOEXEC)); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path; + goto done; + } + + if (GetBlockDevSize(fd, &range[1]) != OK) { + PLOG(ERROR) << "Failed to determine size of " << path; + goto done; + } + + LOG(INFO) << "About to discard " << range[1] << " on " << path; + if (ioctl(fd, BLKDISCARD, &range) == 0) { + LOG(INFO) << "Discard success on " << path; + res = 0; + } else { + PLOG(ERROR) << "Discard failure on " << path; + } + +done: + close(fd); + return res; +} + +static bool isValidFilename(const std::string& name) { + if (name.empty() || (name == ".") || (name == "..") || (name.find('/') != std::string::npos)) { + return false; + } else { + return true; + } +} + +std::string BuildKeyPath(const std::string& partGuid) { + return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str()); +} + +std::string BuildDataSystemLegacyPath(userid_t userId) { + return StringPrintf("%s/system/users/%u", BuildDataPath("").c_str(), userId); +} + +std::string BuildDataSystemCePath(userid_t userId) { + return StringPrintf("%s/system_ce/%u", BuildDataPath("").c_str(), userId); +} + +std::string BuildDataSystemDePath(userid_t userId) { + return StringPrintf("%s/system_de/%u", BuildDataPath("").c_str(), userId); +} + +// Keep in sync with installd (frameworks/native/cmds/installd/utils.h) +std::string BuildDataProfilesDePath(userid_t userId) { + return StringPrintf("%s/misc/profiles/cur/%u", BuildDataPath("").c_str(), userId); +} + +std::string BuildDataVendorCePath(userid_t userId) { + return StringPrintf("%s/vendor_ce/%u", BuildDataPath("").c_str(), userId); +} + +std::string BuildDataVendorDePath(userid_t userId) { + return StringPrintf("%s/vendor_de/%u", BuildDataPath("").c_str(), userId); +} + +std::string BuildDataPath(const std::string& volumeUuid) { + // TODO: unify with installd path generation logic + if (volumeUuid.empty()) { + return "/data"; + } else { + CHECK(isValidFilename(volumeUuid)); + return StringPrintf("/mnt/expand/%s", volumeUuid.c_str()); + } +} + +std::string BuildDataMediaCePath(const std::string& volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + return StringPrintf("%s/media/%u", data.c_str(), userId); +} + +std::string BuildDataMiscCePath(const std::string& volumeUuid, userid_t userId) { + return StringPrintf("%s/misc_ce/%u", BuildDataPath(volumeUuid).c_str(), userId); +} + +std::string BuildDataMiscDePath(const std::string& volumeUuid, userid_t userId) { + return StringPrintf("%s/misc_de/%u", BuildDataPath(volumeUuid).c_str(), userId); +} + +std::string BuildDataUserCePath(const std::string& volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + return StringPrintf("%s/user/%u", data.c_str(), userId); +} + +std::string BuildDataUserDePath(const std::string& volumeUuid, userid_t userId) { + // TODO: unify with installd path generation logic + std::string data(BuildDataPath(volumeUuid)); + return StringPrintf("%s/user_de/%u", data.c_str(), userId); +} + +dev_t GetDevice(const std::string& path) { + struct stat sb; + if (stat(path.c_str(), &sb)) { + PLOG(WARNING) << "Failed to stat " << path; + return 0; + } else { + return sb.st_dev; + } +} + +// Returns true if |path| exists and is a symlink. +bool IsSymlink(const std::string& path) { + struct stat stbuf; + return lstat(path.c_str(), &stbuf) == 0 && S_ISLNK(stbuf.st_mode); +} + +// Returns true if |path1| names the same existing file or directory as |path2|. +bool IsSameFile(const std::string& path1, const std::string& path2) { + struct stat stbuf1, stbuf2; + if (stat(path1.c_str(), &stbuf1) != 0 || stat(path2.c_str(), &stbuf2) != 0) return false; + return stbuf1.st_ino == stbuf2.st_ino && stbuf1.st_dev == stbuf2.st_dev; +} + +status_t RestoreconRecursive(const std::string& path) { + LOG(DEBUG) << "Starting restorecon of " << path; + + static constexpr const char* kRestoreconString = "selinux.restorecon_recursive"; + + android::base::SetProperty(kRestoreconString, ""); + android::base::SetProperty(kRestoreconString, path); + + android::base::WaitForProperty(kRestoreconString, path); + + LOG(DEBUG) << "Finished restorecon of " << path; + return OK; +} + +bool Readlinkat(int dirfd, const std::string& path, std::string* result) { + // Shamelessly borrowed from android::base::Readlink() + result->clear(); + + // Most Linux file systems (ext2 and ext4, say) limit symbolic links to + // 4095 bytes. Since we'll copy out into the string anyway, it doesn't + // waste memory to just start there. We add 1 so that we can recognize + // whether it actually fit (rather than being truncated to 4095). + std::vector buf(4095 + 1); + while (true) { + ssize_t size = readlinkat(dirfd, path.c_str(), &buf[0], buf.size()); + // Unrecoverable error? + if (size == -1) return false; + // It fit! (If size == buf.size(), it may have been truncated.) + if (static_cast(size) < buf.size()) { + result->assign(&buf[0], size); + return true; + } + // Double our buffer and try again. + buf.resize(buf.size() * 2); + } +} + +static unsigned int GetMajorBlockVirtioBlk() { + std::string devices; + if (!ReadFileToString(kProcDevices, &devices)) { + PLOG(ERROR) << "Unable to open /proc/devices"; + return 0; + } + + bool blockSection = false; + for (auto line : android::base::Split(devices, "\n")) { + if (line == "Block devices:") { + blockSection = true; + } else if (line == "Character devices:") { + blockSection = false; + } else if (blockSection) { + auto tokens = android::base::Split(line, " "); + if (tokens.size() == 2 && tokens[1] == "virtblk") { + return std::stoul(tokens[0]); + } + } + } + + return 0; +} + +bool IsVirtioBlkDevice(unsigned int major) { + // Most virtualized platforms expose block devices with the virtio-blk + // block device driver. Unfortunately, this driver does not use a fixed + // major number, but relies on the kernel to assign one from a specific + // range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE" + // per Documentation/devices.txt. This is true even for the latest Linux + // kernel (4.4; see init() in drivers/block/virtio_blk.c). + static unsigned int kMajorBlockVirtioBlk = GetMajorBlockVirtioBlk(); + return kMajorBlockVirtioBlk && major == kMajorBlockVirtioBlk; +} + +status_t UnmountTree(const std::string& mountPoint) { + if (TEMP_FAILURE_RETRY(umount2(mountPoint.c_str(), MNT_DETACH)) < 0 && errno != EINVAL && + errno != ENOENT) { + PLOG(ERROR) << "Failed to unmount " << mountPoint; + return -errno; + } + return OK; +} + +bool IsDotOrDotDot(const struct dirent& ent) { + return strcmp(ent.d_name, ".") == 0 || strcmp(ent.d_name, "..") == 0; +} + +static status_t delete_dir_contents(DIR* dir) { + // Shamelessly borrowed from android::installd + int dfd = dirfd(dir); + if (dfd < 0) { + return -errno; + } + + status_t result = OK; + struct dirent* de; + while ((de = readdir(dir))) { + const char* name = de->d_name; + if (de->d_type == DT_DIR) { + /* always skip "." and ".." */ + if (IsDotOrDotDot(*de)) continue; + + android::base::unique_fd subfd( + openat(dfd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)); + if (subfd.get() == -1) { + PLOG(ERROR) << "Couldn't openat " << name; + result = -errno; + continue; + } + std::unique_ptr subdirp( + android::base::Fdopendir(std::move(subfd)), closedir); + if (!subdirp) { + PLOG(ERROR) << "Couldn't fdopendir " << name; + result = -errno; + continue; + } + result = delete_dir_contents(subdirp.get()); + if (unlinkat(dfd, name, AT_REMOVEDIR) < 0) { + PLOG(ERROR) << "Couldn't unlinkat " << name; + result = -errno; + } + } else { + if (unlinkat(dfd, name, 0) < 0) { + PLOG(ERROR) << "Couldn't unlinkat " << name; + result = -errno; + } + } + } + return result; +} + +status_t DeleteDirContentsAndDir(const std::string& pathname) { + status_t res = DeleteDirContents(pathname); + if (res < 0) { + return res; + } + if (TEMP_FAILURE_RETRY(rmdir(pathname.c_str())) < 0 && errno != ENOENT) { + PLOG(ERROR) << "rmdir failed on " << pathname; + return -errno; + } + LOG(VERBOSE) << "Success: rmdir on " << pathname; + return OK; +} + +status_t DeleteDirContents(const std::string& pathname) { + // Shamelessly borrowed from android::installd + std::unique_ptr dirp(opendir(pathname.c_str()), closedir); + if (!dirp) { + if (errno == ENOENT) { + return OK; + } + PLOG(ERROR) << "Failed to opendir " << pathname; + return -errno; + } + return delete_dir_contents(dirp.get()); +} + +// TODO(118708649): fix duplication with init/util.h +status_t WaitForFile(const char* filename, std::chrono::nanoseconds timeout) { + android::base::Timer t; + while (t.duration() < timeout) { + struct stat sb; + if (stat(filename, &sb) != -1) { + LOG(INFO) << "wait for '" << filename << "' took " << t; + return 0; + } + std::this_thread::sleep_for(10ms); + } + LOG(WARNING) << "wait for '" << filename << "' timed out and took " << t; + return -1; +} + +bool pathExists(const std::string& path) { + return access(path.c_str(), F_OK) == 0; +} + +bool FsyncDirectory(const std::string& dirname) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dirname.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << dirname; + return false; + } + if (fsync(fd) == -1) { + if (errno == EROFS || errno == EINVAL) { + PLOG(WARNING) << "Skip fsync " << dirname + << " on a file system does not support synchronization"; + } else { + PLOG(ERROR) << "Failed to fsync " << dirname; + return false; + } + } + return true; +} + +bool FsyncParentDirectory(const std::string& path) { + return FsyncDirectory(android::base::Dirname(path)); +} + +// Creates all parent directories of |path| that don't already exist. Assigns +// the specified |mode| to any new directories, and also fsync()s their parent +// directories so that the new directories get written to disk right away. +bool MkdirsSync(const std::string& path, mode_t mode) { + if (path[0] != '/') { + LOG(ERROR) << "MkdirsSync() needs an absolute path, but got " << path; + return false; + } + std::vector components = android::base::Split(android::base::Dirname(path), "/"); + + std::string current_dir = "/"; + for (const std::string& component : components) { + if (component.empty()) continue; + + std::string parent_dir = current_dir; + if (current_dir != "/") current_dir += "/"; + current_dir += component; + + if (!pathExists(current_dir)) { + if (mkdir(current_dir.c_str(), mode) != 0) { + PLOG(ERROR) << "Failed to create " << current_dir; + return false; + } + if (!FsyncDirectory(parent_dir)) return false; + LOG(DEBUG) << "Created directory " << current_dir; + } + } + return true; +} + +bool writeStringToFile(const std::string& payload, const std::string& filename) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + open(filename.c_str(), O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0666))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << filename; + return false; + } + if (!android::base::WriteStringToFd(payload, fd)) { + PLOG(ERROR) << "Failed to write to " << filename; + unlink(filename.c_str()); + return false; + } + // fsync as close won't guarantee flush data + // see close(2), fsync(2) and b/68901441 + if (fsync(fd) == -1) { + if (errno == EROFS || errno == EINVAL) { + PLOG(WARNING) << "Skip fsync " << filename + << " on a file system does not support synchronization"; + } else { + PLOG(ERROR) << "Failed to fsync " << filename; + unlink(filename.c_str()); + return false; + } + } + return true; +} + +status_t AbortFuseConnections() { + namespace fs = std::filesystem; + + static constexpr const char* kFuseConnections = "/sys/fs/fuse/connections"; + + std::error_code ec; + for (const auto& itEntry : fs::directory_iterator(kFuseConnections, ec)) { + std::string fsPath = itEntry.path().string() + "/filesystem"; + std::string fs; + + // Virtiofs is on top of fuse and there isn't any user space daemon. + // Android user space doesn't manage it. + if (android::base::ReadFileToString(fsPath, &fs, false) && + android::base::Trim(fs) == "virtiofs") { + LOG(INFO) << "Ignore virtiofs connection entry " << itEntry.path().string(); + continue; + } + + std::string abortPath = itEntry.path().string() + "/abort"; + LOG(DEBUG) << "Aborting fuse connection entry " << abortPath; + bool ret = writeStringToFile("1", abortPath); + if (!ret) { + LOG(WARNING) << "Failed to write to " << abortPath; + } + } + + if (ec) { + LOG(WARNING) << "Failed to iterate through " << kFuseConnections << ": " << ec.message(); + return -ec.value(); + } + + return OK; +} + +status_t EnsureDirExists(const std::string& path, mode_t mode, uid_t uid, gid_t gid) { + if (access(path.c_str(), F_OK) != 0) { + PLOG(WARNING) << "Dir does not exist: " << path; + if (fs_prepare_dir(path.c_str(), mode, uid, gid) != 0) { + return -errno; + } + } + return OK; +} + +// Gets the sysfs path for parameters of the backing device info (bdi) +static std::string getBdiPathForMount(const std::string& mount) { + // First figure out MAJOR:MINOR of mount. Simplest way is to stat the path. + struct stat info; + if (stat(mount.c_str(), &info) != 0) { + PLOG(ERROR) << "Failed to stat " << mount; + return ""; + } + unsigned int maj = major(info.st_dev); + unsigned int min = minor(info.st_dev); + + return StringPrintf("/sys/class/bdi/%u:%u", maj, min); +} + +// Configures max_ratio for the FUSE filesystem. +void ConfigureMaxDirtyRatioForFuse(const std::string& fuse_mount, unsigned int max_ratio) { + LOG(INFO) << "Configuring max_ratio of " << fuse_mount << " fuse filesystem to " << max_ratio; + if (max_ratio > 100) { + LOG(ERROR) << "Invalid max_ratio: " << max_ratio; + return; + } + std::string fuseBdiPath = getBdiPathForMount(fuse_mount); + if (fuseBdiPath == "") { + return; + } + std::string max_ratio_file = StringPrintf("%s/max_ratio", fuseBdiPath.c_str()); + unique_fd fd(TEMP_FAILURE_RETRY(open(max_ratio_file.c_str(), O_WRONLY | O_CLOEXEC))); + if (fd.get() == -1) { + PLOG(ERROR) << "Failed to open " << max_ratio_file; + return; + } + LOG(INFO) << "Writing " << max_ratio << " to " << max_ratio_file; + if (!WriteStringToFd(std::to_string(max_ratio), fd)) { + PLOG(ERROR) << "Failed to write to " << max_ratio_file; + } +} + +// Configures read ahead property of the fuse filesystem with the mount point |fuse_mount| by +// writing |read_ahead_kb| to the /sys/class/bdi/MAJOR:MINOR/read_ahead_kb. +void ConfigureReadAheadForFuse(const std::string& fuse_mount, size_t read_ahead_kb) { + LOG(INFO) << "Configuring read_ahead of " << fuse_mount << " fuse filesystem to " + << read_ahead_kb << "kb"; + std::string fuseBdiPath = getBdiPathForMount(fuse_mount); + if (fuseBdiPath == "") { + return; + } + // We found the bdi path for our filesystem, time to configure read ahead! + std::string read_ahead_file = StringPrintf("%s/read_ahead_kb", fuseBdiPath.c_str()); + unique_fd fd(TEMP_FAILURE_RETRY(open(read_ahead_file.c_str(), O_WRONLY | O_CLOEXEC))); + if (fd.get() == -1) { + PLOG(ERROR) << "Failed to open " << read_ahead_file; + return; + } + LOG(INFO) << "Writing " << read_ahead_kb << " to " << read_ahead_file; + if (!WriteStringToFd(std::to_string(read_ahead_kb), fd)) { + PLOG(ERROR) << "Failed to write to " << read_ahead_file; + } +} + +status_t MountUserFuse(userid_t user_id, const std::string& absolute_lower_path, + const std::string& relative_upper_path, android::base::unique_fd* fuse_fd) { + std::string pre_fuse_path(StringPrintf("/mnt/user/%d", user_id)); + std::string fuse_path( + StringPrintf("%s/%s", pre_fuse_path.c_str(), relative_upper_path.c_str())); + + std::string pre_pass_through_path(StringPrintf("/mnt/pass_through/%d", user_id)); + std::string pass_through_path( + StringPrintf("%s/%s", pre_pass_through_path.c_str(), relative_upper_path.c_str())); + + // Ensure that /mnt/user is 0700. With FUSE, apps don't need access to /mnt/user paths directly. + // Without FUSE however, apps need /mnt/user access so /mnt/user in init.rc is 0755 until here + auto result = PrepareDir("/mnt/user", 0750, AID_ROOT, AID_MEDIA_RW); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory /mnt/user"; + return -1; + } + + // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to + // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to + // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps) + // These bits should be consistent with what is set in zygote in + // com_android_internal_os_Zygote#MountEmulatedStorage on volume bind mount during app fork + result = PrepareDir(pre_fuse_path, 0710, user_id ? AID_ROOT : AID_SHELL, + multiuser_get_uid(user_id, AID_EVERYBODY)); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << pre_fuse_path; + return -1; + } + + result = PrepareDir(fuse_path, 0700, AID_ROOT, AID_ROOT); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << fuse_path; + return -1; + } + + result = PrepareDir(pre_pass_through_path, 0710, AID_ROOT, AID_MEDIA_RW); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << pre_pass_through_path; + return -1; + } + + result = PrepareDir(pass_through_path, 0710, AID_ROOT, AID_MEDIA_RW); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << pass_through_path; + return -1; + } + + if (relative_upper_path == "emulated") { + std::string linkpath(StringPrintf("/mnt/user/%d/self", user_id)); + result = PrepareDir(linkpath, 0755, AID_ROOT, AID_ROOT); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << linkpath; + return -1; + } + linkpath += "/primary"; + Symlink("/storage/emulated/" + std::to_string(user_id), linkpath); + + std::string pass_through_linkpath(StringPrintf("/mnt/pass_through/%d/self", user_id)); + result = PrepareDir(pass_through_linkpath, 0710, AID_ROOT, AID_MEDIA_RW); + if (result != android::OK) { + PLOG(ERROR) << "Failed to prepare directory " << pass_through_linkpath; + return -1; + } + pass_through_linkpath += "/primary"; + Symlink("/storage/emulated/" + std::to_string(user_id), pass_through_linkpath); + } + + // Open fuse fd. + fuse_fd->reset(open("/dev/fuse", O_RDWR | O_CLOEXEC)); + if (fuse_fd->get() == -1) { + PLOG(ERROR) << "Failed to open /dev/fuse"; + return -1; + } + + // Note: leaving out default_permissions since we don't want kernel to do lower filesystem + // permission checks before routing to FUSE daemon. + const auto opts = StringPrintf( + "fd=%i," + "rootmode=40000," + "allow_other," + "user_id=0,group_id=0,", + fuse_fd->get()); + + result = TEMP_FAILURE_RETRY(mount("/dev/fuse", fuse_path.c_str(), "fuse", + MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_LAZYTIME, + opts.c_str())); + if (result != 0) { + PLOG(ERROR) << "Failed to mount " << fuse_path; + return -errno; + } + + if (IsSdcardfsUsed()) { + std::string sdcardfs_path( + StringPrintf("/mnt/runtime/full/%s", relative_upper_path.c_str())); + + LOG(INFO) << "Bind mounting " << sdcardfs_path << " to " << pass_through_path; + return BindMount(sdcardfs_path, pass_through_path); + } else { + LOG(INFO) << "Bind mounting " << absolute_lower_path << " to " << pass_through_path; + return BindMount(absolute_lower_path, pass_through_path); + } +} + +status_t UnmountUserFuse(userid_t user_id, const std::string& absolute_lower_path, + const std::string& relative_upper_path) { + std::string fuse_path(StringPrintf("/mnt/user/%d/%s", user_id, relative_upper_path.c_str())); + std::string pass_through_path( + StringPrintf("/mnt/pass_through/%d/%s", user_id, relative_upper_path.c_str())); + + LOG(INFO) << "Unmounting fuse path " << fuse_path; + android::status_t result = ForceUnmount(fuse_path); + if (result != android::OK) { + // TODO(b/135341433): MNT_DETACH is needed for fuse because umount2 can fail with EBUSY. + // Figure out why we get EBUSY and remove this special casing if possible. + PLOG(ERROR) << "Failed to unmount. Trying MNT_DETACH " << fuse_path << " ..."; + if (umount2(fuse_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) && errno != EINVAL && + errno != ENOENT) { + PLOG(ERROR) << "Failed to unmount with MNT_DETACH " << fuse_path; + return -errno; + } + result = android::OK; + } + rmdir(fuse_path.c_str()); + + LOG(INFO) << "Unmounting pass_through_path " << pass_through_path; + auto status = ForceUnmount(pass_through_path); + if (status != android::OK) { + LOG(ERROR) << "Failed to unmount " << pass_through_path; + } + rmdir(pass_through_path.c_str()); + + return result; +} + +status_t PrepareAndroidDirs(const std::string& volumeRoot) { + std::string androidDir = volumeRoot + kAndroidDir; + std::string androidDataDir = volumeRoot + kAppDataDir; + std::string androidObbDir = volumeRoot + kAppObbDir; + std::string androidMediaDir = volumeRoot + kAppMediaDir; + + bool useSdcardFs = IsSdcardfsUsed(); + + // mode 0771 + sticky bit for inheriting GIDs + mode_t mode = S_IRWXU | S_IRWXG | S_IXOTH | S_ISGID; + if (fs_prepare_dir(androidDir.c_str(), mode, AID_MEDIA_RW, AID_MEDIA_RW) != 0) { + PLOG(ERROR) << "Failed to create " << androidDir; + return -errno; + } + + gid_t dataGid = useSdcardFs ? AID_MEDIA_RW : AID_EXT_DATA_RW; + if (fs_prepare_dir(androidDataDir.c_str(), mode, AID_MEDIA_RW, dataGid) != 0) { + PLOG(ERROR) << "Failed to create " << androidDataDir; + return -errno; + } + + gid_t obbGid = useSdcardFs ? AID_MEDIA_RW : AID_EXT_OBB_RW; + if (fs_prepare_dir(androidObbDir.c_str(), mode, AID_MEDIA_RW, obbGid) != 0) { + PLOG(ERROR) << "Failed to create " << androidObbDir; + return -errno; + } + // Some other apps, like installers, have write access to the OBB directory + // to pre-download them. To make sure newly created folders in this directory + // have the right permissions, set a default ACL. + SetDefaultAcl(androidObbDir, mode, AID_MEDIA_RW, obbGid, {}); + + if (fs_prepare_dir(androidMediaDir.c_str(), mode, AID_MEDIA_RW, AID_MEDIA_RW) != 0) { + PLOG(ERROR) << "Failed to create " << androidMediaDir; + return -errno; + } + + return OK; +} + +namespace ab = android::base; + +static ab::unique_fd openDirFd(int parentFd, const char* name) { + return ab::unique_fd{::openat(parentFd, name, O_CLOEXEC | O_DIRECTORY | O_PATH | O_NOFOLLOW)}; +} + +static ab::unique_fd openAbsolutePathFd(std::string_view path) { + if (path.empty() || path[0] != '/') { + errno = EINVAL; + return {}; + } + if (path == "/") { + return openDirFd(-1, "/"); + } + + // first component is special - it includes the leading slash + auto next = path.find('/', 1); + auto component = std::string(path.substr(0, next)); + if (component == "..") { + errno = EINVAL; + return {}; + } + auto fd = openDirFd(-1, component.c_str()); + if (!fd.ok()) { + return fd; + } + path.remove_prefix(std::min(next + 1, path.size())); + while (next != path.npos && !path.empty()) { + next = path.find('/'); + component.assign(path.substr(0, next)); + fd = openDirFd(fd, component.c_str()); + if (!fd.ok()) { + return fd; + } + path.remove_prefix(std::min(next + 1, path.size())); + } + return fd; +} + +std::pair OpenDirInProcfs(std::string_view path) { + auto fd = openAbsolutePathFd(path); + if (!fd.ok()) { + return {}; + } + + auto linkPath = std::string("/proc/self/fd/") += std::to_string(fd.get()); + return {std::move(fd), std::move(linkPath)}; +} + +static bool IsPropertySet(const char* name, bool& value) { + if (base::GetProperty(name, "") == "") return false; + + value = base::GetBoolProperty(name, false); + LOG(INFO) << "fuse-bpf is " << (value ? "enabled" : "disabled") << " because of property " + << name; + return true; +} + +bool IsFuseBpfEnabled() { + // This logic is reproduced in packages/providers/MediaProvider/jni/FuseDaemon.cpp + // so changes made here must be reflected there + bool enabled = false; + + if (IsPropertySet("ro.fuse.bpf.is_running", enabled)) return enabled; + + if (!IsPropertySet("persist.sys.fuse.bpf.override", enabled) && + !IsPropertySet("ro.fuse.bpf.enabled", enabled)) { + // If the kernel has fuse-bpf, /sys/fs/fuse/features/fuse_bpf will exist and have the + // contents 'supported\n' - see fs/fuse/inode.c in the kernel source + std::string contents; + const char* filename = "/sys/fs/fuse/features/fuse_bpf"; + if (!base::ReadFileToString(filename, &contents)) { + LOG(INFO) << "fuse-bpf is disabled because " << filename << " cannot be read"; + enabled = false; + } else if (contents == "supported\n") { + LOG(INFO) << "fuse-bpf is enabled because " << filename << " reads 'supported'"; + enabled = true; + } else { + LOG(INFO) << "fuse-bpf is disabled because " << filename + << " does not read 'supported'"; + enabled = false; + } + } + + std::string value = enabled ? "true" : "false"; + LOG(INFO) << "Setting ro.fuse.bpf.is_running to " << value; + base::SetProperty("ro.fuse.bpf.is_running", value); + return enabled; +} + +} // namespace vold +} // namespace android diff --git a/aosp/system/vold/model/EmulatedVolume.cpp b/aosp/system/vold/model/EmulatedVolume.cpp new file mode 100644 index 000000000..2431276ac --- /dev/null +++ b/aosp/system/vold/model/EmulatedVolume.cpp @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "EmulatedVolume.h" + +#include "AppFuseUtil.h" +#include "Utils.h" +#include "VolumeBase.h" +#include "VolumeManager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using android::base::StringPrintf; + +namespace android { +namespace vold { + +static const char* kSdcardFsPath = "/system/bin/sdcard"; + +EmulatedVolume::EmulatedVolume(const std::string& rawPath, int userId) + : VolumeBase(Type::kEmulated) { + setId(StringPrintf("emulated;%u", userId)); + mRawPath = rawPath; + mLabel = "emulated"; + mFuseMounted = false; + mUseSdcardFs = IsSdcardfsUsed(); + mAppDataIsolationEnabled = base::GetBoolProperty(kVoldAppDataIsolationEnabled, false); +} + +EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device, const std::string& fsUuid, + int userId) + : VolumeBase(Type::kEmulated) { + setId(StringPrintf("emulated:%u,%u;%u", major(device), minor(device), userId)); + mRawPath = rawPath; + mLabel = fsUuid; + mFuseMounted = false; + mUseSdcardFs = IsSdcardfsUsed(); + mAppDataIsolationEnabled = base::GetBoolProperty(kVoldAppDataIsolationEnabled, false); +} + +EmulatedVolume::~EmulatedVolume() {} + +std::string EmulatedVolume::getLabel() const { + // We could have migrated storage to an adopted private volume, so always + // call primary storage "emulated" to avoid media rescans. + if (getMountFlags() & MountFlags::kPrimary) { + return "emulated"; + } else { + return mLabel; + } +} + +// Creates a bind mount from source to target +static status_t doFuseBindMount(const std::string& source, const std::string& target, + std::list& pathsToUnmount) { + LOG(INFO) << "Bind mounting " << source << " on " << target; + auto status = BindMount(source, target); + if (status != OK) { + return status; + } + LOG(INFO) << "Bind mounted " << source << " on " << target; + pathsToUnmount.push_front(target); + return OK; +} + +// Bind mounts the volume 'volume' onto this volume. +status_t EmulatedVolume::bindMountVolume(const EmulatedVolume& volume, + std::list& pathsToUnmount) { + int myUserId = getMountUserId(); + int volumeUserId = volume.getMountUserId(); + std::string label = volume.getLabel(); + + // eg /mnt/user/10/emulated/10 + std::string srcUserPath = GetFuseMountPathForUser(volumeUserId, label); + std::string srcPath = StringPrintf("%s/%d", srcUserPath.c_str(), volumeUserId); + // eg /mnt/user/0/emulated/10 + std::string dstUserPath = GetFuseMountPathForUser(myUserId, label); + std::string dstPath = StringPrintf("%s/%d", dstUserPath.c_str(), volumeUserId); + + auto status = doFuseBindMount(srcPath, dstPath, pathsToUnmount); + if (status == OK) { + // Store the mount path, so we can unmount it when this volume goes away + mSharedStorageMountPath = dstPath; + } + + return status; +} + +status_t EmulatedVolume::mountFuseBindMounts() { + std::string androidSource; + std::string label = getLabel(); + int userId = getMountUserId(); + std::list pathsToUnmount; + + auto unmounter = [&]() { + LOG(INFO) << "mountFuseBindMounts() unmount scope_guard running"; + for (const auto& path : pathsToUnmount) { + LOG(INFO) << "Unmounting " << path; + auto status = UnmountTree(path); + if (status != OK) { + LOG(INFO) << "Failed to unmount " << path; + } else { + LOG(INFO) << "Unmounted " << path; + } + } + }; + auto unmount_guard = android::base::make_scope_guard(unmounter); + + if (mUseSdcardFs) { + androidSource = StringPrintf("/mnt/runtime/default/%s/%d/Android", label.c_str(), userId); + } else { + androidSource = StringPrintf("/%s/%d/Android", mRawPath.c_str(), userId); + } + + status_t status = OK; + // Zygote will unmount these dirs if app data isolation is enabled, so apps + // cannot access these dirs directly. + std::string androidDataSource = StringPrintf("%s/data", androidSource.c_str()); + std::string androidDataTarget( + StringPrintf("/mnt/user/%d/%s/%d/Android/data", userId, label.c_str(), userId)); + status = doFuseBindMount(androidDataSource, androidDataTarget, pathsToUnmount); + if (status != OK) { + return status; + } + + std::string androidObbSource = StringPrintf("%s/obb", androidSource.c_str()); + std::string androidObbTarget( + StringPrintf("/mnt/user/%d/%s/%d/Android/obb", userId, label.c_str(), userId)); + status = doFuseBindMount(androidObbSource, androidObbTarget, pathsToUnmount); + if (status != OK) { + return status; + } + + // Installers get the same view as all other apps, with the sole exception that the + // OBB dirs (Android/obb) are writable to them. On sdcardfs devices, this requires + // a special bind mount, since app-private and OBB dirs share the same GID, but we + // only want to give access to the latter. + if (mUseSdcardFs) { + std::string obbSource(StringPrintf("/mnt/runtime/write/%s/%d/Android/obb", + label.c_str(), userId)); + std::string obbInstallerTarget(StringPrintf("/mnt/installer/%d/%s/%d/Android/obb", + userId, label.c_str(), userId)); + + status = doFuseBindMount(obbSource, obbInstallerTarget, pathsToUnmount); + if (status != OK) { + return status; + } + } + + // For users that share their volume with another user (eg a clone + // profile), the current mount setup can cause page cache inconsistency + // issues. Let's say this is user 10, and the user it shares storage with + // is user 0. + // Then: + // * The FUSE daemon for user 0 serves /mnt/user/0 + // * The FUSE daemon for user 10 serves /mnt/user/10 + // The emulated volume for user 10 would be located at two paths: + // /mnt/user/0/emulated/10 + // /mnt/user/10/emulated/10 + // Since these paths refer to the same files but are served by different FUSE + // daemons, this can result in page cache inconsistency issues. To prevent this, + // bind mount the relevant paths for the involved users: + // 1. /mnt/user/10/emulated/10 =B=> /mnt/user/0/emulated/10 + // 2. /mnt/user/0/emulated/0 =B=> /mnt/user/10/emulated/0 + // + // This will ensure that any access to the volume for a specific user always + // goes through a single FUSE daemon. + userid_t sharedStorageUserId = VolumeManager::Instance()->getSharedStorageUser(userId); + if (sharedStorageUserId != USER_UNKNOWN) { + auto filter_fn = [&](const VolumeBase& vol) { + if (vol.getState() != VolumeBase::State::kMounted) { + // The volume must be mounted + return false; + } + if (vol.getType() != VolumeBase::Type::kEmulated) { + return false; + } + if (vol.getMountUserId() != sharedStorageUserId) { + return false; + } + if ((vol.getMountFlags() & MountFlags::kPrimary) == 0) { + // We only care about the primary emulated volume, so not a private + // volume with an emulated volume stacked on top. + return false; + } + return true; + }; + auto vol = VolumeManager::Instance()->findVolumeWithFilter(filter_fn); + if (vol != nullptr) { + auto sharedVol = static_cast(vol.get()); + // Bind mount this volume in the other user's primary volume + status = sharedVol->bindMountVolume(*this, pathsToUnmount); + if (status != OK) { + return status; + } + // And vice-versa + status = bindMountVolume(*sharedVol, pathsToUnmount); + if (status != OK) { + return status; + } + } + } + unmount_guard.Disable(); + return OK; +} + +status_t EmulatedVolume::unmountFuseBindMounts() { + std::string label = getLabel(); + int userId = getMountUserId(); + + if (!mSharedStorageMountPath.empty()) { + LOG(INFO) << "Unmounting " << mSharedStorageMountPath; + auto status = UnmountTree(mSharedStorageMountPath); + if (status != OK) { + LOG(ERROR) << "Failed to unmount " << mSharedStorageMountPath; + } + mSharedStorageMountPath = ""; + } + if (mUseSdcardFs || mAppDataIsolationEnabled) { + std::string installerTarget( + StringPrintf("/mnt/installer/%d/%s/%d/Android/obb", userId, label.c_str(), userId)); + LOG(INFO) << "Unmounting " << installerTarget; + auto status = UnmountTree(installerTarget); + if (status != OK) { + LOG(ERROR) << "Failed to unmount " << installerTarget; + // Intentional continue to try to unmount the other bind mount + } + } + if (mAppDataIsolationEnabled) { + std::string obbTarget( StringPrintf("/mnt/androidwritable/%d/%s/%d/Android/obb", + userId, label.c_str(), userId)); + LOG(INFO) << "Unmounting " << obbTarget; + auto status = UnmountTree(obbTarget); + if (status != OK) { + LOG(ERROR) << "Failed to unmount " << obbTarget; + // Intentional continue to try to unmount the other bind mount + } + std::string dataTarget(StringPrintf("/mnt/androidwritable/%d/%s/%d/Android/data", + userId, label.c_str(), userId)); + LOG(INFO) << "Unmounting " << dataTarget; + status = UnmountTree(dataTarget); + if (status != OK) { + LOG(ERROR) << "Failed to unmount " << dataTarget; + // Intentional continue to try to unmount the other bind mount + } + } + + // When app data isolation is enabled, kill all apps that obb/ is mounted, otherwise we should + // umount the whole Android/ dir. + if (mAppDataIsolationEnabled) { + std::string appObbDir(StringPrintf("%s/%d/Android/obb", getPath().c_str(), userId)); + // Here we assume obb/data dirs is mounted as tmpfs, then it must be caused by + // app data isolation. + KillProcessesWithTmpfsMountPrefix(appObbDir); + } + + // Always unmount data and obb dirs as they are mounted to lowerfs for speeding up access. + std::string androidDataTarget( + StringPrintf("/mnt/user/%d/%s/%d/Android/data", userId, label.c_str(), userId)); + + LOG(INFO) << "Unmounting " << androidDataTarget; + auto status = UnmountTree(androidDataTarget); + if (status != OK) { + return status; + } + LOG(INFO) << "Unmounted " << androidDataTarget; + + std::string androidObbTarget( + StringPrintf("/mnt/user/%d/%s/%d/Android/obb", userId, label.c_str(), userId)); + + LOG(INFO) << "Unmounting " << androidObbTarget; + status = UnmountTree(androidObbTarget); + if (status != OK) { + return status; + } + LOG(INFO) << "Unmounted " << androidObbTarget; + return OK; +} + +status_t EmulatedVolume::unmountSdcardFs() { + if (!mUseSdcardFs || getMountUserId() != 0) { + // For sdcardfs, only unmount for user 0, since user 0 will always be running + // and the paths don't change for different users. + return OK; + } + + ForceUnmount(mSdcardFsDefault); + ForceUnmount(mSdcardFsRead); + ForceUnmount(mSdcardFsWrite); + ForceUnmount(mSdcardFsFull); + + rmdir(mSdcardFsDefault.c_str()); + rmdir(mSdcardFsRead.c_str()); + rmdir(mSdcardFsWrite.c_str()); + rmdir(mSdcardFsFull.c_str()); + + mSdcardFsDefault.clear(); + mSdcardFsRead.clear(); + mSdcardFsWrite.clear(); + mSdcardFsFull.clear(); + + return OK; +} + +status_t EmulatedVolume::doMount() { + std::string label = getLabel(); + bool isVisible = isVisibleForWrite(); + + mSdcardFsDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str()); + mSdcardFsRead = StringPrintf("/mnt/runtime/read/%s", label.c_str()); + mSdcardFsWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str()); + mSdcardFsFull = StringPrintf("/mnt/runtime/full/%s", label.c_str()); + + setInternalPath(mRawPath); + setPath(StringPrintf("/storage/%s", label.c_str())); + + if (fs_prepare_dir(mSdcardFsDefault.c_str(), 0700, AID_ROOT, AID_ROOT) || + fs_prepare_dir(mSdcardFsRead.c_str(), 0700, AID_ROOT, AID_ROOT) || + fs_prepare_dir(mSdcardFsWrite.c_str(), 0700, AID_ROOT, AID_ROOT) || + fs_prepare_dir(mSdcardFsFull.c_str(), 0700, AID_ROOT, AID_ROOT)) { + PLOG(ERROR) << getId() << " failed to create mount points"; + return -errno; + } + + dev_t before = GetDevice(mSdcardFsFull); + + // Mount sdcardfs regardless of FUSE, since we need it to bind-mount on top of the + // FUSE volume for various reasons. + if (mUseSdcardFs && getMountUserId() == 0) { + LOG(INFO) << "Executing sdcardfs"; + int sdcardFsPid; + if (!(sdcardFsPid = fork())) { + // clang-format off + if (execl(kSdcardFsPath, kSdcardFsPath, + "-u", "1023", // AID_MEDIA_RW + "-g", "1023", // AID_MEDIA_RW + "-m", + "-w", + "-G", + "-i", + "-o", + mRawPath.c_str(), + label.c_str(), + NULL)) { + // clang-format on + PLOG(ERROR) << "Failed to exec"; + } + + LOG(ERROR) << "sdcardfs exiting"; + _exit(1); + } + + if (sdcardFsPid == -1) { + PLOG(ERROR) << getId() << " failed to fork"; + return -errno; + } + nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME); + while (before == GetDevice(mSdcardFsFull)) { + LOG(DEBUG) << "Waiting for sdcardfs to spin up..."; + usleep(50000); // 50ms + + nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME); + if (nanoseconds_to_milliseconds(now - start) > 5000) { + LOG(WARNING) << "Timed out while waiting for sdcardfs to spin up"; + return -ETIMEDOUT; + } + } + /* sdcardfs will have exited already. The filesystem will still be running */ + TEMP_FAILURE_RETRY(waitpid(sdcardFsPid, nullptr, 0)); + sdcardFsPid = 0; + } + + if (isVisible) { + // Make sure we unmount sdcardfs if we bail out with an error below + auto sdcardfs_unmounter = [&]() { + LOG(INFO) << "sdcardfs_unmounter scope_guard running"; + unmountSdcardFs(); + }; + auto sdcardfs_guard = android::base::make_scope_guard(sdcardfs_unmounter); + + LOG(INFO) << "Mounting emulated fuse volume"; + android::base::unique_fd fd; + int user_id = getMountUserId(); + auto volumeRoot = getRootPath(); + + // Make sure Android/ dirs exist for bind mounting + status_t res = PrepareAndroidDirs(volumeRoot); + if (res != OK) { + LOG(ERROR) << "Failed to prepare Android/ directories"; + return res; + } + + res = MountUserFuse(user_id, getInternalPath(), label, &fd); + if (res != 0) { + PLOG(ERROR) << "Failed to mount emulated fuse volume"; + return res; + } + + mFuseMounted = true; + auto fuse_unmounter = [&]() { + LOG(INFO) << "fuse_unmounter scope_guard running"; + fd.reset(); + if (UnmountUserFuse(user_id, getInternalPath(), label) != OK) { + PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume"; + } + mFuseMounted = false; + }; + auto fuse_guard = android::base::make_scope_guard(fuse_unmounter); + + auto callback = getMountCallback(); + if (callback) { + bool is_ready = false; + callback->onVolumeChecking(std::move(fd), getPath(), getInternalPath(), &is_ready); + if (!is_ready) { + return -EIO; + } + } + + if (!IsFuseBpfEnabled()) { + // Only do the bind-mounts when we know for sure the FUSE daemon can resolve the path. + res = mountFuseBindMounts(); + if (res != OK) { + return res; + } + } + + ConfigureReadAheadForFuse(GetFuseMountPathForUser(user_id, label), 256u); + + // By default, FUSE has a max_dirty ratio of 1%. This means that out of + // all dirty pages in the system, only 1% is allowed to belong to any + // FUSE filesystem. The reason this is in place is that FUSE + // filesystems shouldn't be trusted by default; a FUSE filesystem could + // take up say 100% of dirty pages, and subsequently refuse to write + // them back to storage. The kernel will then apply rate-limiting, and + // block other tasks from writing. For this particular FUSE filesystem + // however, we trust the implementation, because it is a part of the + // Android platform. So use the default ratio of 100%. + // + // The reason we're setting this is that there's a suspicion that the + // kernel starts rate-limiting the FUSE filesystem under extreme + // memory pressure scenarios. While the kernel will only rate limit if + // the writeback can't keep up with the write rate, under extreme + // memory pressure the write rate may dip as well, in which case FUSE + // writes to a 1% max_ratio filesystem are throttled to an extreme amount. + // + // To prevent this, just give FUSE 40% max_ratio, meaning it can take + // up to 40% of all dirty pages in the system. + ConfigureMaxDirtyRatioForFuse(GetFuseMountPathForUser(user_id, label), 40u); + + // All mounts where successful, disable scope guards + sdcardfs_guard.Disable(); + fuse_guard.Disable(); + } + + return OK; +} + +status_t EmulatedVolume::doUnmount() { + int userId = getMountUserId(); + + // Kill all processes using the filesystem before we unmount it. If we + // unmount the filesystem first, most file system operations will return + // ENOTCONN until the unmount completes. This is an exotic and unusual + // error code and might cause broken behaviour in applications. + if (mFuseMounted) { + // For FUSE specifically, we have an emulated volume per user, so only kill + // processes using files from this particular user. + std::string user_path(StringPrintf("%s/%d", getPath().c_str(), getMountUserId())); + LOG(INFO) << "Killing all processes referencing " << user_path; + KillProcessesUsingPath(user_path); + } else { + KillProcessesUsingPath(getPath()); + } + + if (mFuseMounted) { + std::string label = getLabel(); + + if (!IsFuseBpfEnabled()) { + // Ignoring unmount return status because we do want to try to + // unmount the rest cleanly. + unmountFuseBindMounts(); + } + + if (UnmountUserFuse(userId, getInternalPath(), label) != OK) { + PLOG(INFO) << "UnmountUserFuse failed on emulated fuse volume"; + return -errno; + } + + mFuseMounted = false; + } + + return unmountSdcardFs(); +} + +std::string EmulatedVolume::getRootPath() const { + int user_id = getMountUserId(); + std::string volumeRoot = StringPrintf("%s/%d", getInternalPath().c_str(), user_id); + + return volumeRoot; +} + +} // namespace vold +} // namespace android diff --git a/aosp/vendor/common/android/scripts/hook_cpuinfo.sh b/aosp/vendor/common/android/scripts/hook_cpuinfo.sh deleted file mode 100644 index 8c78dc29c..000000000 --- a/aosp/vendor/common/android/scripts/hook_cpuinfo.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/system/bin/sh -# Copyright Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. - -sed -n '1,71p' /proc/cpuinfo > /system/cpuinfo_origin -cat /system/cpuinfo_origin |sed 's/^Features[[:blank:]]*:[[:blank:]].*$/Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt lpae evtstrm aes pmull sha1 sha2 crc32/g' > /system/cpuinfo_merge -rm -f /system/cpuinfo_origin -chmod 644 /system/cpuinfo_merge -umount /proc/cpuinfo -mount --bind /system/cpuinfo_merge /proc/cpuinfo -mount |grep cpuinfo diff --git a/aosp/vendor/common/products/product_common.mk b/aosp/vendor/common/products/product_common.mk index 16d8887e6..5993930c2 100644 --- a/aosp/vendor/common/products/product_common.mk +++ b/aosp/vendor/common/products/product_common.mk @@ -35,7 +35,7 @@ PRODUCT_PACKAGES += \ rild \ libreference-ril.kbox \ libril \ - audio.primary.kbox \ + audio.primary.kbox \ PRODUCT_PROPERTY_OVERRIDES += \ ro.hardware=kbox \ @@ -49,7 +49,7 @@ PRODUCT_PROPERTY_OVERRIDES += \ net.netmask= \ ro.hardware.width=720 \ ro.hardware.height=1280 \ - ro.hardware.fps=30 \ + ro.hardware.fps=30 \ ro.hardware.vulkan=radeon \ ro.product.platform=radeon \ ro.hardware.egl=mesa \ @@ -81,7 +81,6 @@ PRODUCT_COPY_FILES += \ frameworks/native/data/etc/android.hardware.usb.accessory.xml:system/etc/permissions/android.hardware.usb.accessory.xml \ vendor/common/android/etc/init.kbox.rc:root/init.kbox.rc \ vendor/common/android/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml \ - vendor/common/android/scripts/hook_cpuinfo.sh:root/hook_cpuinfo.sh \ device/generic/goldfish/camera/media/profiles.xml:system/etc/media_profiles.xml \ device/generic/goldfish/camera/media/codecs.xml:system/etc/media_codecs.xml \ -- Gitee