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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ add_library(flapi-lib STATIC
src/api_server.cpp
src/archive_io.cpp
src/audit_logger.cpp
src/bundle_locator.cpp
src/auth_middleware.cpp
src/cache_manager.cpp
src/database_manager_cache_adapter.cpp
Expand Down Expand Up @@ -263,6 +264,7 @@ add_library(flapi-lib STATIC
src/prepared_value_converter.cpp
src/route_translator.cpp
src/security_auditor.cpp
src/selfpath.cpp
src/sql_parameter_classifier.cpp
src/sql_template_processor.cpp
src/sql_utils.cpp
Expand Down
147 changes: 147 additions & 0 deletions src/bundle_locator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "bundle_locator.hpp"
#include "selfpath.hpp"

#include <algorithm>
#include <cstdint>
#include <fstream>
#include <system_error>
#include <vector>

namespace flapi {

namespace {

constexpr std::size_t kEocdRecordSize = 22;
constexpr std::size_t kMaxCommentLen = 0xffffu;

// We accept padding well in excess of the 10 KiB spike default; the
// total tail buffer is EOCD + max comment + 64 KiB pad budget.
constexpr std::size_t kPaddingBudget = 65536;
constexpr std::size_t kScanBudget = kEocdRecordSize + kMaxCommentLen + kPaddingBudget;

std::uint16_t ReadU16(const std::uint8_t* p) {
return static_cast<std::uint16_t>(
static_cast<std::uint16_t>(p[0]) |
(static_cast<std::uint16_t>(p[1]) << 8));
}

std::uint32_t ReadU32(const std::uint8_t* p) {
return static_cast<std::uint32_t>(p[0])
| (static_cast<std::uint32_t>(p[1]) << 8)
| (static_cast<std::uint32_t>(p[2]) << 16)
| (static_cast<std::uint32_t>(p[3]) << 24);
}

} // namespace

std::optional<BundleLocation> LocateBundle(const std::filesystem::path& path) {
std::error_code ec;
const auto file_size = std::filesystem::file_size(path, ec);
if (ec || file_size < kEocdRecordSize) {
return std::nullopt;
}

std::ifstream in(path, std::ios::binary);
if (!in.is_open()) {
return std::nullopt;
}

const std::size_t tail_bytes =
static_cast<std::size_t>(std::min<std::uint64_t>(file_size, kScanBudget));
const std::uint64_t tail_start = file_size - tail_bytes;

std::vector<std::uint8_t> tail(tail_bytes);
in.seekg(static_cast<std::streamoff>(tail_start), std::ios::beg);
in.read(reinterpret_cast<char*>(tail.data()),
static_cast<std::streamsize>(tail_bytes));
if (!in) {
return std::nullopt;
}
if (tail.size() < kEocdRecordSize) {
return std::nullopt;
}

// Reverse-scan from the latest valid signature position. The latest
// (largest-offset) EOCD wins, since any earlier signature byte
// sequence in random leading data is a false positive.
const std::size_t max_start = tail.size() - kEocdRecordSize;
for (std::size_t i = max_start + 1; i-- > 0; ) {
if (tail[i] != 0x50 ||
tail[i + 1] != 0x4b ||
tail[i + 2] != 0x05 ||
tail[i + 3] != 0x06) {
continue;
}

const std::uint8_t* p = tail.data() + i;
const std::uint16_t this_disk = ReadU16(p + 4);
const std::uint16_t cd_start_disk = ReadU16(p + 6);
const std::uint16_t entries_this = ReadU16(p + 8);
const std::uint16_t entries_total = ReadU16(p + 10);
const std::uint32_t cd_size = ReadU32(p + 12);
const std::uint32_t cd_offset_arch = ReadU32(p + 16);
const std::uint16_t comment_len = ReadU16(p + 20);

// Multi-disk archives are not supported.
if (this_disk != 0 || cd_start_disk != 0) {
continue;
}
if (entries_this != entries_total) {
continue;
}

// The comment must fit in the tail.
const std::size_t comment_end = i + kEocdRecordSize + comment_len;
if (comment_end > tail.size()) {
continue;
}

// Anything after the comment up to file-EOF must be zero
// padding -- the libarchive tar-block rounding tolerance.
bool padding_ok = true;
for (std::size_t j = comment_end; j < tail.size(); ++j) {
if (tail[j] != 0) {
padding_ok = false;
break;
}
}
if (!padding_ok) {
continue;
}

const std::uint64_t eocd_file_offset = tail_start + i;

// The central directory sits immediately before the EOCD.
if (cd_size > eocd_file_offset) {
continue;
}
const std::uint64_t cd_file_offset = eocd_file_offset - cd_size;

if (cd_offset_arch > cd_file_offset) {
continue;
}
const std::uint64_t bundle_start = cd_file_offset - cd_offset_arch;

const std::uint64_t bundle_end = eocd_file_offset + kEocdRecordSize + comment_len;
if (bundle_end < bundle_start) {
continue; // overflow paranoia
}

BundleLocation loc;
loc.offset = bundle_start;
loc.size = bundle_end - bundle_start;
return loc;
}

return std::nullopt;
}

std::optional<BundleLocation> LocateBundleInSelf() {
try {
return LocateBundle(GetSelfPath());
} catch (...) {
return std::nullopt;
}
}

} // namespace flapi
34 changes: 34 additions & 0 deletions src/include/bundle_locator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include <cstdint>
#include <filesystem>
#include <optional>

namespace flapi {

// Location of an appended ZIP archive within a host binary.
//
// `offset` is the file offset of the first ZIP local file header --
// the byte you would seek to when slicing the file for libarchive.
// `size` is the total bytes from `offset` through (and including)
// the End-of-Central-Directory record plus its comment, excluding any
// trailing zero padding that the locator tolerates.
struct BundleLocation {
std::uint64_t offset = 0;
std::uint64_t size = 0;
};

// Reverse-scans the file at `path` for a ZIP End-of-Central-Directory
// record. Returns the bundle's location, or nullopt if no valid
// bundle is detected.
//
// Tolerates trailing zero padding after the EOCD record -- the
// spike-caught case of libarchive's default 10240-byte tar-block
// rounding pushing the EOCD off file-EOF.
std::optional<BundleLocation> LocateBundle(const std::filesystem::path& path);

// Convenience: scan the currently running executable. Returns nullopt
// if either the self-path lookup or the EOCD scan fails.
std::optional<BundleLocation> LocateBundleInSelf();

} // namespace flapi
15 changes: 15 additions & 0 deletions src/include/selfpath.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <filesystem>

namespace flapi {

// Returns the absolute path of the currently running executable.
// Implemented per-OS:
// - Linux: readlink("/proc/self/exe")
// - macOS: _NSGetExecutablePath
// - Windows: GetModuleFileNameW
// Throws std::system_error on resolution failure.
std::filesystem::path GetSelfPath();

} // namespace flapi
61 changes: 61 additions & 0 deletions src/selfpath.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "selfpath.hpp"

#include <cerrno>
#include <string>
#include <system_error>
#include <vector>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#include <cstdint>
#else
#include <unistd.h>
#endif

namespace flapi {

std::filesystem::path GetSelfPath() {
#ifdef _WIN32
std::vector<wchar_t> buf(MAX_PATH);
while (true) {
const DWORD n = GetModuleFileNameW(nullptr, buf.data(), static_cast<DWORD>(buf.size()));
if (n == 0) {
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(),
"GetModuleFileNameW failed");
}
if (n < buf.size()) {
return std::filesystem::path(std::wstring(buf.data(), n));
}
// Buffer too small (MS docs say n == buf.size() means truncated on
// older Windows; on newer it sets ERROR_INSUFFICIENT_BUFFER).
buf.resize(buf.size() * 2);
}
#elif defined(__APPLE__)
std::uint32_t size = 0;
// First call sizes the buffer.
_NSGetExecutablePath(nullptr, &size);
std::vector<char> buf(size);
if (_NSGetExecutablePath(buf.data(), &size) != 0) {
throw std::system_error(errno, std::generic_category(),
"_NSGetExecutablePath failed");
}
std::error_code ec;
auto canonical = std::filesystem::canonical(std::filesystem::path(buf.data()), ec);
if (ec) {
return std::filesystem::path(buf.data());
}
return canonical;
#else
std::error_code ec;
auto resolved = std::filesystem::read_symlink("/proc/self/exe", ec);
if (ec) {
throw std::system_error(ec, "read_symlink(/proc/self/exe) failed");
}
return resolved;
#endif
}

} // namespace flapi
1 change: 1 addition & 0 deletions test/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_executable(flapi_tests
main.cpp
archive_io_test.cpp
audit_logger_test.cpp
bundle_locator_test.cpp
auth_middleware_test.cpp
config_manager_test.cpp
config_manager_yaml_validation_test.cpp
Expand Down
Loading
Loading