Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ cmake_args=(
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
"-DBUILD_TESTING=OFF"
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
# TODO: we don't necessarily want to hardcode this here
"-DBINFMT_INTERPRETER_PATH_PREPEND_LD_P_NATIVE_PACKAGES_PREFIX=/opt/appimagelauncher.AppDir/"
)

if [[ "${BUILD_LITE:-}" == "" ]]; then
Expand Down
7 changes: 7 additions & 0 deletions cmake/install.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ if(ENABLE_UPDATE_HELPER)
)
endif()

option(BINFMT_INTERPRETER_PATH_PREPEND_LD_P_NATIVE_PACKAGES_PREFIX "")

if(NOT BUILD_LITE)
# unfortunately, due to a cyclic dependency, we need to hardcode parts of this variable, which is included in the
# install scripts and the binfmt.d config
set(BINFMT_INTERPRETER_PATH ${CMAKE_INSTALL_PREFIX}/${_private_libdir}/binfmt-interpreter)

if(BINFMT_INTERPRETER_PATH_PREPEND_LD_P_NATIVE_PACKAGES_PREFIX)
message(STATUS "Prepending prefix ${BINFMT_INTERPRETER_PATH_PREPEND_LD_P_NATIVE_PACKAGES_PREFIX} to binfmt interpreter path")
set(BINFMT_INTERPRETER_PATH "${BINFMT_INTERPRETER_PATH_PREPEND_LD_P_NATIVE_PACKAGES_PREFIX}${BINFMT_INTERPRETER_PATH}")
endif()

# according to https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html, we must make sure the
# interpreter string does not exceed 127 characters
set(BINFMT_INTERPRETER_PATH_LENGTH_MAX 127)
Expand Down
2 changes: 1 addition & 1 deletion resources/install-scripts/post-install.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /bin/bash

set -eo pipefail
set -euo pipefail

echo "Installing AppImageLauncher as interpreter for AppImages"

Expand Down
2 changes: 1 addition & 1 deletion resources/install-scripts/post-uninstall.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /bin/bash

set -eo pipefail
set -euo pipefail

echo "Removing AppImageLauncher as interpreter for AppImages"
(set -x; systemctl restart systemd-binfmt)
Expand Down
4 changes: 2 additions & 2 deletions src/binfmt-bypass/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ target_link_libraries(${bypass_lib} PUBLIC dl)
# we need to include the preload lib headers (see below) from the binary dir
target_include_directories(${bypass_lib} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_compile_options(${bypass_lib}
PRIVATE -DPRELOAD_LIB_PATH="${CMAKE_INSTALL_PREFIX}/${_private_libdir}/$<TARGET_FILE_NAME:${preload_lib}>"
PRIVATE -DPRELOAD_LIB_NAME="$<TARGET_FILE_NAME:${preload_lib}>"
PUBLIC -D_GNU_SOURCE
# obviously needs to be private, otherwise the value leaks into users of this lib
PRIVATE -DCOMPONENT_NAME="lib"
)
if(build_32bit_preload_library)
target_compile_options(${bypass_lib}
PRIVATE -DPRELOAD_LIB_PATH_32BIT="${CMAKE_INSTALL_PREFIX}/${_private_libdir}/$<TARGET_FILE_NAME:${preload_lib_32bit}>"
PRIVATE -DPRELOAD_LIB_NAME_32BIT="$<TARGET_FILE_NAME:${preload_lib_32bit}>"
)
target_sources(${bypass_lib} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${preload_lib_32bit}.h)
endif()
Expand Down
73 changes: 72 additions & 1 deletion src/binfmt-bypass/elf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,49 @@ off_t get_elf_size(std::ifstream& ifs)
return sht_end > last_section_end ? sht_end : last_section_end;
}

