Run executables directly (#645)
- Apps can be launched directly from file paths (including Files app support) - Added executable detection to identify unsupported or invalid binaries before launch. - App startup is now streamlined through separate registered-app and direct-execution interfaces. - Existing app launch points were migrated to the updated startup APIs.
This commit is contained in:
committed by
GitHub
parent
643cbc3806
commit
a0b2ee7ebc
@@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/elf_check.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
|
||||
@@ -27,6 +28,36 @@ bool is_regular_file(const std::string& path) {
|
||||
return ::stat(path.c_str(), &path_stat) == 0 && S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
constexpr ElfRequirements EXECUTABLE_REQUIREMENTS = {
|
||||
.elf_class = ELF_CLASS_64,
|
||||
.data = ELF_DATA_2LSB,
|
||||
.type = ELF_TYPE_DYN,
|
||||
#if defined(__x86_64__)
|
||||
.machine = ELF_MACHINE_X86_64,
|
||||
#elif defined(__aarch64__)
|
||||
.machine = ELF_MACHINE_AARCH64,
|
||||
#else
|
||||
#error "Unsupported POSIX architecture for ELF machine check"
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
// Validates an already-resolved binary path (see resolve_app_path()) before it's handed to
|
||||
// dlopen(): the extension check is a cheap string comparison, so the file is only opened as a
|
||||
// last resort.
|
||||
bool is_executable_file(const std::string& resolved_path) {
|
||||
if (!resolved_path.ends_with(".so")) {
|
||||
return false;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
// The simulator's app binaries are Mach-O on macOS, not ELF, so there is no header to check.
|
||||
return is_regular_file(resolved_path);
|
||||
#else
|
||||
return elf_check_file(resolved_path.c_str(), &EXECUTABLE_REQUIREMENTS);
|
||||
#endif
|
||||
}
|
||||
|
||||
// location.location can be either an app's install directory or the .so file directly; the
|
||||
// former resolves to the per-architecture binary at {dir}/elf/posix-{TACTILITY_POSIX_ARCH}.so,
|
||||
// mirroring app_esp32_loader_service.cpp's resolve_elf_path().
|
||||
@@ -56,6 +87,11 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!is_executable_file(app_path)) {
|
||||
LOG_E(TAG, "Not executable: %s", app_path.c_str());
|
||||
return ERROR_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "Loading %s", app_path.c_str());
|
||||
|
||||
// RTLD_NOW: a missing symbol fails here, not mid-run(). RTLD_LOCAL: this app's own exported
|
||||
@@ -100,10 +136,24 @@ void api_unload(AppRuntime runtime_ptr) {
|
||||
delete runtime;
|
||||
}
|
||||
|
||||
bool api_is_executable(AppLocation location) {
|
||||
if (location.type != APP_LOCATION_PATH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string app_path;
|
||||
if (resolve_app_path(static_cast<const char*>(location.location), app_path) != ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_executable_file(app_path);
|
||||
}
|
||||
|
||||
AppLoaderApi loader_api = {
|
||||
.load = api_load,
|
||||
.run = api_run,
|
||||
.unload = api_unload,
|
||||
.is_executable = api_is_executable,
|
||||
};
|
||||
|
||||
void* create_service(const ServiceManifest*) {
|
||||
|
||||
@@ -11,6 +11,10 @@ add_library(app_posix_module_test_fixture SHARED EXCLUDE_FROM_ALL ${CMAKE_CURREN
|
||||
target_include_directories(app_posix_module_test_fixture PRIVATE ${CMAKE_SOURCE_DIR}/Modules/app-module/include)
|
||||
set_target_properties(app_posix_module_test_fixture PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# A file with a ".so" extension but no ELF header, for is_executable() rejection tests.
|
||||
set(NON_ELF_FIXTURE_PATH "${CMAKE_CURRENT_BINARY_DIR}/not-elf.so")
|
||||
file(WRITE "${NON_ELF_FIXTURE_PATH}" "not an elf file")
|
||||
|
||||
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp)
|
||||
add_executable(AppPosixModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
||||
add_dependencies(AppPosixModuleTests app_posix_module_test_fixture)
|
||||
@@ -18,6 +22,7 @@ add_dependencies(AppPosixModuleTests app_posix_module_test_fixture)
|
||||
target_include_directories(AppPosixModuleTests PRIVATE ${DOCTESTINC})
|
||||
target_compile_definitions(AppPosixModuleTests PRIVATE
|
||||
FIXTURE_APP_PATH="$<TARGET_FILE:app_posix_module_test_fixture>"
|
||||
FIXTURE_NON_ELF_PATH="${NON_ELF_FIXTURE_PATH}"
|
||||
)
|
||||
|
||||
add_test(NAME AppPosixModuleTests COMMAND AppPosixModuleTests)
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/execute.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
#include <service/manager.h>
|
||||
@@ -11,6 +13,7 @@
|
||||
#include <tactility/delay.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
extern ServiceManifest loader_service_manifest; // app-posix-module's own
|
||||
extern ServiceManifest app_internal_loader_service_manifest; // app-module's real memory loader
|
||||
@@ -29,6 +32,13 @@ void ensure_memory_loader_registered() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string directory_of(const std::string& path) {
|
||||
auto slash = path.find_last_of('/');
|
||||
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||
}
|
||||
|
||||
const std::string FIXTURE_DIR = directory_of(FIXTURE_APP_PATH);
|
||||
|
||||
bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ms) {
|
||||
uint32_t waited = 0;
|
||||
while (waited < timeout_ms) {
|
||||
@@ -41,6 +51,11 @@ bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_
|
||||
return app_manager_get_state(id) == target;
|
||||
}
|
||||
|
||||
bool is_executable_path(const char* path) {
|
||||
AppLocation location { APP_LOCATION_PATH, const_cast<char*>(path) };
|
||||
return app_is_executable(location);
|
||||
}
|
||||
|
||||
std::atomic<int32_t> g_fixture_result { -1 };
|
||||
std::atomic<bool> g_fixture_result_received { false };
|
||||
|
||||
@@ -62,7 +77,7 @@ int32_t parent_app_main(int, char*[]) {
|
||||
app_manager_add(&fixture_manifest);
|
||||
|
||||
AppInstanceId fixture_id = 0;
|
||||
app_manager_start_for_result("test.posix.fixture", self_id, 0, nullptr, &fixture_id);
|
||||
app_start_for_result("test.posix.fixture", 0, nullptr, self_id, &fixture_id);
|
||||
|
||||
while (true) {
|
||||
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
|
||||
@@ -100,7 +115,7 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its
|
||||
REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE);
|
||||
|
||||
AppInstanceId parent_id = 0;
|
||||
REQUIRE_EQ(app_manager_start("test.posix.parent", &parent_id), ERROR_NONE);
|
||||
REQUIRE_EQ(app_start("test.posix.parent", 0, nullptr, &parent_id), ERROR_NONE);
|
||||
REQUIRE(wait_for_state(parent_id, APP_INSTANCE_STATE_STOPPED, 3000));
|
||||
|
||||
CHECK(g_fixture_result_received.load(std::memory_order_acquire));
|
||||
@@ -111,3 +126,28 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its
|
||||
|
||||
app_manager_remove("test.posix.parent");
|
||||
}
|
||||
|
||||
TEST_CASE("app_is_executable() accepts a real .so") {
|
||||
ensure_path_loader_registered();
|
||||
|
||||
CHECK(is_executable_path(FIXTURE_APP_PATH));
|
||||
}
|
||||
|
||||
TEST_CASE("app_is_executable() rejects a .so-named file with no ELF header") {
|
||||
ensure_path_loader_registered();
|
||||
|
||||
CHECK_FALSE(is_executable_path(FIXTURE_NON_ELF_PATH));
|
||||
}
|
||||
|
||||
TEST_CASE("app_is_executable() rejects a nonexistent path") {
|
||||
ensure_path_loader_registered();
|
||||
|
||||
CHECK_FALSE(is_executable_path((FIXTURE_DIR + "/does-not-exist.so").c_str()));
|
||||
}
|
||||
|
||||
TEST_CASE("app_is_executable() rejects an install-directory-shaped path missing its per-arch .so") {
|
||||
ensure_path_loader_registered();
|
||||
|
||||
// FIXTURE_DIR itself has no elf/posix-<arch>.so under it, so resolution fails.
|
||||
CHECK_FALSE(is_executable_path(FIXTURE_DIR.c_str()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user