Loading init/README.md +4 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,10 @@ runs the service. seclabel or computed based on the service executable file security context. For native executables see libcutils android\_get\_control\_socket(). `enter_namespace <type> <path>` > Enters the namespace of type _type_ located at _path_. Only network namespaces are supported with _type_ set to "net". Note that only one namespace of a given _type_ may be entered. `file <path> <type>` > Open a file path and pass its fd to the launched process. _type_ must be "r", "w" or "rw". For native executables see libcutils Loading init/property_service.cpp +39 −11 Original line number Diff line number Diff line Loading @@ -95,6 +95,11 @@ uint32_t (*property_set)(const std::string& name, const std::string& value) = In void CreateSerializedPropertyInfo(); struct PropertyAuditData { const ucred* cr; const char* name; }; void property_init() { mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); CreateSerializedPropertyInfo(); Loading @@ -111,7 +116,7 @@ static bool CheckMacPerms(const std::string& name, const char* target_context, return false; } property_audit_data audit_data; PropertyAuditData audit_data; audit_data.name = name.c_str(); audit_data.cr = &cr; Loading Loading @@ -388,6 +393,35 @@ class SocketConnection { DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection); }; 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.<action> 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.<service> 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 HandlePropertySet(const std::string& name, const std::string& value, const std::string& source_context, const ucred& cr, std::string* error) { Loading @@ -397,15 +431,9 @@ uint32_t HandlePropertySet(const std::string& name, const std::string& value, } if (StartsWith(name, "ctl.")) { // ctl. properties have their name ctl.<action> 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.<service> to check permissions. auto control_string = "ctl." + value; const char* target_context = nullptr; const char* type = nullptr; property_info_area->GetPropertyInfo(control_string.c_str(), &target_context, &type); if (!CheckMacPerms(control_string, target_context, source_context.c_str(), cr)) { *error = StringPrintf("Unable to '%s' service %s", name.c_str() + 4, value.c_str()); 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; } Loading Loading @@ -737,7 +765,7 @@ void load_system_props() { } static int SelinuxAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) { property_audit_data* d = reinterpret_cast<property_audit_data*>(data); auto* d = reinterpret_cast<PropertyAuditData*>(data); if (!d || !d->name || !d->cr) { LOG(ERROR) << "AuditCallback invoked with null data arguments!"; Loading init/property_service.h +0 −5 Original line number Diff line number Diff line Loading @@ -24,11 +24,6 @@ namespace android { namespace init { struct property_audit_data { const ucred* cr; const char* name; }; extern uint32_t (*property_set)(const std::string& name, const std::string& value); uint32_t HandlePropertySet(const std::string& name, const std::string& value, Loading init/service.cpp +75 −17 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ #include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <hidl-util/FQName.h> #include <processgroup/processgroup.h> #include <selinux/selinux.h> Loading @@ -59,13 +60,13 @@ using android::base::Join; using android::base::ParseInt; using android::base::StartsWith; using android::base::StringPrintf; using android::base::unique_fd; using android::base::WriteStringToFile; namespace android { namespace init { static Result<std::string> ComputeContextFromExecutable(std::string& service_name, const std::string& service_path) { static Result<std::string> ComputeContextFromExecutable(const std::string& service_path) { std::string computed_context; char* raw_con = nullptr; Loading Loading @@ -101,36 +102,49 @@ static Result<std::string> ComputeContextFromExecutable(std::string& service_nam return computed_context; } static void SetUpPidNamespace(const std::string& service_name) { Result<Success> Service::SetUpMountNamespace() const { constexpr unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID; // It's OK to LOG(FATAL) in this function since it's running in the first // child process. // Recursively remount / as slave like zygote does so unmounting and mounting /proc // doesn't interfere with the parent namespace's /proc mount. This will also // prevent any other mounts/unmounts initiated by the service from interfering // with the parent namespace but will still allow mount events from the parent // namespace to propagate to the child. if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) { PLOG(FATAL) << "couldn't remount(/) recursively as slave for " << service_name; return ErrnoError() << "Could not remount(/) recursively as slave"; } // umount() then mount() /proc. // umount() then mount() /proc and/or /sys // Note that it is not sufficient to mount with MS_REMOUNT. if (namespace_flags_ & CLONE_NEWPID) { if (umount("/proc") == -1) { PLOG(FATAL) << "couldn't umount(/proc) for " << service_name; return ErrnoError() << "Could not umount(/proc)"; } if (mount("", "/proc", "proc", kSafeFlags, "") == -1) { PLOG(FATAL) << "couldn't mount(/proc) for " << service_name; return ErrnoError() << "Could not mount(/proc)"; } } bool remount_sys = std::any_of(namespaces_to_enter_.begin(), namespaces_to_enter_.end(), [](const auto& entry) { return entry.first == CLONE_NEWNET; }); if (remount_sys) { if (umount2("/sys", MNT_DETACH) == -1) { return ErrnoError() << "Could not umount(/sys)"; } if (mount("", "/sys", "sys", kSafeFlags, "") == -1) { return ErrnoError() << "Could not mount(/sys)"; } } return Success(); } if (prctl(PR_SET_NAME, service_name.c_str()) == -1) { PLOG(FATAL) << "couldn't set name for " << service_name; Result<Success> Service::SetUpPidNamespace() const { if (prctl(PR_SET_NAME, name_.c_str()) == -1) { return ErrnoError() << "Could not set name"; } pid_t child_pid = fork(); if (child_pid == -1) { PLOG(FATAL) << "couldn't fork init inside the PID namespace for " << service_name; return ErrnoError() << "Could not fork init inside the PID namespace"; } if (child_pid > 0) { Loading @@ -153,6 +167,20 @@ static void SetUpPidNamespace(const std::string& service_name) { } _exit(WEXITSTATUS(init_exitstatus)); } return Success(); } Result<Success> Service::EnterNamespaces() const { for (const auto& [nstype, path] : namespaces_to_enter_) { auto fd = unique_fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; if (!fd) { return ErrnoError() << "Could not open namespace at " << path; } if (setns(fd, nstype) == -1) { return ErrnoError() << "Could not setns() namespace at " << path; } } return Success(); } static bool ExpandArgsAndExecv(const std::vector<std::string>& args) { Loading Loading @@ -418,6 +446,20 @@ Result<Success> Service::ParseDisabled(const std::vector<std::string>& args) { return Success(); } Result<Success> Service::ParseEnterNamespace(const std::vector<std::string>& args) { if (args[1] != "net") { return Error() << "Init only supports entering network namespaces"; } if (!namespaces_to_enter_.empty()) { return Error() << "Only one network namespace may be entered"; } // Network namespaces require that /sys is remounted, otherwise the old adapters will still be // present. Therefore, they also require mount namespaces. namespace_flags_ |= CLONE_NEWNS; namespaces_to_enter_.emplace_back(CLONE_NEWNET, args[2]); return Success(); } Result<Success> Service::ParseGroup(const std::vector<std::string>& args) { auto gid = DecodeUid(args[1]); if (!gid) { Loading Loading @@ -682,6 +724,8 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const { {"console", {0, 1, &Service::ParseConsole}}, {"critical", {0, 0, &Service::ParseCritical}}, {"disabled", {0, 0, &Service::ParseDisabled}}, {"enter_namespace", {2, 2, &Service::ParseEnterNamespace}}, {"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}}, {"interface", {2, 2, &Service::ParseInterface}}, {"ioprio", {2, 2, &Service::ParseIoprio}}, Loading Loading @@ -783,7 +827,7 @@ Result<Success> Service::Start() { if (!seclabel_.empty()) { scon = seclabel_; } else { auto result = ComputeContextFromExecutable(name_, args_[0]); auto result = ComputeContextFromExecutable(args_[0]); if (!result) { return result.error(); } Loading @@ -802,10 +846,24 @@ Result<Success> Service::Start() { if (pid == 0) { umask(077); if (auto result = EnterNamespaces(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not enter namespaces: " << result.error(); } if (namespace_flags_ & CLONE_NEWNS) { if (auto result = SetUpMountNamespace(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not set up mount namespace: " << result.error(); } } if (namespace_flags_ & CLONE_NEWPID) { // This will fork again to run an init process inside the PID // namespace. SetUpPidNamespace(name_); if (auto result = SetUpPidNamespace(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not set up PID namespace: " << result.error(); } } for (const auto& [key, value] : environment_vars_) { Loading init/service.h +6 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,9 @@ class Service { using OptionParser = Result<Success> (Service::*)(const std::vector<std::string>& args); class OptionParserMap; Result<Success> SetUpMountNamespace() const; Result<Success> SetUpPidNamespace() const; Result<Success> EnterNamespaces() const; void NotifyStateChange(const std::string& new_state) const; void StopOrReset(int how); void ZapStdio() const; Loading @@ -136,6 +139,7 @@ class Service { Result<Success> ParseConsole(const std::vector<std::string>& args); Result<Success> ParseCritical(const std::vector<std::string>& args); Result<Success> ParseDisabled(const std::vector<std::string>& args); Result<Success> ParseEnterNamespace(const std::vector<std::string>& args); Result<Success> ParseGroup(const std::vector<std::string>& args); Result<Success> ParsePriority(const std::vector<std::string>& args); Result<Success> ParseInterface(const std::vector<std::string>& args); Loading Loading @@ -179,6 +183,8 @@ class Service { std::vector<gid_t> supp_gids_; CapSet capabilities_; unsigned namespace_flags_; // Pair of namespace type, path to namespace. std::vector<std::pair<int, std::string>> namespaces_to_enter_; std::string seclabel_; Loading Loading
init/README.md +4 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,10 @@ runs the service. seclabel or computed based on the service executable file security context. For native executables see libcutils android\_get\_control\_socket(). `enter_namespace <type> <path>` > Enters the namespace of type _type_ located at _path_. Only network namespaces are supported with _type_ set to "net". Note that only one namespace of a given _type_ may be entered. `file <path> <type>` > Open a file path and pass its fd to the launched process. _type_ must be "r", "w" or "rw". For native executables see libcutils Loading
init/property_service.cpp +39 −11 Original line number Diff line number Diff line Loading @@ -95,6 +95,11 @@ uint32_t (*property_set)(const std::string& name, const std::string& value) = In void CreateSerializedPropertyInfo(); struct PropertyAuditData { const ucred* cr; const char* name; }; void property_init() { mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); CreateSerializedPropertyInfo(); Loading @@ -111,7 +116,7 @@ static bool CheckMacPerms(const std::string& name, const char* target_context, return false; } property_audit_data audit_data; PropertyAuditData audit_data; audit_data.name = name.c_str(); audit_data.cr = &cr; Loading Loading @@ -388,6 +393,35 @@ class SocketConnection { DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection); }; 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.<action> 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.<service> 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 HandlePropertySet(const std::string& name, const std::string& value, const std::string& source_context, const ucred& cr, std::string* error) { Loading @@ -397,15 +431,9 @@ uint32_t HandlePropertySet(const std::string& name, const std::string& value, } if (StartsWith(name, "ctl.")) { // ctl. properties have their name ctl.<action> 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.<service> to check permissions. auto control_string = "ctl." + value; const char* target_context = nullptr; const char* type = nullptr; property_info_area->GetPropertyInfo(control_string.c_str(), &target_context, &type); if (!CheckMacPerms(control_string, target_context, source_context.c_str(), cr)) { *error = StringPrintf("Unable to '%s' service %s", name.c_str() + 4, value.c_str()); 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; } Loading Loading @@ -737,7 +765,7 @@ void load_system_props() { } static int SelinuxAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) { property_audit_data* d = reinterpret_cast<property_audit_data*>(data); auto* d = reinterpret_cast<PropertyAuditData*>(data); if (!d || !d->name || !d->cr) { LOG(ERROR) << "AuditCallback invoked with null data arguments!"; Loading
init/property_service.h +0 −5 Original line number Diff line number Diff line Loading @@ -24,11 +24,6 @@ namespace android { namespace init { struct property_audit_data { const ucred* cr; const char* name; }; extern uint32_t (*property_set)(const std::string& name, const std::string& value); uint32_t HandlePropertySet(const std::string& name, const std::string& value, Loading
init/service.cpp +75 −17 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ #include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <hidl-util/FQName.h> #include <processgroup/processgroup.h> #include <selinux/selinux.h> Loading @@ -59,13 +60,13 @@ using android::base::Join; using android::base::ParseInt; using android::base::StartsWith; using android::base::StringPrintf; using android::base::unique_fd; using android::base::WriteStringToFile; namespace android { namespace init { static Result<std::string> ComputeContextFromExecutable(std::string& service_name, const std::string& service_path) { static Result<std::string> ComputeContextFromExecutable(const std::string& service_path) { std::string computed_context; char* raw_con = nullptr; Loading Loading @@ -101,36 +102,49 @@ static Result<std::string> ComputeContextFromExecutable(std::string& service_nam return computed_context; } static void SetUpPidNamespace(const std::string& service_name) { Result<Success> Service::SetUpMountNamespace() const { constexpr unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID; // It's OK to LOG(FATAL) in this function since it's running in the first // child process. // Recursively remount / as slave like zygote does so unmounting and mounting /proc // doesn't interfere with the parent namespace's /proc mount. This will also // prevent any other mounts/unmounts initiated by the service from interfering // with the parent namespace but will still allow mount events from the parent // namespace to propagate to the child. if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) { PLOG(FATAL) << "couldn't remount(/) recursively as slave for " << service_name; return ErrnoError() << "Could not remount(/) recursively as slave"; } // umount() then mount() /proc. // umount() then mount() /proc and/or /sys // Note that it is not sufficient to mount with MS_REMOUNT. if (namespace_flags_ & CLONE_NEWPID) { if (umount("/proc") == -1) { PLOG(FATAL) << "couldn't umount(/proc) for " << service_name; return ErrnoError() << "Could not umount(/proc)"; } if (mount("", "/proc", "proc", kSafeFlags, "") == -1) { PLOG(FATAL) << "couldn't mount(/proc) for " << service_name; return ErrnoError() << "Could not mount(/proc)"; } } bool remount_sys = std::any_of(namespaces_to_enter_.begin(), namespaces_to_enter_.end(), [](const auto& entry) { return entry.first == CLONE_NEWNET; }); if (remount_sys) { if (umount2("/sys", MNT_DETACH) == -1) { return ErrnoError() << "Could not umount(/sys)"; } if (mount("", "/sys", "sys", kSafeFlags, "") == -1) { return ErrnoError() << "Could not mount(/sys)"; } } return Success(); } if (prctl(PR_SET_NAME, service_name.c_str()) == -1) { PLOG(FATAL) << "couldn't set name for " << service_name; Result<Success> Service::SetUpPidNamespace() const { if (prctl(PR_SET_NAME, name_.c_str()) == -1) { return ErrnoError() << "Could not set name"; } pid_t child_pid = fork(); if (child_pid == -1) { PLOG(FATAL) << "couldn't fork init inside the PID namespace for " << service_name; return ErrnoError() << "Could not fork init inside the PID namespace"; } if (child_pid > 0) { Loading @@ -153,6 +167,20 @@ static void SetUpPidNamespace(const std::string& service_name) { } _exit(WEXITSTATUS(init_exitstatus)); } return Success(); } Result<Success> Service::EnterNamespaces() const { for (const auto& [nstype, path] : namespaces_to_enter_) { auto fd = unique_fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)}; if (!fd) { return ErrnoError() << "Could not open namespace at " << path; } if (setns(fd, nstype) == -1) { return ErrnoError() << "Could not setns() namespace at " << path; } } return Success(); } static bool ExpandArgsAndExecv(const std::vector<std::string>& args) { Loading Loading @@ -418,6 +446,20 @@ Result<Success> Service::ParseDisabled(const std::vector<std::string>& args) { return Success(); } Result<Success> Service::ParseEnterNamespace(const std::vector<std::string>& args) { if (args[1] != "net") { return Error() << "Init only supports entering network namespaces"; } if (!namespaces_to_enter_.empty()) { return Error() << "Only one network namespace may be entered"; } // Network namespaces require that /sys is remounted, otherwise the old adapters will still be // present. Therefore, they also require mount namespaces. namespace_flags_ |= CLONE_NEWNS; namespaces_to_enter_.emplace_back(CLONE_NEWNET, args[2]); return Success(); } Result<Success> Service::ParseGroup(const std::vector<std::string>& args) { auto gid = DecodeUid(args[1]); if (!gid) { Loading Loading @@ -682,6 +724,8 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const { {"console", {0, 1, &Service::ParseConsole}}, {"critical", {0, 0, &Service::ParseCritical}}, {"disabled", {0, 0, &Service::ParseDisabled}}, {"enter_namespace", {2, 2, &Service::ParseEnterNamespace}}, {"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}}, {"interface", {2, 2, &Service::ParseInterface}}, {"ioprio", {2, 2, &Service::ParseIoprio}}, Loading Loading @@ -783,7 +827,7 @@ Result<Success> Service::Start() { if (!seclabel_.empty()) { scon = seclabel_; } else { auto result = ComputeContextFromExecutable(name_, args_[0]); auto result = ComputeContextFromExecutable(args_[0]); if (!result) { return result.error(); } Loading @@ -802,10 +846,24 @@ Result<Success> Service::Start() { if (pid == 0) { umask(077); if (auto result = EnterNamespaces(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not enter namespaces: " << result.error(); } if (namespace_flags_ & CLONE_NEWNS) { if (auto result = SetUpMountNamespace(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not set up mount namespace: " << result.error(); } } if (namespace_flags_ & CLONE_NEWPID) { // This will fork again to run an init process inside the PID // namespace. SetUpPidNamespace(name_); if (auto result = SetUpPidNamespace(); !result) { LOG(FATAL) << "Service '" << name_ << "' could not set up PID namespace: " << result.error(); } } for (const auto& [key, value] : environment_vars_) { Loading
init/service.h +6 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,9 @@ class Service { using OptionParser = Result<Success> (Service::*)(const std::vector<std::string>& args); class OptionParserMap; Result<Success> SetUpMountNamespace() const; Result<Success> SetUpPidNamespace() const; Result<Success> EnterNamespaces() const; void NotifyStateChange(const std::string& new_state) const; void StopOrReset(int how); void ZapStdio() const; Loading @@ -136,6 +139,7 @@ class Service { Result<Success> ParseConsole(const std::vector<std::string>& args); Result<Success> ParseCritical(const std::vector<std::string>& args); Result<Success> ParseDisabled(const std::vector<std::string>& args); Result<Success> ParseEnterNamespace(const std::vector<std::string>& args); Result<Success> ParseGroup(const std::vector<std::string>& args); Result<Success> ParsePriority(const std::vector<std::string>& args); Result<Success> ParseInterface(const std::vector<std::string>& args); Loading Loading @@ -179,6 +183,8 @@ class Service { std::vector<gid_t> supp_gids_; CapSet capabilities_; unsigned namespace_flags_; // Pair of namespace type, path to namespace. std::vector<std::pair<int, std::string>> namespaces_to_enter_; std::string seclabel_; Loading