_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file src/scan.c
#define _POSIX_C_SOURCE 200809L
#include "scan.h"
#include "elf.h"
#include "package.h"
#include "verify.h"
#include "stage.h"
#include "script.h"
#include <archive.h>
#include <archive_entry.h>
#include <gelf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void print_token(const char *path)
{
const unsigned char *p = (const unsigned char *)path;
for (; *p; ++p) {
if (*p == '\\' || *p <= 32 || *p >= 127)
printf("\\x%02x", (unsigned int)*p);
else putchar(*p);
}
}
static const char *named_runtime(const struct holy_elf_info *info, const char *path)
{
const char *base = strrchr(path, '/');
base = base ? base + 1 : path;
if (!strcmp(base, "libc.so.6")) return "glibc";
if ((!strcmp(holy_elf_machine(info), "x86_64") &&
(!strcmp(base, "libc.musl-x86_64.so.1") || !strcmp(base, "ld-musl-x86_64.so.1"))) ||
(!strcmp(holy_elf_machine(info), "x86") &&
(!strcmp(base, "libc.musl-i386.so.1") || !strcmp(base, "ld-musl-i386.so.1")))) return "musl";
return NULL;
}
struct scan_link { char *path, *target; unsigned int mode; };
struct scan_links { struct scan_link *items; size_t count; struct holy_scan_result *collected; };
static int collect_link(void *opaque, const struct holy_manifest_entry *entry)
{
struct scan_links *links = opaque;
struct scan_link *next;
if (entry->link && links->collected) {
struct holy_scanned_symlink *alias;
if (links->collected->symlink_count >= 65536) return 0;
alias = realloc(links->collected->symlinks,
(links->collected->symlink_count + 1) * sizeof *alias);
if (!alias) return 0;
links->collected->symlinks = alias;
alias = &alias[links->collected->symlink_count];
alias->path = strdup(entry->path);
alias->target = strdup(entry->link);
if (!alias->path || !alias->target) {
free(alias->path); free(alias->target);
return 0;
}
++links->collected->symlink_count;
}
if (!entry->hardlink) return 1;
if (links->count >= 65536) return 0;
next = realloc(links->items, (links->count + 1) * sizeof *next);
if (!next) return 0;
links->items = next;
next = &next[links->count++];
next->path = malloc(strlen(entry->path) + 6);
next->target = strdup(entry->hardlink);
next->mode = entry->mode;
if (next->path) sprintf(next->path, "DATA/%s", entry->path);
return next->path && next->target;
}
static int link_order(const void *left, const void *right)
{
const struct scan_link *a = left, *b = right;
int result = strcmp(a->target, b->target);
return result ? result : strcmp(a->path, b->path);
}
static const char *module_release(const char *name, size_t *length)
{
const char *prefix = "DATA/usr/lib/modules/";
const char *release, *end;
size_t size = strlen(name);
if (strncmp(name, prefix, strlen(prefix)) || size < 3 ||
strcmp(name + size - 3, ".ko")) return NULL;
release = name + strlen(prefix);
end = strchr(release, '/');
if (!end || end == release || strncmp(end, "/kernel/", 8) || !end[8])
return NULL;
*length = (size_t)(end - release);
return release;
}
static int inspect_elf(int fd, const char *name, unsigned int mode,
const char *arch, const char *libc, int emit, size_t *edges,
struct holy_scan_result *collected)
{
struct holy_elf_info info;
const char *runtime;
int result;
result = holy_elf_read_fd(fd, &info);
if (result) {
holy_elf_free(&info);
fprintf(stderr, "holypkg: malformed ELF in payload: %s\n", name);
return 0;
}
if (info.type == ET_REL) {
size_t release_length = 0;
const char *release = module_release(name, &release_length);
if (!release || strcmp(libc, "nolibc") ||
strcmp(arch, holy_elf_machine(&info)) ||
!holy_elf_kernel_module_fd(fd, &info, release, release_length)) {
fprintf(stderr, "holypkg: invalid kernel module or unsupported ELF object: %s\n", name);
holy_elf_free(&info);
return 0;
}
if (emit) {
fputs("kernel-module ", stdout);
print_token(name + 5);
printf(" machine=%s release=", holy_elf_machine(&info));
fwrite(release, 1, release_length, stdout);
putchar('\n');
}
holy_elf_free(&info);
return 1;
}
{
size_t i;
for (i = 0; i < info.needed_count; ++i) {
const char *required = named_runtime(&info, info.needed[i]);
if (required && strcmp(libc, required)) {
fprintf(stderr, "holypkg: ELF runtime requirement conflicts with package libc: %s\n", name);
holy_elf_free(&info);
return 0;
}
}
}
runtime = holy_elf_runtime(&info);
if (!strcmp(runtime, "unknown") && info.type == ET_DYN) {
size_t i;
for (i = 0; i < info.needed_count; ++i) {
const char *required = named_runtime(&info, info.needed[i]);
if (required) runtime = required;
}
if (info.soname &&
((!strcmp(info.soname, "ld-linux-x86-64.so.2") &&
!strcmp(holy_elf_machine(&info), "x86_64")) ||
(!strcmp(info.soname, "ld-linux.so.2") &&
!strcmp(holy_elf_machine(&info), "x86"))))
for (i = 0; i < info.defined_version_count; ++i)
if (!strcmp(info.defined_versions[i].name, "GLIBC_PRIVATE"))
runtime = "glibc";
}
if (!strcmp(runtime, "unknown")) {
fprintf(stderr, "holypkg: ELF runtime unknown; package tag cannot prove ABI: %s\n",
name);
holy_elf_free(&info);
return 0;
}
if (strcmp(arch, holy_elf_machine(&info)) || strcmp(libc, runtime)) {
fprintf(stderr, "holypkg: ELF arch/libc mismatch in payload: %s\n", name);
holy_elf_free(&info);
return 0;
}
if (emit) {
size_t i;
fputs("elf ", stdout);
print_token(name + 5);
printf(" class=ELF%d machine=%s e_machine=%u runtime=%s isa=%s\n",
info.elf_class == 1 ? 32 : 64, holy_elf_machine(&info),
(unsigned int)info.machine, runtime,
holy_elf_isa(&info));
if (info.soname) {
fputs("soname ", stdout);
print_token(name + 5);
putchar(' ');
print_token(info.soname);
putchar('\n');
}
for (i = 0; i < info.needed_count; ++i) {
fputs("needed ", stdout);
print_token(name + 5);
putchar(' ');
print_token(info.needed[i]);
putchar('\n');
}
for (i = 0; i < info.version_count; ++i) {
fputs("version ", stdout);
print_token(name + 5);
putchar(' ');
print_token(info.versions[i].provider);
putchar(' ');
print_token(info.versions[i].name);
printf(" %s\n", info.versions[i].weak ? "weak" : "required");
}
for (i = 0; i < info.defined_version_count; ++i) {
fputs("version-def ", stdout);
print_token(name + 5);
putchar(' ');
print_token(info.defined_versions[i].name);
putchar('\n');
}
for (i = 0; i < info.symbol_count; ++i) {
const struct holy_elf_symbol *s = &info.symbols[i];
if (!s->name[0]) continue;
fputs("symbol ", stdout);
print_token(name + 5);
putchar(' ');
print_token(s->name);
printf(" binding=%u type=%u visibility=%u section=%u version-index=%u hidden=%d version=",
s->binding, s->type, s->visibility, (unsigned)s->section,
(unsigned)s->version_index, s->version_hidden);
print_token(s->version ? s->version : "none");
fputs(" provider=", stdout);
print_token(s->provider ? s->provider : "none");
putchar('\n');
}
}
if (info.needed_count > (size_t)-1 - *edges) {
holy_elf_free(&info);
return 0;
}
*edges += info.needed_count;
if (collected) {
struct holy_scanned_file *next;
char *copy = strdup(name + 5);
if (!copy || collected->count >= 65536) {
free(copy); holy_elf_free(&info); return 0;
}
next = realloc(collected->files, (collected->count + 1) * sizeof *next);
if (!next) { free(copy); holy_elf_free(&info); return 0; }
collected->files = next;
next[collected->count].path = copy;
next[collected->count].runtime = runtime;
next[collected->count].mode = mode;
next[collected->count++].elf = info;
memset(&info, 0, sizeof info);
}
holy_elf_free(&info);
return 1;
}
static int inspect_script(int fd, const char *name, unsigned int mode,
la_int64_t size, int emit, struct holy_scan_result *collected)
{
char *interpreter = NULL;
int kind = holy_script_read_fd(fd, (off_t)size, (mode_t)mode, &interpreter);
if (kind < 0) return 0;
if (!kind) return 1;
if (emit) {
fputs("script ", stdout);
print_token(name + 5);
putchar(' ');
print_token(interpreter);
printf(" kind=%s\n", kind == 1 ? "direct" : kind == 2 ? "env" : "unknown");
}
if (collected) {
struct holy_scanned_script *next;
if (collected->script_count >= 65536) { free(interpreter); return 0; }
next = realloc(collected->scripts, (collected->script_count + 1) * sizeof *next);
if (!next) { free(interpreter); return 0; }
collected->scripts = next;
next = &next[collected->script_count];
next->path = strdup(name + 5);
if (!next->path) { free(interpreter); return 0; }
next->interpreter = interpreter;
next->kind = kind;
++collected->script_count;
} else free(interpreter);
return 1;
}
static int scan(const char *path, int emit, size_t *needed,
struct holy_scan_result *collected)
{
struct archive *a = NULL;
struct archive_entry *entry;
char *snapshot = holy_stage_local(path, "holy-scan");
char *arch = NULL, *libc = NULL;
char buffer[65536];
size_t scanned = 0, edges = 0, i;
struct scan_links links = {0};
int status, ok = 0;
if (needed) *needed = 0;
if (!snapshot) {
fprintf(stderr, "holypkg: could not stage regular local input\n");
return 0;
}
links.collected = collected;
if (!holy_verify_visit(snapshot, collect_link, &links) ||
!holy_package_tags(snapshot, &arch, &libc)) goto done;
if (links.count) qsort(links.items, links.count, sizeof *links.items, link_order);
a = archive_read_new();
if (!a || archive_read_support_filter_lz4(a) != ARCHIVE_OK ||
archive_read_support_format_tar(a) != ARCHIVE_OK ||
archive_read_open_filename(a, snapshot, 8192) != ARCHIVE_OK) goto done;
while ((status = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
const char *name = archive_entry_pathname(entry);
size_t prefix = 0, prefix_limit;
la_ssize_t got;
FILE *temp;
int elf, script;
if (!name || strncmp(name, "DATA/", 5) || !name[5] ||
archive_entry_filetype(entry) != AE_IFREG ||
archive_entry_hardlink(entry) || archive_entry_size(entry) < 2) {
if (archive_read_data_skip(a) != ARCHIVE_OK) goto done;
continue;
}
prefix_limit = archive_entry_size(entry) < 4 ?
(size_t)archive_entry_size(entry) : 4;
while (prefix < prefix_limit) {
got = archive_read_data(a, buffer + prefix, prefix_limit - prefix);
if (got <= 0) goto done;
prefix += (size_t)got;
}
elf = prefix == 4 && !memcmp(buffer, "\177ELF", 4);
script = (archive_entry_perm(entry) & 0111) && prefix >= 2 &&
buffer[0] == '#' && buffer[1] == '!';
if (!elf && !script) {
if (archive_read_data_skip(a) != ARCHIVE_OK) goto done;
continue;
}
temp = tmpfile();
if (!temp) goto done;
if (fwrite(buffer, 1, prefix, temp) != prefix) {
fclose(temp);
goto done;
}
while ((got = archive_read_data(a, buffer,
elf ? sizeof buffer :
prefix < 256 ? 256 - prefix : 0)) > 0) {
if (fwrite(buffer, 1, (size_t)got, temp) != (size_t)got) {
fclose(temp);
goto done;
}
if (!elf) {
prefix += (size_t)got;
if (prefix == 256) break;
}
}
if (got < 0 || fflush(temp)) { fclose(temp); goto done; }
if (!elf) {
size_t low = 0, high = links.count, j;
if (!inspect_script(fileno(temp), name, (unsigned int)archive_entry_perm(entry),
archive_entry_size(entry), emit, collected)) { fclose(temp); goto done; }
while (low < high) {
size_t middle = low + (high - low) / 2;
if (strcmp(links.items[middle].target, name + 5) < 0) low = middle + 1;
else high = middle;
}
for (j = low; j < links.count && !strcmp(links.items[j].target, name + 5); ++j)
if (!inspect_script(fileno(temp), links.items[j].path, links.items[j].mode,
archive_entry_size(entry), emit, collected)) { fclose(temp); goto done; }
if (archive_read_data_skip(a) != ARCHIVE_OK) { fclose(temp); goto done; }
fclose(temp);
continue;
}
if (!inspect_elf(fileno(temp), name, (unsigned int)archive_entry_perm(entry),
arch, libc, emit, &edges, collected)) { fclose(temp); goto done; }
{
size_t low = 0, high = links.count, i;
while (low < high) {
size_t middle = low + (high - low) / 2;
if (strcmp(links.items[middle].target, name + 5) < 0) low = middle + 1;
else high = middle;
}
for (i = low; i < links.count && !strcmp(links.items[i].target, name + 5); ++i) {
if (!inspect_elf(fileno(temp), links.items[i].path, links.items[i].mode,
arch, libc, emit, &edges, collected)) { fclose(temp); goto done; }
++scanned;
}
}
fclose(temp);
++scanned;
}
if (status != ARCHIVE_EOF) goto done;
if (emit) printf("scanned %zu ELF files\n", scanned);
if (needed) *needed = edges;
ok = 1;
done:
if (!ok) fprintf(stderr, "holypkg: payload ELF scan incomplete\n");
if (a) archive_read_free(a);
unlink(snapshot);
free(snapshot);
free(arch);
free(libc);
for (i = 0; i < links.count; ++i) { free(links.items[i].path); free(links.items[i].target); }
free(links.items);
return ok;
}
int holy_scan_local_with_output(const char *path, int emit)
{
return holy_scan_local_facts(path, emit, NULL);
}
int holy_scan_local_facts(const char *path, int emit, size_t *needed)
{
return scan(path, emit, needed, NULL);
}
int holy_scan_collect(const char *path, struct holy_scan_result *result)
{
memset(result, 0, sizeof *result);
return scan(path, 0, NULL, result);
}
void holy_scan_free(struct holy_scan_result *result)
{
size_t i;
for (i = 0; i < result->count; ++i) {
free(result->files[i].path);
holy_elf_free(&result->files[i].elf);
}
for (i = 0; i < result->script_count; ++i) {
free(result->scripts[i].path);
free(result->scripts[i].interpreter);
}
free(result->scripts);
for (i = 0; i < result->symlink_count; ++i) {
free(result->symlinks[i].path);
free(result->symlinks[i].target);
}
free(result->symlinks);
free(result->files);
memset(result, 0, sizeof *result);
}
int holy_scan_local(const char *path)
{
return holy_scan_local_with_output(path, 1);
}