Compare commits

...

3 Commits

Author SHA1 Message Date
cc4f8a7239 update readme 2025-03-16 03:00:11 +02:00
0101ed8a24 implement new loader 2025-03-16 02:37:31 +02:00
7c7832ba40 restructure 2025-03-16 01:50:21 +02:00
30 changed files with 1630 additions and 104 deletions

View File

@@ -1,38 +1,55 @@
# requires lua, curl and zlib (in the future, lua will be statically linked)
libraries := -llua -lcurl -lz
flags := -Wall
# lua is required in the build process, in order to compile the bytecode
lua := lua
cc := gcc
bundle = $(src_entry_dir)/bundle.lua
dump_loader = $(src_entry_dir)/dump_loader.lua
all_flags += -W -Wall -fPIC -std=gnu11
# requires lua, curl and zlib (in the future, lua will be statically linked)
main_libraries += -llua -lcurl -lz
main_cflags += $(all_flags)
loader_libraries += -lfuse -lpthread -lsquashfuse
loader_cflags += $(all_flags)
loader_cflags += -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=29
loader_cflags += -I/usr/include/fuse
dst_dir = dst
src_lua_dir = src
src_c_dir = lib
src_main_dir = src/main
src_entry_dir = src/entry
src_loader_dir = src/loader
# Uncomment to get debugging symbols
# flags := -g
lua_sources += $(wildcard $(src_lua_dir)/*.lua)
lua_sources += $(wildcard $(src_lua_dir)/cli/*.lua)
lua_sources += $(wildcard $(src_lua_dir)/formats/*.lua)
lua_sources += $(wildcard $(src_lua_dir)/util/*.lua)
main_sources += $(wildcard $(src_main_dir)/*.lua)
main_sources += $(wildcard $(src_main_dir)/cli/*.lua)
main_sources += $(wildcard $(src_main_dir)/formats/*.lua)
main_sources += $(wildcard $(src_main_dir)/util/*.lua)
c_sources = $(wildcard $(src_c_dir)/*.c)
entry_sources = $(wildcard $(src_entry_dir)/*.c)
build_entry = $(src_c_dir)/build.lua
loader_sources = $(wildcard $(src_loader_dir)/*.c)
target = $(dst_dir)/slimpack
bytecode_target = $(dst_dir)/bytecode.h
main_target = $(dst_dir)/slimpack
loader_target = $(dst_dir)/loader
bytecode_h_target = $(dst_dir)/bytecode.h
loader_h_target = $(dst_dir)/loader.h
.PHONY: build
build: $(target)
build: $(main_target)
$(dst_dir):
mkdir -p $@
$(bytecode_target): $(lua_sources) $(dst_dir)
$(lua) lib/build.lua src $@ $(lua_sources)
$(target): $(c_sources) $(bytecode_target) $(dst_dir)
$(cc) $(flags) $(libraries) -include $(bytecode_target) $(c_sources) -o $@
$(bytecode_h_target): $(dst_dir) $(bundle) $(main_sources)
$(lua) $(bundle) $(src_main_dir) $@ $(main_sources)
$(loader_h_target): $(dst_dir) $(dump_loader) $(loader_target)
$(lua) $(dump_loader) $(loader_target) $@
$(loader_target): $(dst_dir) $(loader_sources)
$(cc) $(loader_cflags) $(loader_libraries) $(loader_sources) -o $@
$(main_target): $(dst_dir) $(bytecode_h_target) $(loader_h_target) $(entry_sources) $(main_sources)
$(cc) $(main_cflags) $(main_libraries) -include $(bytecode_h_target) -include $(loader_h_target) $(entry_sources) -o $@

View File

@@ -1,36 +1,51 @@
# Slimpack
My shot at creating a lighter flatpak client with (almost) no containerization of the applications.
A pragmatic and minimalistic repacker of flatpak applications without (much) isolation.
## Why?
Some might be big proponents of the whole isolation thing, but I believe that only works in theory with simple apps, as anyone who has tried to get anything running under flatpak can tell you. In my humble opinion, isolation offers too little security benefits for the heaps of problems it creates - yet again, we are solving a problem that really wasn't a problem. To be more precise, the following are some major issues with flatpak, which isolation creates:
Some might be big proponents of the whole isolation thing, but I believe that linux is in the unique position where isolation doesn't really *work* on the desktop. Why? Because to have isolation, you need to have a way to talk to the outside world. On phones, this happens trough well-established APIs that the vendors provide the programmers, because of which isolation on mobile works. However, on linux, there isn't really a standardized set of APIs to do desktop apps. You have KDE and Gnome's takes on them, but no one unified standard. This of course means that if we ever wish to containerize, we need to create APIs for KDE, Gnome and whatever comes next.
- You need to do a lot of fiddling before you get the themes right
- Basic UI features (like drag-n-drop) just don't work
- You get mysterious "file doesn't exist" errors
- Flatpak just straight up installs 3 other distros to run your apps, which takes up A LOT of space
- Apps usually come with enough permissions to be unsafe, but not enough permissions to work properly, partially due to laziness and the shitty permission system flatpaks have
- The CLI is TREMENDOUSLY bad
Flatpak's take on this conundrum, of course, is as dreadful as possible - not only are they not solving the issues, they refuse to recognize that there's an issue at all! Furthermore, the tooling around flatpak is simply, and without any doubt, a whole new level of terrible.
However, flatpak did one thing - it created a network. If you want to publish an application, probably the easiest way to do so is to do it using flatpak. This has resulted in a lot of apps being published exclusively for flatpak. However, we don't have to deal with this whole debacle, as we can just create and use an alternative client that just runs the apps, but outside of their designated containers. At the end of the day, we are still distributing files, just with some permissions metadata sprinkled in.
However, flatpak did one thing - it convinced a lot of developers they were the only place you should be publishing your apps. This means that flathub is one of, if not the most valuable assets to the linux desktop ecosystem. If only we could run those apps like any sane person would - natively...
This project aims to do just that - be the minimalistic client that allows you to just run the darn flatpak app, without having to fiddle with flatseal for 2 hours.
This is where this project comes in. Slimpack aims to be a small tool that allows you to repackage a flatpak package into a self-contained executable, that runs pretty much natively. No longer do you have to fiddle with flatseal for 2 hours just to run a darn electron app.
## Why not?
This project is VERY early stage. You are expected to know what you're doing, and you will brick your system if you don't. At the very least, you are expected to take a quick look trough the code. The whole process of running an app using this client is very manual, but will improve in the future, to a point where you can just integrate it into discover or another client of your choosing.
This project is VERY experimental, employing a custom filesystem, which has bricked my whole OS I/O, until I restart. You are expected to know what you're doing, and even if you do, expect stuff to go south.
## How?
Since the flagship flatpak client needs to download the apps from a repository, we can do that too. In short, this project implements a simple OSTree client (without all the BS the flagship client comes with) that can successfully list the remote refs (apps) and download any given app (and its metadata).
The other big (and not yet implemented) part of the project is the actual repacking of the app. In short, each app has dependencies and a specific runtime it demands to be present. The original flatpak client will 1. download the runtime (for all intents and purposes a distro) and 2. download all the dependencies it has. This client in stark contrast will only download the dependencies and PRAY to the lords that the correct runtime libraries are already present on the host.
The other big part of the project is the actual repacking of the app. In short, each app has dependencies and a specific runtime it demands to be present. The original flatpak client will 1. download the runtime (for all intents and purposes a distro) and 2. download all the dependencies it has. This client in stark contrast will only download the dependencies and will assume that the libraries, packaged by flatpak's runtimes are present on the host.
The last thing this project does is create a *light* container around the application, so that the application-specific dependencies and the app itself can be layered on top of the existing system. Now, don't be mistaken, I'm not doing this because it's the most minimalistic way to do it, but because flatpak basically REQUIRES this architecture. However, the container won't isolate the container any further. In the future, a more configurable isolation might be created, but for the moment, I'm trying to achieve the bare minimum.
When this program repacks a flatpak, it will get put in a self-contained executable as a squashfs image. When the executable gets run, the application will get run in a *VERY* light container, which only layers the squashfs on top of the container (unfortunately, using the currently employed methods, there are *some* limitations, but everything that ran under flatpak *should* run here, too).
Now, don't be mistaken, I'm not containerizing because it's the best solution (the best solution is called AppImage), but because flatpak basically REQUIRES its own view of the filesystem (namely the /app folder). However, we don't isolate more than absolutely necessary, because the whole point is to run the programs as natively as possible.
## Usage
First of all, you need to build the project. This is done via the `make` command. You will need lua 5.4, curl, zlib and some sort of cc installed. The executable will be `dst/slimpack`.
First of all, you need to build the project. This is done via the `make` command. You will need lua 5.4, curl, zlib and some sort of cc installed. The executable will be `dst/slimpack`. Feel free to copy it to `./local/bin/` or `/usr/local/bin/`
TODO: add actual usage when the tool is done lol
The CLI of slimpack works in a very simple way - first, you specify the generic options (for now, only --repo is available), followed by the action. Each action has its own options and then its own subaction and so on.
In syntax form, it looks like this:
`[--global-opt-1 --global-opt-2 ...] <action> [--action-opt ...] <subaction> [--subaction-opt ...]`
Sometimes, if an action is final, it might just take a plain argument (like how `pack` will just take one argument which is the ref to pack)
### Querying
The first action is `query`. It groups all operations that are related to asking questions about a flatpak remote.
To find a ref, you can use `slimpack query search <name>`. This is case-insensitive, but you need to be careful with dashes and underscores. Usually, you will care about one result - `app/.../x86_64/stable`. The rest, for all intents and purposes, may be ignored.
To find out more info about a ref, you can do `slimpack query info <ref>`. This will print some information about the package, like its version, license, dependencies, etc.
### Packaging
The star of the show is the packaging - this will take a flatpak package and repack it into a self-contained executable (not too unlike AppImage). To repack a package, run `slimpack pack <ref> -o <output-filename>`. This will eventually, after a lot of stdout spam, write an executable file to "output-filename". When run, it will just run the flatpak application without (a lot of) containerization, without any fuss.

6
mod/elf_loader.d.lua Normal file
View File

@@ -0,0 +1,6 @@
--- @meta elf_loader
--- @type string
local res;
return res;

View File

@@ -8,11 +8,11 @@ local fs = {};
--- @return string? err
function fs.mkdir(path, recursive) end
--- @param src string
--- @param dst string
--- @param target string
--- @param name string
--- @return boolean? ok
--- @return string? err
function fs.symlink(src, dst) end
function fs.symlink(target, name) end
--- @param path string
--- @param mode string | integer

36
src/entry/dump_loader.lua Normal file
View File

@@ -0,0 +1,36 @@
local function escape(str)
return "\"" .. str:gsub(".", function (c)
local b = string.byte(c);
if c == "\n" then
return "\\n";
elseif c == "\\" then
return "\\\\";
elseif c == "\"" then
return "\\\"";
elseif b >= 32 and b <= 126 then
return c;
else
return ("\\%.3o"):format(b);
end
end) .. "\"";
end
local function main(in_f, out_f)
local f = assert(io.open(out_f, "w"));
f:write "#include <stdint.h>\n";
f:write "#define LOADER loader_bytecode\n";
f:write "#define LOADER_SIZE (sizeof loader_bytecode - 1)\n";
f:write "static const uint8_t loader_bytecode[] = ";
for part in io.lines(in_f, 64) do
f:write(escape(part));
f:write"\n";
end
f:write ";";
assert(f:close());
end
main(...);

View File

@@ -77,7 +77,7 @@ static int fmt_parse_width(size_t *i, const char *raw, size_t raw_size) {
static fmt_code_t fmt_parse_fmt(lua_State *ctx, const char *raw, size_t raw_size) {
if (raw == NULL || *raw == 0) return FMT_BAD_ARGS;
if (raw_size == -1) raw_size = strlen(raw);
if (raw_size == SIZE_MAX) raw_size = strlen(raw);
fmt_segment_t segments[raw_size];
size_t segments_n = 0;
@@ -278,7 +278,7 @@ static void write_buff_fit(write_buff_t *buff) {
static bool fmt_read_uint8(const uint8_t *raw, size_t *i, size_t size, uint8_t *res) {
if ((*i) + 1 > size || *i < 0) return false;
if ((*i) + 1 > size) return false;
*res = raw[(*i)++];
@@ -289,7 +289,7 @@ static bool fmt_read_int8(const uint8_t *raw, size_t *i, size_t size, int8_t *re
}
static bool fmt_read_uint16(const uint8_t *raw, size_t *i, size_t size, uint16_t *res, bool little_endian) {
if ((*i) + 2 > size || *i < 0) return false;
if ((*i) + 2 > size) return false;
uint16_t a = raw[(*i)++];
uint16_t b = raw[(*i)++];
@@ -304,7 +304,7 @@ static bool fmt_read_int16(const uint8_t *raw, size_t *i, size_t size, int16_t *
}
static bool fmt_read_uint32(const uint8_t *raw, size_t *i, size_t size, uint32_t *res, bool little_endian) {
if ((*i) + 4 > size || *i < 0) return false;
if ((*i) + 4 > size) return false;
uint8_t a = raw[(*i)++];
uint8_t b = raw[(*i)++];
@@ -321,7 +321,7 @@ static bool fmt_read_int32(const uint8_t *raw, size_t *i, size_t size, int32_t *
}
static bool fmt_read_uint64(const uint8_t *raw, size_t *i, size_t size, uint64_t *res, bool little_endian) {
if ((*i) + 8 > size || *i < 0) return false;
if ((*i) + 8 > size) return false;
uint64_t a = raw[(*i)++];
uint64_t b = raw[(*i)++];
@@ -342,7 +342,7 @@ static bool fmt_read_int64(const uint8_t *raw, size_t *i, size_t size, int64_t *
}
static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *res, bool little_endian) {
if ((*i) + 4 > size || *i < 0) return false;
if ((*i) + 4 > size) return false;
uint8_t a = raw[(*i)++];
uint8_t b = raw[(*i)++];
@@ -356,7 +356,7 @@ static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *
return true;
}
static bool fmt_read_float64(const uint8_t *raw, size_t *i, size_t size, double *res, bool little_endian) {
if ((*i) + 8 > size || *i < 0) return false;
if ((*i) + 8 > size) return false;
uint8_t a = raw[(*i)++];
uint8_t b = raw[(*i)++];
@@ -411,7 +411,6 @@ static bool fmt_read_string(lua_State *ctx, fmt_segment_t segment, const uint8_t
fprintf(stderr, "%lu %lu", len, *i);
if ((*i) + len > size) return false;
if (*i < 0) return false;
char data[len];
memcpy(data, raw + (*i), len);

View File

@@ -18,7 +18,7 @@
static const char *fs_check_path(lua_State *ctx, int i, size_t *psize) {
size_t n;
const char *path = luaL_checklstring(ctx, 1, &n);
const char *path = luaL_checklstring(ctx, i, &n);
size_t real_len = strlen(path);
if (n != real_len) {
@@ -71,7 +71,7 @@ static int lib_fs_symlink(lua_State *ctx) {
if (symlink(src, dst) < 0) {
lua_pushnil(ctx);
lua_pushfstring(ctx, "failed to chmod: %s", strerror(errno));
lua_pushfstring(ctx, "failed to symlink: %s", strerror(errno));
return 2;
}

View File

@@ -14,6 +14,8 @@ typedef struct {
} http_body_buff_t;
static size_t body_writer(char *ptr, size_t _, size_t size, void *pbuff) {
(void)_;
http_body_buff_t *buff = pbuff;
size_t new_cap = buff->cap;

View File

@@ -14,17 +14,29 @@
#define BYTECODE_SIZE (sizeof entry_bytecode - 1)
#endif
#ifndef LOADER
static uint8_t loader_bytecode[] = "";
#define LOADER loader_bytecode
#define LOADER_SIZE (sizeof loader_bytecode - 1)
#endif
static char err_bytecode[] =
"local err = ...;"
"local trace = debug.traceback(nil, 2):match \"^[^\\n]+\\n(.+)$\""
"print(table.concat { \"unhandled error: \", err, \"\\n\", trace })";
static int elf_loader_open_lib(lua_State *ctx) {
lua_pushlstring(ctx, (const char*)LOADER, LOADER_SIZE);
return 1;
}
static void load_modules(lua_State *ctx) {
luaL_openlibs(ctx);
luaL_requiref(ctx, "http", http_open_lib, false);
luaL_requiref(ctx, "fmt", fmt_open_lib, false);
luaL_requiref(ctx, "zlib", zlib_open_lib, false);
luaL_requiref(ctx, "fs", fs_open_lib, false);
luaL_requiref(ctx, "elf_loader", elf_loader_open_lib, false);
}
int main(int argc, const char **argv) {
@@ -42,7 +54,7 @@ int main(int argc, const char **argv) {
return 1;
}
for (size_t i = 1; i < argc; i++) {
for (int i = 1; i < argc; i++) {
lua_pushstring(ctx, argv[i]);
}

View File

@@ -39,6 +39,7 @@ static int lib_decompress_iter(lua_State *ctx) {
case Z_STREAM_END:
inflateEnd(&stream->stream);
stream->finalized = true;
// fall through
case Z_OK: {
lua_pushlstring(ctx, (const char*)stream->buff, sizeof stream->buff - stream->stream.avail_out);
stream->processed = size - stream->stream.avail_in;

204
src/loader/entry.c Normal file
View File

@@ -0,0 +1,204 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <linux/limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <err.h>
#include <errno.h>
#include "loader.h"
#include "slimfs.h"
typedef struct {
char root_dir[PATH_MAX];
struct fuse *fuse;
bool freed;
} *fuse_pthread_data_t;
static slimfs_t fs;
static char *mount_dir;
static void *fuse_pthread_entry(void *pdata) {
slimfs_t fs = pdata;
if (fuse_loop(fs->fuse) < 0) err(1, "Couldn't start fuse loop");
return NULL;
}
static slimfs_t slimfs_mount(const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset) {
slimfs_t fs = slimfs_new(1, (char*[]){ "", NULL }, target_dir, root_dir, img_path, img_offset);
if (fs == NULL) return NULL;
pthread_t thread;
pthread_create(&thread, NULL, fuse_pthread_entry, fs);
return fs;
}
static void handle_close() {
fprintf(stderr, "\nCTRL+C detected, closing SlimFS...\n");
slimfs_free(fs);
rmdir(mount_dir);
exit(0);
}
static int write_flag(const char *file, const char *format, ...) {
int fd = open(file, O_WRONLY);
if (fd < 0) {
warn("Failed to write to %s", file);
return -1;
}
va_list list;
va_start(list, format);
char buff[vsnprintf(NULL, 0, format, list) + 1];
va_end(list);
va_start(list, format);
vsnprintf(buff, sizeof buff, format, list);
va_end(list);
int n = write(fd, buff, sizeof buff - 1);
if (n < 0 || (size_t)n != sizeof buff - 1) {
warn("Failed to write to %s", file);
return -1;
}
close(fd);
return 0;
}
static int err_mount(const char *name) {
warn("Failed to setup virtual mount for %s", name);
return 127;
}
int slimpak_isolate(const char *root_dir, char **argv) {
pid_t pid = fork();
if (pid < 0) err(EXIT_EXECERROR, "fork error");
else if (pid == 0) {
int uid = geteuid();
int gid = getegid();
errno = 0;
// Child is responsible for running the actual command
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) {
warn("Failed to containerize");
exit(127);
}
if (write_flag("/proc/self/uid_map", "%d %d 1\n", uid, uid) < 0) exit(127);
if (write_flag("/proc/self/setgroups", "deny\n") < 0) exit(127);
if (write_flag("/proc/self/gid_map", "%d %d 1\n", gid, gid) < 0) exit(127);
char *cwd = getcwd(NULL, 0);
chdir(root_dir);
// if (mount(NULL, "/", NULL, MS_REC | MS_SILENT | MS_SLAVE, NULL) < 0) return err_mount("root");
if (mount("/dev", "dev", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/dev");
if (mount("/proc", "proc", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/proc");
if (mount("/run", "run", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/run");
if (chroot(root_dir) < 0) {
warn("Failed to chroot into virtual root");
exit(127);
}
chdir(cwd);
free(cwd);
execv(argv[0], argv);
err(127, "Failed to execute command");
}
else {
int status;
if (waitpid(pid, &status, 0) < 0) {
warn("Failed to get child status");
return -1;
}
if (WIFEXITED(status)) return (uint16_t)WEXITSTATUS(status);
else return -1;
}
return 0;
}
int main(int argc, char **argv) {
const char *self_path = "/proc/self/exe";
const char *temp_base = "/tmp";
const char *slimpack_arg = NULL;
size_t offset = slimpack_get_offset(self_path);
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == '-') {
slimpack_arg = (const char*)argv[1] + 2;
}
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-offset")) {
printf("%zu\n", offset);
return 0;
}
mount_dir = malloc(strlen(temp_base) + 19 + 1);
strcpy(mount_dir, temp_base);
strcat(mount_dir, "/.slimpack.XXXXXXXX");
if (!mkdtemp(mount_dir)) err(EXIT_EXECERROR, "create mount dir error");
fs = slimfs_mount(mount_dir, "/", self_path, offset);
if (fs == NULL) errx(1, "Couldn't mount SlimFS");
signal(SIGINT, handle_close);
int status;
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-mount")) {
fprintf(stderr, "The mountpoint is at %s. Press Ctrl+D when you're ready\n", mount_dir);
char buff[4096];
while (read(STDIN_FILENO, buff, sizeof buff) != 0);
slimfs_free(fs);
rmdir(mount_dir);
return 0;
}
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-shell")) {
status = slimpak_isolate((const char*)mount_dir, (char*[]) { "/bin/bash", NULL });
}
else {
argv[0] = "/entry";
status = slimpak_isolate((const char*)mount_dir, argv);
}
slimfs_free(fs);
rmdir(mount_dir);
if (status < 0) {
err(127, "Failed to start isolated process (status %d)", status);
}
else {
return status;
}
}

16
src/loader/loader.h Normal file
View File

@@ -0,0 +1,16 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
// Exit status to use when launching an AppImage fails.
// For applications that assign meanings to exit status codes (e.g. rsync),
// we avoid "cluttering" pre-defined exit status codes by using 127 which
// is known to alias an application exit status and also known as launcher
// error, see SYSTEM(3POSIX).
#define EXIT_EXECERROR 127
ssize_t slimpack_get_offset(const char *executable);
int slimpak_isolate(const char *root_dir, char **argv);
int slimpak_mount(const char *executable, const char *work_dir, const char *root_dir);
int slimpak_unmount(const char *work_dir, const char *root_dir);

228
src/loader/offset.c Normal file
View File

@@ -0,0 +1,228 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include "loader.h"
#define EI_NIDENT 16
#define ELFCLASS32 1
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2
#define ELFCLASS64 2
#define EI_CLASS 4
#define EI_DATA 5
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ELFDATANATIVE ELFDATA2LSB
#elif __BYTE_ORDER == __BIG_ENDIAN
#define ELFDATANATIVE ELFDATA2MSB
#else
#error "Unknown machine endian"
#endif
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry; /* Entry point */
uint32_t e_phoff;
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} elf32_hdr_t;
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
uint32_t sh_addr;
uint32_t sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
} elf32_shdr_t;
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry; /* Entry point virtual address */
uint64_t e_phoff; /* Program header table file offset */
uint64_t e_shoff; /* Section header table file offset */
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} elf64_hdr_t;
typedef struct {
uint32_t sh_name; /* Section name, index in string tbl */
uint32_t sh_type; /* Type of section */
uint64_t sh_flags; /* Miscellaneous section attributes */
uint64_t sh_addr; /* Section virtual addr at execution */
uint64_t sh_offset; /* Section file offset */
uint64_t sh_size; /* Size of section in bytes */
uint32_t sh_link; /* Index of another section */
uint32_t sh_info; /* Additional section information */
uint64_t sh_addralign; /* Section alignment */
uint64_t sh_entsize; /* Entry size if section holds table */
} elf64_shdr_t;
/* Note header in a PT_NOTE section */
typedef struct elf32_note {
uint32_t n_namesz; /* Name size */
uint32_t n_descsz; /* Content size */
uint32_t n_type; /* Content type */
} Elf32_Nhdr;
typedef Elf32_Nhdr Elf_Nhdr;
static uint16_t file16_to_cpu(uint8_t ident[EI_NIDENT], uint16_t val) {
if (ident[EI_DATA] != ELFDATANATIVE) {
uint16_t a = val & 0xFF;
uint16_t b = (val >> 8) & 0xFF;
val = a << 8 | b;
}
return val;
}
static uint32_t file32_to_cpu(uint8_t ident[EI_NIDENT], uint32_t val) {
if (ident[EI_DATA] != ELFDATANATIVE) {
uint32_t a = val & 0xFF;
uint32_t b = (val >> 8) & 0xFF;
uint32_t c = (val >> 16) & 0xFF;
uint32_t d = (val >> 24) & 0xFF;
val = a << 24 | b << 16 | c << 8 | d;
}
return val;
}
static uint64_t file64_to_cpu(uint8_t ident[EI_NIDENT], uint64_t val) {
if (ident[EI_DATA] != ELFDATANATIVE) {
uint64_t a = val & 0xFF;
uint64_t b = (val >> 8) & 0xFF;
uint64_t c = (val >> 16) & 0xFF;
uint64_t d = (val >> 24) & 0xFF;
uint64_t e = (val >> 32) & 0xFF;
uint64_t f = (val >> 40) & 0xFF;
uint64_t g = (val >> 48) & 0xFF;
uint64_t h = (val >> 56) & 0xFF;
val = a << 56 | b << 48 | c << 40 | d << 32 | e << 24 | f << 16 | g << 8 | h;
}
return val;
}
static ssize_t read_elf32_size(uint8_t ident[EI_NIDENT], FILE *fd) {
ssize_t ret;
fseek(fd, 0, SEEK_SET);
elf32_hdr_t header;
ret = fread(&header, 1, sizeof(header), fd);
if (ret < 0 || ret != sizeof header) {
warn("Read of ELF header failed");
return -1;
}
size_t sh_offset = file32_to_cpu(ident, header.e_shoff);
size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize);
size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum);
off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1));
fseek(fd, last_shdr_offset, SEEK_SET);
elf32_shdr_t sect_header;
ret = fread(&sect_header, 1, sizeof(sect_header), fd);
if (ret < 0 || (size_t) ret != sizeof(sect_header)) {
warn("Read of ELF section header failed");
return -1;
}
/* ELF ends either with the table of section headers (SHT) or with a section. */
off_t sh_end = sh_offset + (sh_entry_size * sh_entry_n);
off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size);
return sh_end > s_end ? sh_end : s_end;
}
static ssize_t read_elf64_size(uint8_t ident[EI_NIDENT], FILE *fd) {
off_t ret;
fseek(fd, 0, SEEK_SET);
elf64_hdr_t header;
ret = fread(&header, 1, sizeof(header), fd);
if (ret < 0 || (size_t) ret != sizeof(header)) {
warn("Read of ELF header failed");
return -1;
}
size_t sh_offset = file64_to_cpu(ident, header.e_shoff);
size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize);
size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum);
off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1));
fseek(fd, last_shdr_offset, SEEK_SET);
elf64_shdr_t sect_header;
ret = fread(&sect_header, 1, sizeof(sect_header), fd);
if (ret < 0 || ret != sizeof(sect_header)) {
warn("Read of ELF section header failed");
return -1;
}
/* ELF ends either with the table of section headers (SHT) or with a section. */
off_t sht_end = sh_offset + (sh_entry_size * sh_entry_n);
off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size);
return sht_end > s_end ? sht_end : s_end;
}
ssize_t slimpack_get_offset(const char *executable) {
FILE *fd = fopen(executable, "rb");
if (fd == NULL) {
warn("Cannot open executable");
return -1;
}
uint8_t ident[EI_NIDENT];
off_t ret = fread(ident, 1, EI_NIDENT, fd);
if (ret != EI_NIDENT) {
warn("Read of e_ident failed");
return -1;
}
if (ident[EI_DATA] != ELFDATA2LSB && ident[EI_DATA] != ELFDATA2MSB) {
warnx("Unknown ELF data order %u", ident[EI_DATA]);
return -1;
}
size_t res;
if (ident[EI_CLASS] == ELFCLASS32) res = read_elf32_size(ident, fd);
else if (ident[EI_CLASS] == ELFCLASS64) res = read_elf64_size(ident, fd);
else {
fprintf(stderr, "Unknown ELF class %u\n", ident[EI_CLASS]);
return -1;
}
fclose(fd);
return res;
}