template<typename EhdrT, typename ShdrT>
ssize_t get_pt_dynamic_offset(std::ifstream& ifs)
{
static_assert(std::is_same<Elf64_Ehdr, EhdrT>::value || std::is_same<Elf32_Ehdr, EhdrT>::value,
"must be Elf{32,64}_Ehdr");
static_assert(std::is_same<Elf64_Shdr, ShdrT>::value || std::is_same<Elf32_Shdr, ShdrT>::value,
"must be Elf{32,64}_Shdr");

EhdrT elf_header{};

ifs.seekg(0, std::ifstream::beg);
ifs.read(reinterpret_cast<char*>(&elf_header), sizeof(elf_header));

if (!ifs) {
log_error("failed to read ELF header\n");
return -1;
}

swap_data_if_necessary(elf_header, elf_header.e_shoff);
swap_data_if_necessary(elf_header, elf_header.e_shentsize);
swap_data_if_necessary(elf_header, elf_header.e_shnum);
swap_data_if_necessary(elf_header, elf_header.e_shnum);

off_t last_shdr_offset = elf_header.e_shoff + (elf_header.e_shentsize * (elf_header.e_shnum - 1));
ShdrT section_header{};

ifs.seekg(last_shdr_offset, std::ifstream::beg);
ifs.read(reinterpret_cast<char*>(&section_header), sizeof(elf_header));

if (!ifs) {
log_error("failed to read ELF section header\n");
return -1;
}

swap_data_if_necessary(elf_header, section_header.sh_offset);
swap_data_if_necessary(elf_header, section_header.sh_size);

/* ELF ends either with the table of section headers (SHT) or with a section. */
off_t sht_end = elf_header.e_shoff + (elf_header.e_shentsize * elf_header.e_shnum);
off_t last_section_end = section_header.sh_offset + section_header.sh_size;
return sht_end > last_section_end ? sht_end : last_section_end;
}