1019
src/loader/slimfs.c Normal file

File diff suppressed because it is too large Load Diff

25
src/loader/slimfs.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* License: BSD-style license
* Copyright: Radek Podgorny <radek@podgorny.cz>,
* Bernd Schubert <bernd-schubert@gmx.de>
*/
#pragma once
#include <fuse.h>
#include <squashfuse/squashfuse.h>
#include <stdbool.h>
typedef struct {
struct fuse *fuse;
struct fuse_chan *channel;
struct sqfs image;
char root_dir[PATH_MAX];
char target_dir[PATH_MAX];
} *slimfs_t;
extern struct fuse_operations unionfs_oper;
slimfs_t slimfs_new(int argc, char **argv, const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset);
void slimfs_free(slimfs_t fs);

View File

@@ -1,61 +1,7 @@
local ostree = require "ostree";
local flatpak = require "flatpak";
local fs = require "fs";
local loader = [[#!/bin/sh
set -euo pipefail;
cleanup() {
cd /;
if [ "${mountpoint_dir+x}" ]; then
umount "$mountpoint_dir";
fi
if [ "${squashfs_dir+x}" ]; then
umount "$squashfs_dir";
fi
if [ "${tmp_dir+x}" ]; then
rm -rf "$tmp_dir";
fi
}
jail_run() {
unshare -cR $mountpoint_dir $@;
}
trap cleanup EXIT;
marker="#END_OF_"LOADER"_MARKER";
offset=$(( $(grep -abo "$marker" "$PWD/$0" | head -n 1 | cut -d: -f1) + ${#marker} + 1 ));
if [ "${1-}" = "--slimpack-offset" ] || [ "${1-}" = "--appimage-offset" ]; then
echo $offset;
exit 0;
fi
tmp_dir=$(mktemp -d /tmp/.slimpack.XXXXXXXXXXXX);
mkdir "$tmp_dir/squashfs";
squashfuse -o offset=$offset "$PWD/$0" "$tmp_dir/squashfs";
squashfs_dir="$tmp_dir/squashfs";
mkdir "$tmp_dir/mount_point";
unionfs -o suid,dev "$squashfs_dir"=RO:/=RW "$tmp_dir/mount_point";
mountpoint_dir="$tmp_dir/mount_point";
if [ "${1-}" = "--slimpack-dbg" ]; then
jail_run /bin/bash;
elif [ "${1-}" = "--slimpack-dbg-2" ]; then
cd "$mountpoint_dir" && bash;
else
jail_run "/entry" $@;
fi
exit 0;
#END_OF_LOADER_MARKER
]];
local elf_loader = require "elf_loader";
local entry_preifx = [[#!/bin/sh
@@ -98,7 +44,7 @@ end
local function write_res(img_path, target)
local f = assert(io.open(target, "wb"));
f:write(loader);
f:write(elf_loader);
local img_f = assert(io.open(img_path, "rb"));

View File

@@ -292,7 +292,7 @@ function ostree.dump_node(path, node)
assert(fs.chmod(path, node.mode & 0xFFF));
elseif node.type == "link" then
assert(fs.mkdir(path:match "^(.+)/[^/]+$", true));
assert(fs.symlink(path, node.target));
assert(fs.symlink(node.target, path));
else
error("Unknown node type '" .. node.type .. "'");
end