bool is_32bit_elf(std::ifstream& ifs) {
if (!ifs) {
log_error("failed to read e_ident from ELF file\n");
Expand Down Expand Up @@ -121,13 +164,41 @@ bool is_32bit_elf(const std::string& filename) {
std::ifstream ifs(filename);

if (!ifs) {
log_error("could not open file\n");
log_error("could not open file: %s\n", filename.c_str());
return -1;
}

return is_32bit_elf(ifs);
}

bool is_statically_linked_elf(std::ifstream& ifs) {
if (!ifs) {
log_error("failed to read e_ident from ELF file\n");
return -1;
}

ssize_t pt_dynamic_offset;

if (is_32bit_elf(ifs)) {
pt_dynamic_offset = get_pt_dynamic_offset<Elf32_Ehdr, Elf32_Shdr>(ifs);
} else {
pt_dynamic_offset = get_pt_dynamic_offset<Elf64_Ehdr, Elf64_Shdr>(ifs);
}

return pt_dynamic_offset != -1;
}

bool is_statically_linked_elf(const std::string& filename) {
std::ifstream ifs(filename);

if (!ifs) {
log_error("could not open file: %s\n", filename.c_str());
return -1;
}

return is_statically_linked_elf(ifs);
}

ssize_t elf_binary_size(const std::string& filename) {
std::ifstream ifs(filename);

Expand Down
7 changes: 7 additions & 0 deletions src/binfmt-bypass/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

#include <string>

/**
* Check whether file is linked staticallly.
* @param filename path to ELF file
* @return true if file is statically linked, false otherwise
*/
bool is_statically_linked_elf(const std::string& filename);

/**
* Calculate size of ELF binary. Useful e.g., to estimate the size of the runtime in an AppImage.
* @param filename path to ELF file
Expand Down
2 changes: 0 additions & 2 deletions src/binfmt-bypass/interpreter_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ int main(int argc, char** argv) {
if (!executableExists(APPIMAGELAUNCHER_PATH)) {
log_message(
"AppImageLauncher not found at %s, launching AppImage directly: %s\n",
APPIMAGELAUNCHER_PATH,
appImagePath.c_str()
);
useAppImageLauncher = false;
Expand All @@ -42,7 +41,6 @@ int main(int argc, char** argv) {
if (getenv("APPIMAGELAUNCHER_DISABLE") != nullptr) {
log_message(
"APPIMAGELAUNCHER_DISABLE set, launching AppImage directly: %s\n",
APPIMAGELAUNCHER_PATH,
appImagePath.c_str()
);
useAppImageLauncher = false;
Expand Down
52 changes: 31 additions & 21 deletions src/binfmt-bypass/lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
#include <memory>
#include <stdexcept>
#include <cassert>
#include <filesystem>

// own headers
#include "elf.h"
#include "logging.h"
#include "lib.h"
#include "binfmt-bypass-preload.h"

#ifdef PRELOAD_LIB_PATH_32BIT
#ifdef PRELOAD_LIB_NAME_32BIT
#include "binfmt-bypass-preload_32bit.h"
#endif

Expand Down Expand Up @@ -122,23 +123,27 @@ int create_shm_fd_with_patched_runtime(const char* const appimage_filename, cons

#endif

std::string find_preload_library(bool is_32bit) {
std::filesystem::path find_preload_library(bool is_32bit) {
// packaging is now done using ld-p-native_packages which does not make guarantees about the install path
// therefore, we need to look up the path of the preload library in relation to the current binary's path
// since we use the F (fix binary) binfmt mode nowadays to enable the use of the interpreter in different cgroups,
// namespaces or changeroots, we can no longer rely on the path to the interpreter to find the preload libs
// also, we compile in the absolute path to AppImageLauncher anyway
// therefore, we can just compile in the paths to the libs as well
// since we also compile in the contents of the preload libraries, this is merely just an optimization so we do not
// have to write and maintain the library files in the majority of all cases (that is, using AppImages regularly
// on some desktop or generally a host system)
// our workflow is: check whether the (right) preload lib exists on the system, otherwise return an empty string
// the creation of a library file needs to be handled by the caller then
#ifdef PRELOAD_LIB_PATH_32BIT
// namespaces or changeroots, we may not find the library there, but we'll at least try

// we expect the library to be placed next to this binary
const auto own_binary_path = std::filesystem::read_symlink("/proc/self/exe");
const auto dir_path = own_binary_path.parent_path();

std::filesystem::path rv = dir_path;

#ifdef PRELOAD_LIB_NAME_32BIT
if (is_32bit) {
return PRELOAD_LIB_PATH_32BIT;
rv /= PRELOAD_LIB_NAME_32BIT;
return rv;
}
#endif

return PRELOAD_LIB_PATH;
rv /= PRELOAD_LIB_NAME;
return rv;
}

/**
Expand Down Expand Up @@ -174,7 +179,7 @@ class TemporaryPreloadLibFile {

private:
int _fd;
std::string _path;
std::filesystem::path _path;
};

// need to keep track of the subprocess pid in a global variable, as signal handlers in C(++) are simple interrupt
Expand Down Expand Up @@ -231,15 +236,17 @@ int bypassBinfmtAndRunAppImage(const std::string& appimage_path, const std::vect
new_argv.push_back(nullptr);

// preload our library
std::string preload_lib_path = find_preload_library(is_32bit_elf(appimage_path));
auto preload_lib_path = find_preload_library(is_32bit_elf(appimage_path));

log_debug("preload lib path: %s\n", preload_lib_path.string().c_str());

// may or may not be used, but must survive until this application terminates
std::unique_ptr<TemporaryPreloadLibFile> temporaryPreloadLibFile;

if (access(preload_lib_path.c_str(), F_OK) != 0) {
log_warning("could not find preload library path, creating new temporary file for it\n");
if (!std::filesystem::exists(preload_lib_path)) {
log_warning("could not find preload library, creating new temporary file for it\n");

#ifdef PRELOAD_LIB_PATH_32BIT
#ifdef PRELOAD_LIB_NAME_32BIT
if (is_32bit_elf(appimage_path)) {
temporaryPreloadLibFile = std::make_unique<TemporaryPreloadLibFile>(
libbinfmt_bypass_preload_32bit_so,
Expand All @@ -260,14 +267,17 @@ int bypassBinfmtAndRunAppImage(const std::string& appimage_path, const std::vect
preload_lib_path = temporaryPreloadLibFile->path();
}

log_debug("library to preload: %s\n", preload_lib_path.c_str());

setenv("LD_PRELOAD", preload_lib_path.c_str(), true);
if (!is_statically_linked_elf(appimage_path)) {
log_debug("library to preload: %s\n", preload_lib_path.string().c_str());
setenv("LD_PRELOAD", preload_lib_path.c_str(), true);
}

// calculate absolute path to AppImage, for use in the preloaded lib
char* abs_appimage_path = realpath(appimage_path.c_str(), nullptr);
log_debug("absolute AppImage path: %s\n", abs_appimage_path);
// TARGET_APPIMAGE is further needed for static runtimes which do not make any use of LD_PRELOAD
setenv("REDIRECT_APPIMAGE", abs_appimage_path, true);
setenv("TARGET_APPIMAGE", abs_appimage_path, true);

// launch memfd directly, no path needed
log_debug("fexecve(...)\n");
Expand Down
2 changes: 1 addition & 1 deletion src/binfmt-bypass/preload.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void __init() {
char* __abs_appimage_path() {
__init();

static const char env_var_name[] = "REDIRECT_APPIMAGE";
static const char env_var_name[] = "TARGET_APPIMAGE";

char* appimage_var = getenv(env_var_name);

Expand Down